Golang 處理 Json(二):解碼

golang 編碼 json 還比較簡單,而解析 json 則非常蛋疼。不像 PHP?一句 json_decode()?就能搞定。之前項目開發中,為了兼容不同客戶端的需求,請求的 content-type 可以是 json,也可以是 www-x-urlencode。然后某天前端希望某個后端服務提供 json 的處理,而當時后端使用 java 實現了 www-x-urlencode 的請求,對于突然希望提供 json 處理產生了極大的情緒。當時不太理解,現在看來,對于靜態語言解析未知的 JSON 確實是一項挑戰。

定義結構

與編碼 json 的 Marshal 類似,解析 json 也提供了 Unmarshal 方法。對于解析 json,也大致分兩步,首先定義結構,然后調用 Unmarshal 方法序列化。我們先從簡單的例子開始吧。

type Account struct {Email    string  `json:"email"`Password string  `json:"password"`Money    float64 `json:"money"`
}var jsonString string = `{"email": "phpgo@163.com","password" : "123456","money" : 100.5
}`func main() {account := Account{}err := json.Unmarshal([]byte(jsonString), &account)if err != nil {log.Fatal(err)}fmt.Printf("%+v\n", account)
}

輸出:

{Email:phpgo@163.com Password:123456 Money:100.5}

Unmarshal 接受一個 byte 數組和空接口指針的參數。和 sql 中讀取數據類似,先定義一個數據實例,然后傳其指針地址。

與編碼類似,golang 會將 json 的數據結構和 go 的數據結構進行匹配。匹配的原則就是尋找 tag 的相同的字段,然后查找字段。查詢的時候是 大小寫不敏感的

type Account struct {Email    string  `json:"email"`PassWord stringMoney    float64 `json:"money"`
}var jsonString string = `{"email": "phpgo@163.com","password" : "123456","money" : 100.5
}`func main() {account := Account{}err := json.Unmarshal([]byte(jsonString), &account)if err != nil {log.Fatal(err)}fmt.Printf("%+v\n", account)
}

輸出:

{Email:phpgo@163.com PassWord:123456 Money:100.5}

把 Password 的 tag 去掉,再修改成 PassWord,依然可以把 json 的 password 匹配到 PassWord,但是如果結構的字段是私有的,即使 tag 符合,也不會被解析:

type Account struct {Email    string  `json:"email"`password string   `json:"password"`Money    float64 `json:"money"`
}var jsonString string = `{"email": "phpgo@163.com","password" : "123456","money" : 100.5
}`func main() {account := Account{}err := json.Unmarshal([]byte(jsonString), &account)if err != nil {log.Fatal(err)}fmt.Printf("%+v\n", account)
}

輸出:

{Email:phpgo@163.com password: Money:100.5}

上面的 password 并不會被解析賦值 json 的 password,大小寫不敏感只是針對公有字段而言。

再尋找 tag 或字段的時候匹配不成功,則會拋棄這個 json 字段的值:

type Account struct {Email    string  `json:"email"`Password string   `json:"password"`
}var jsonString string = `{"email": "phpgo@163.com","password" : "123456","money" : 100.5
}`func main() {account := Account{}err := json.Unmarshal([]byte(jsonString), &account)if err != nil {log.Fatal(err)}fmt.Printf("%+v\n", account)
}

輸出:

{Email:phpgo@163.com Password:123456}

并不會有money字段被賦值。

string tag

在編碼的時候,我們使用 tag string,可以把結構定義的數字類型以字串形式編碼。同樣在解碼的時候,只有字串類型的數字,才能被正確解析,否則會報錯:

type Account struct {Email    string  `json:"email"`Password string  `json:"password"`Money    float64 `json:"money,string"`
}var jsonString string = `{"email": "phpgo@163.com","password" : "123456","money" : "100.5" // 不能沒有 雙引號,否則會報錯
}`func main() {account := Account{}err := json.Unmarshal([]byte(jsonString), &account)if err != nil {log.Fatal(err)}fmt.Printf("%+v\n", account)
}

輸出:

{Email:phpgo@163.com Password:123456 Money:100.5}

Money 是 float64 類型。

如果 json 的 money 是?100.5, 會得到下面的錯誤:

2017/03/08 17:39:21 json: invalid use of ,string struct tag, trying to unmarshal unquoted value into float64
exit status 1

-?tag

與編碼一樣,tag 的-也不會被解析,但是會初始化其 零值

type Account struct {Email    string  `json:"email"`Password string  `json:"password"`Money    float64 `json:"-"`
}var jsonString string = `{"email": "phpgo@163.com","password" : "123456","money" : 100.5
}`func main() {account := Account{}err := json.Unmarshal([]byte(jsonString), &account)if err != nil {log.Fatal(err)}fmt.Printf("%+v\n", account)
}

輸出:

{Email:phpgo@163.com Password:123456 Money:0}

稍微總結一下,解析 json 最好的方式就是定義與將要被解析 json 的結構。有人寫了一個小工具?json-to-go,自動將 json 格式化成 golang 的結構。

動態解析

通常根據?json 的格式預先定義 golang 的結構進行解析是最理想的情況。可是實際開發中,理想的情況往往都存在理想的愿望之中,很多 json 非但格式不確定,有的還可能是動態數據類型。

例如通常登錄的時候,往往既可以使用手機號做用戶名,也可以使用郵件做用戶名,客戶端傳的 json 可以是字串,也可以是數字。此時服務端解析就需要技巧了。

Decode

前面我們使用了簡單的方法 Unmarshal 直接解析 json 字串,下面我們使用更底層的方法 NewDecode 和 Decode 方法。

package mainimport ("encoding/json""fmt""io""log""strings"
)type User struct {UserName string `json:"username"`Password string `json:"password"`
}var jsonString string = `{"username": "phpgo@163.com","password": "123"
}`func Decode(r io.Reader) (u *User, err error) {u = new(User)err = json.NewDecoder(r).Decode(u)if err != nil {return}return
}func main() {user, err := Decode(strings.NewReader(jsonString))if err != nil {log.Fatal(err)}fmt.Printf("%#v\n", user)
}

輸出:

&main.User{UserName:"phpgo@163.com", Password:"123"}

我們定義了一個 Decode 函數,在這個函數進行 json 字串的解析。然后調用 json 的 NewDecoder 方法構造一個 Decode 對象,最后使用這個對象的 Decode 方法賦值給定義好的結構對象。

對于字串,可是使用 strings.NewReader 方法,讓字串變成一個 Stream 對象。

接口

如果客戶端傳的 username 的值是一個數字類型的手機號,那么上面的解析方法將會失敗。正如我們之前所介紹的動態類型行為一樣,使用空接口可以 hold 住這樣的情景。

?

type User struct {UserName interface{} `json:"username"`Password string `json:"password"`
}var jsonString string = `{"username": 15899758289,"password": "123"
}`func Decode(r io.Reader) (u *User, err error) {u = new(User)err = json.NewDecoder(r).Decode(u)if err != nil {return}return
}func main() {user, err := Decode(strings.NewReader(jsonString))if err != nil {log.Fatal(err)}fmt.Printf("%#v\n", user)
}

輸出:

&main.User{UserName:1.5899758289e+10, Password:"123"}

貌似成功了,可是返回的數字是科學計數法,有點奇怪。可以使用 golang 的斷言,然后轉換類型:

type User struct {UserName interface{} `json:"username"`Password string `json:"password"`
}var jsonString string = `{"username": 15899758289,"password": "123"
}`func Decode(r io.Reader) (u *User, err error) {u = new(User)if err = json.NewDecoder(r).Decode(u); err != nil {return}switch t := u.UserName.(type) {case string:u.UserName = tcase float64:u.UserName = int64(t)}return
}func main() {user, err := Decode(strings.NewReader(jsonString))if err != nil {log.Fatal(err)}fmt.Printf("%#v\n", user)
}

輸出:

&main.User{UserName:15899758289, Password:"123"}

看起來挺好,可是我們的 UserName 字段始終是一個空接口,使用他的時候,還是需要轉換類型,這樣情況看來,解析的時候就應該轉換好類型,那么用的時候就省心了。

修改定義的結構如下:

type User struct {UserName interface{} `json:"username"`Password string `json:"password"`Email stringPhone int64
}

這樣就能通過?fmt.Println(user.Email + " add me")?使用字段進行操作了。當然也有人認為 Email 和 Phone 純粹多于,因為使用的時候,還是需要再判斷當前結構實例是那種情況。

延遲解析

因為 UserName 字段,實際上是在使用的時候,才會用到他的具體類型,因此我們可以延遲解析。使用 json.RawMessage 方式,將 json 的字串繼續以 byte 數組方式存在。

type User struct {UserName json.RawMessage `json:"username"`Password string `json:"password"`Email stringPhone int64
}var jsonString string = `{"username": "phpgo@163.com","password": "123"
}`func Decode(r io.Reader) (u *User, err error) {u = new(User)if err = json.NewDecoder(r).Decode(u); err != nil {return}var email stringif err = json.Unmarshal(u.UserName, &email); err == nil {u.Email = emailreturn}var phone int64if err = json.Unmarshal(u.UserName, &phone); err == nil {u.Phone = phone}return
}func main() {user, err := Decode(strings.NewReader(jsonString))if err != nil {log.Fatal(err)}fmt.Printf("%#v\n", user)
}

總體而言,延遲解析和使用空接口的方式類似。需要再次調用 Unmarshal 方法,對 json.RawMessage 進行解析。原理和解析到接口的形式類似。

不定字段解析

對于未知 json 結構的解析,不同的數據類型可以映射到接口或者使用延遲解析。有時候,會遇到 json 的數據字段都不一樣的情況。例如需要解析下面一個 json 字串:

接口配合斷言

var jsonString string = `{"things": [{"name": "Alice","age": 37},{"city": "Ipoh","country": "Malaysia"},{"name": "Bob","age": 36},{"city": "Northampton","country": "England"}]}`

json 字串的是一個對象,其中一個 key things 的值是一個數組,這個數組的每一個 item 都未必一樣,大致是兩種數據結構,可以抽象為 person 和 place。即,定義下面的結構體:

type Person struct {Name string `json:"name"`Age  int    `json:"age"`
}type Place struct {City    string `json:"city"`Country string `json:"country"`
}

接下來我們 Unmarshal json 字串到一個 map 結構,然后迭代 item 并使用 type 斷言的方式解析數據:

func decode(jsonStr []byte) (persons []Person, places []Place) {var data map[string][]map[string]interface{}err := json.Unmarshal(jsonStr, &data)if err != nil {fmt.Println(err)return}for i := range data["things"] {item := data["things"][i]if item["name"] != nil {persons = addPerson(persons, item)} else {places = addPlace(places, item)}}return
}

迭代的時候會判斷 item 是否是 person 還是 place,然后調用對應的解析方法:

func addPerson(persons []Person, item map[string]interface{}) []Person {name := item["name"].(string)age := item["age"].(float64)person := Person{name, int(age)}persons = append(persons, person)return persons
}func addPlace(places []Place, item map[string]interface{}) []Place {city := item["city"].(string)country := item["country"].(string)place := Place{City: city, Country: country}places = append(places, place)return places
}

代碼匯總:

type Person struct {Name string `json:"name"`Age  int    `json:"age"`
}type Place struct {City    string `json:"city"`Country string `json:"country"`
}func decode(jsonStr []byte) (persons []Person, places []Place) {var data map[string][]map[string]interface{}err := json.Unmarshal(jsonStr, &data)if err != nil {fmt.Println(err)return}for i := range data["things"] {item := data["things"][i]if item["name"] != nil {persons = addPerson(persons, item)} else {places = addPlace(places, item)}}return
}func addPerson(persons []Person, item map[string]interface{}) []Person {name := item["name"].(string)age := item["age"].(float64)person := Person{name, int(age)}persons = append(persons, person)return persons
}func addPlace(places []Place, item map[string]interface{}) []Place {city := item["city"].(string)country := item["country"].(string)place := Place{City: city, Country: country}places = append(places, place)return places
}var jsonString string = `{"things": [{"name": "Alice","age": 37},{"city": "Ipoh","country": "Malaysia"},{"name": "Bob","age": 36},{"city": "Northampton","country": "England"}]
}`func main() {personA, placeA := decode([]byte(jsonString))fmt.Printf("%+v\n", personA)fmt.Printf("%+v\n", placeA)
}

輸出:

[{Name:Alice Age:37} {Name:Bob Age:36}]
[{City:Ipoh Country:Malaysia} {City:Northampton Country:England}]

混合結構

混合結構很好理解,如同我們前面解析 username 為 email 和 phone 兩種情況,就在結構中定義好這兩種結構即可。

type Mixed struct {Name    string `json:"name"`Age     int `json:"age"`city    string `json:"city"`Country string  `json:"country"`
}

混合結構的思路很簡單,借助 golang 會初始化沒有匹配的 json 和拋棄沒有匹配的 json,給特定的字段賦值。比如每一個 item 都具有四個字段,只不過有的會匹配 person 的 json 數據,有的則是匹配 place。沒有匹配的字段則是零值。接下來在根據 item 的具體情況,分別賦值到對于的 Person 或 Place 結構。

type Person struct {Name string `json:"name"`Age  int    `json:"age"`
}type Place struct {City    string `json:"city"`Country string `json:"country"`
}type Mixed struct {Name    string `json:"name"`Age     int `json:"age"`city    string `json:"city"`Country string  `json:"country"`
}func decode(jsonStr []byte) (persons []Person, places []Place) {var data map[string][]Mixederr := json.Unmarshal(jsonStr, &data)if err != nil {fmt.Println(err)return}fmt.Printf("%+v\n", data["things"])for i := range data["things"] {item := data["things"][i]if item.Name != "" {persons = append(persons, Person{Name: item.Name, Age: item.Age})} else {places = append(places, Place{City: item.city, Country:item.Country})}}return
}var jsonString string = `{"things": [{"name": "Alice","age": 37},{"city": "Ipoh","country": "Malaysia"},{"name": "Bob","age": 36},{"city": "Northampton","country": "England"}]
}`func main() {personA, placeA := decode([]byte(jsonString))fmt.Printf("%+v\n", personA)fmt.Printf("%+v\n", placeA)
}

輸出:

[{Name:Alice Age:37 city: Country:} {Name: Age:0 city: Country:Malaysia} {Name:Bob Age:36 city: Country:} {Name: Age:0 city: Country:England}]
[{Name:Alice Age:37} {Name:Bob Age:36}]
[{City: Country:Malaysia} {City: Country:England}]

混合結構的解析方式也很不錯。思路還是借助了解析 json 中拋棄不要的字段,借助零值處理。

json.RawMessage

json.RawMessage 非常有用,延遲解析也可以使用這個樣例。我們已經介紹過類似的技巧,下面就貼代碼了:

type Person struct {Name string `json:"name"`Age  int    `json:"age"`
}type Place struct {City    string `json:"city"`Country string `json:"country"`
}func addPerson(item json.RawMessage, persons []Person) ([]Person) {person := Person{}if err := json.Unmarshal(item, &person); err != nil {fmt.Println(err)} else {if person != *new(Person) {persons = append(persons, person)}}return persons
}func addPlace(item json.RawMessage, places []Place) ([]Place) {place := Place{}if err := json.Unmarshal(item, &place); err != nil {fmt.Println(err)} else {if place != *new(Place) {places = append(places, place)}}return places
}func decode(jsonStr []byte) (persons []Person, places []Place) {var data map[string][]json.RawMessageerr := json.Unmarshal(jsonStr, &data)if err != nil {fmt.Println(err)return}for _, item := range data["things"] {persons = addPerson(item, persons)places = addPlace(item, places)}return
}var jsonString string = `{"things": [{"name": "Alice","age": 37},{"city": "Ipoh","country": "Malaysia"},{"name": "Bob","age": 36},{"city": "Northampton","country": "England"}]
}`func main() {personA, placeA := decode([]byte(jsonString))fmt.Printf("%+v\n", personA)fmt.Printf("%+v\n", placeA)
}

輸出:

[{Name:Alice Age:37} {Name:Bob Age:36}]
[{City:Ipoh Country:Malaysia} {City:Northampton Country:England}]

把 things 的 item 數組解析成一個 json.RawMessage,然后再定義其他結構逐步解析。上述這些例子其實在真實的開發環境下,應該盡量避免。像 person 或是 place 這樣的數據,可以定義兩個數組分別存儲他們,這樣就方便很多。不管怎么樣,通過這個略傻的例子,我們也知道了如何解析 json 數據。

總結

關于 golang 解析 json 的介紹基本就這么多。想要解析越簡單,就需要定義越明確的 map 結構。面對無法確定的數據結構或類型,再動態解析方面可以借助接口與斷言的方式解析,也可以使 json.RawMessage 延遲解析。具體使用情況,還得考慮實際的需求和應用場景。

總而言之,使用 json 作為現在 api 的數據通信方式已經很普遍了。

?

?

相關文章

Golang 處理 Json(一):編碼

Golang 處理 Json(二):解碼

?

?

參考:

http://json.org/

http://www.jianshu.com/p/31757e530144

https://golang.org/pkg/encoding/json/?

轉載于:https://www.cnblogs.com/52php/p/6518728.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/255173.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/255173.shtml
英文地址,請注明出處:http://en.pswp.cn/news/255173.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

五、畸變矯正—讓世界不在扭曲

五、畸變矯正—讓世界不在扭曲 這篇博文所要講述的內容,是標定的主要用途之一:矯正攝像機的畸變。對于圖像畸變矯正的方法,張正友教授也在其大作“A Flexible New Technique forCamera Calibration”中給出。 玉米在這里先為大家介紹一下&…

第二階段個人沖刺08

昨天做了什么? 解決新建項目時會遇到“Your android sdk is out of date or is missing templates”的問題,,實現學生交流區,只有學生和管理員有權查看,教師無權查看的功能 今天要做什么? 實現學生交流區&a…

HALCON示例程序distance_transform.hdev通過distance_transform檢測線的缺陷

HALCON示例程序distance_transform.hdev通過distance_transform檢測線的缺陷 示例程序源碼(加注釋) 關于顯示類函數解釋 dev_close_window () dev_open_window (0, 0, 400, 400, ‘black’, WindowHandle)通過一系列的坐標點生成多邊形像素輪廓 gen_re…

java面試-Java并發編程(二)——重排序

當我們寫一個單線程程序時,總以為計算機會一行行地運行代碼,然而事實并非如此。 什么是重排序? 重排序指的是編譯器、處理器在不改變程序執行結果的前提下,重新排列指令的執行順序,以達到最佳的運行效率。 重排序分類 …

《MySQL必知必會》[01] 基本查詢

《MySQL必知必會》(點擊查看詳情)1、寫在前面的話這本書是一本MySQL的經典入門書籍,小小的一本,也受到眾多網友推薦。之前自己學習的時候是啃的清華大學出版社的計算機系列教材《數據庫系統概論》,基礎也算是半罐水&am…

(七)立體標定與立體校正 【計算機視覺學習筆記--雙目視覺幾何框架系列】

七、立體標定與立體校正 這篇博文中,讓玉米和大家一起了解一下,張氏標定是怎樣過渡到立體標定的?在這里主要以雙目立體視覺進行分析。對于雙目立體視覺,我們有兩個攝像頭。它們就像人的一雙眼睛一樣,從不同的方向看世界…

HALCON示例程序edge_segments.hdev提取連續的邊緣段

HALCON示例程序edge_segments.hdev提取連續的邊緣段 示例程序源碼(加注釋) 關于顯示類函數解釋 dev_update_off () dev_close_window () read_image (Image, ‘mreut’) get_image_size (Image, Width, Height) dev_open_window_fit_image (Image, 0, 0…

讓 jQuery UI draggable 適配移動端

背景: 在移動端,本人要實現對某個元素的拖動,想到使用 jQuery UI 的 draggable 功能。但是發現此插件的拖動只支持PC端,不支持移動端。 原因: 原始的 jQuery UI 里,都是mousedown、mousemove、mouseup來描述…

LAMP(7限定某個目錄禁止解析php、 限制user_agent、 PHP相關配置、PHP擴展模塊

限定某個目錄禁止解析php防止***上傳一個目錄文件php&#xff0c;網站會從而解析php,對我們的網站有很大的危險。因此&#xff0c;我們需要在能上傳文件的目錄直接禁止解析PHP代碼禁止步驟1.編輯虛擬主機配置文件&#xff1a;增添內容核心配置文件內容<Directory /data/wwwr…

編譯器的功能是什么

1、編譯器就是將“一種語言&#xff08;通常為高級語言&#xff09;”翻譯為“另一種語言&#xff08;通常為低級語言&#xff09;”的程序。一個現代編譯器的主要工作流程&#xff1a;源代碼 (source code) → 預處理器(preprocessor) → 編譯器 (compiler) → 目標代碼 (obje…

八、走向三維

八、走向三維 我們前面花了七篇博文做鋪墊&#xff0c;我們所做的一切努力都是為了最后的這一擊——立體成像。因為玉米的這個系列文章是對雙目視覺幾何框架的總結。此處跳過匹配&#xff0c;假設左右圖像點的完美匹配的。只看在幾何上&#xff0c;三維坐標是如何被還原的。相對…

通用連接池項目開啟

通用連接池項目開啟 待完善......轉載于:https://www.cnblogs.com/aresyl/p/5552092.html

HALCON示例程序fin.hdev通過形態學檢測缺陷

HALCON示例程序fin.hdev通過形態學檢測缺陷 示例程序源碼&#xff08;加注釋&#xff09; 關于顯示類函數解釋 dev_update_window (‘off’) read_image (Fins, ‘fin’ [1:3]) get_image_size (Fins, Width, Height) dev_close_window () dev_open_window (0, 0, Width[0],…

FEZ前端模塊化工程開發框架

FEZ FEZ 是面向前端模塊化工程的開發框架。主要目的是統一前端開發模式和項目開發結構&#xff0c;自動化前端工作流&#xff0c;提高開發效率和開發質量。使用持續集成等軟件工程的架構模式&#xff0c;集成眾多業界先進的解決方案&#xff0c;讓研發人員更專注于業務邏輯的實…

棧內存和堆內存

堆和棧這兩個字我們已經接觸多很多次&#xff0c;那么具體是什么存在棧中什么存在堆中呢&#xff1f;就拿JavaScript中的變量來說&#xff1a; 首先JavaScript中的變量分為基本類型和引用類型。 基本類型就是保存在棧內存中的簡單數據段&#xff0c;而引用類型指的是那些保存在…

L~M方法

L~M方法&#xff1a; L~M&#xff08;Levenberg-Marquardt&#xff09;方法有些讓人摸不清頭腦。玉米覺得L~M讓人困擾的主要原因有兩點&#xff1a;一是L~M從何而來、二是L~M怎么樣用&#xff1f;因為玉米也不是研究最優化理論的&#xff0c;所以玉米在這里用較為通俗的觀點&a…

Android——Activity去除標題欄和狀態欄

一、在代碼中設置 public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //去除title requestWindowFeature(Window.FEATURE_NO_TITLE); //去掉Activity上面的狀態欄getWindow().setFlags(WindowManager.LayoutParams. FLAG_FULLSC…

Ghosts for Tea

Ghosts for Tea 喝茶&#xff1f;鬧鬼&#xff1f; Ten pence for a view over the bay . said the old man with the telescope. Lovely clearmorning. Have a look at the old lighthouse and the remains of the great shipwreckof 1935. “在如此可愛清爽的早晨&#xff0…

HALCON示例程序find_pads.hdev通過fit_rectangle2_contour_xld繪制精準輪廓

HALCON示例程序find_pads.hdev通過fit_rectangle2_contour_xld繪制精準輪廓 示例程序源碼&#xff08;加注釋&#xff09; 關于顯示類函數解釋 dev_update_pc (‘off’) dev_update_window (‘off’) dev_update_var (‘off’) read_image (Image, ‘die_pads’) dev_close_w…

IDEA將項目上傳至碼云/GitHub托管

前言 好久都沒有寫博客了&#xff0c;由于博主之前一直都在上班處于加班的階段&#xff0c;所以根本就沒有時間去學習。現在請假回到學校寫論文&#xff0c;有時間來學習了。 所以會不斷的進行博客的更新&#xff0c;以及分享我在公司學到的一些新的技術&#xff0c;希望大家多…