前言
本文總結一下自己這一個多月寫Go代碼以來有關JSON序列化與反序列化的學習及實踐使用經驗,如有更好的包或者解決方法歡迎下方留言。
一些實踐經驗
將結構復雜的map數據直接解析為string處理 ***
實際中有個API返回的數據是這樣結構的:

{"id": "23846617xxxxx", "name": "sisi-dpa-xxx", "targeting": { "age_max": 34, "age_min": 25, "app_install_state": "not_installed", "excluded_custom_audiences": [ { "id": "23843939736920230", "name": "install-7D" } ], "flexible_spec": [ { "interests": [ { "id": "6002839660079", "name": "化妝品" }, { "id": "6002991239659", "name": "母子關系" }, { "id": "6003054884732", "name": "抵用券" }, { "id": "6003088846792", "name": "美容院" }, { "id": "6003103108917", "name": "精品屋" }, { "id": "6003188355978", "name": "連衣裙" }, { "id": "6003198476967", "name": "手提包" }, { "id": "6003198851865", "name": "約會" }, { "id": "6003220634758", "name": "折扣商店" }, { "id": "6003255640088", "name": "太陽鏡" }, { "id": "6003266225248", "name": "珠寶" }, { "id": "6003346592981", "name": "線上購物" }, { "id": "6003348453981", "name": "鞋" }, { "id": "6003351764757", "name": "三項全能" }, { "id": "6003390752144", "name": "購物廣場" }, { "id": "6003415393053", "name": "童裝" }, { "id": "6003443805331", "name": "香水" }, { "id": "6003445506042", "name": "婚姻" }, { "id": "6003456330903", "name": "美發產品" }, { "id": "6003476182657", "name": "家人" }, { "id": "6004100985609", "name": "友情" }, { "id": "6007828099136", "name": "奢侈品" }, { "id": "6011366104268", "name": "女裝" }, { "id": "6011994253127", "name": "男裝" } ] } ], "genders": [ 2 ], "geo_locations": { "countries": [ "SA" ], "location_types": [ "home", "recent" ] }, "targeting_optimization": "expansion_all", "user_device": [ "iPad", "iPhone", "iPod" ], "user_os": [ "iOS" ], "brand_safety_content_filter_levels": [ "FACEBOOK_STANDARD" ], "publisher_platforms": [ "facebook", "instagram" ], "facebook_positions": [ "feed", "video_feeds", "instant_article", "instream_video", "story", "search" ], "instagram_positions": [ "stream", "story", "explore" ], "device_platforms": [ "mobile" ] } }
可以看到,返回的字典中只有3個key,id、name與targeting,targeting里面的結構太復雜了,實際中我們是將這個復雜結構序列化為string存入數據庫的,定義結構體如下:
type AdSetFB struct { AdsetFbId string `json:"id" gorm:"-"` AdsetFbName string `json:"name" gorm:"-"` Targeting interface{} `json:"targeting" gorm:"-"` }
在做數據處理的時候將復雜的targeting數據直接序列化為string:
// AdSet中的數據做處理 func (a *AdSetFB) Format() { // interface轉json if s, err := json.Marshal(a.Targeting); err == nil{ a.Targeting = string(s) }else{ a.Targeting = "" } }
返回的結構是一個列表格式的而不是字典格式string的處理 ***
實際中遇到一個這樣返回格式的數據:

[ { "results": [ { "customer": { "resourceName": "customers/xxx", "id": "xxx", "descriptiveName": "hhh-自投-jjjs", "timeZone": "Asia/Shanghai", "manager": false, "testAccount": false } } ], "fieldMask": "customer.timeZone,customer.id,customer.manager,customer.testAccount,customer.optimizationScore,customer.descriptiveName" } ]
直接解析為切片的解決方式
一開始還不太熟練JSON與go基礎類型的相互轉換,想着先將結果Unmarshal成一個列表,然后一步步的使用interface對象的轉型去處理:

// 先將結果轉成切片 var retInfo []map[string]interface{} err1 := json.Unmarshal([]byte(strRet),&retInfo) if err1 != nil{ fmt.Println(err1.Error()) } if len(retInfo) < 1{ fmt.Println("沒返回數據") } // 1、獲取interface結果(結果只有一個所以沒用循環) fmt.Println("results>>> ",retInfo[0]["results"]) resInterface := retInfo[0]["results"] fmt.Printf("type_resInterface>>> %T \n",resInterface)// []interface {} // 2、轉成列表套interface格式的 ret1 := resInterface.([]interface{}) fmt.Printf("ret1>>> %T \n",ret1) // []interface {} // 3、里層的數據也需要轉一下(結果只有一個所以沒用循環) customerInterface := ret1[0].(map[string]interface{}) fmt.Printf("type_customerDic>>> %T \n",customerInterface) // map[string]interface {} // 4、獲取里層的customer字典 customerDic := customerInterface["customer"] // 5、獲取customer字典的信息 // 注意這里的數據都是interface類型的,需要轉一下!轉成對應的格式!!! id := customerDic.(map[string]interface{})["id"].(string) name := customerDic.(map[string]interface{})["descriptiveName"].(string) timeZone := customerDic.(map[string]interface{})["timeZone"].(string) if name == nil{ name = fmt.Sprintf("%s_noName",customerId) } fmt.Printf("id>>> %T \n",id)// string fmt.Printf("name>>> %T \n",name)// string fmt.Printf("timeZone>>> %T \n",timeZone)// string
解析為切片套結構體的方法 ***
返回的格式是一個列表格式的數據。這樣格式的數據可以轉成切片嵌套結構體的結構去處理,下面是我實現的完整代碼:
package main import ( "encoding/json" "fmt" ) // 返回的results結構 type ResultsResponse struct { Results []*CustomerParent `json:"results"` FieldMask string `json:"fieldMask"` } // 父級字典 type CustomerParent struct { // 兒子字典 CustomerChildren *Customer `json:"customer"` } // 基礎結構 type Customer struct { Id string `json:"id"` Name string `json:"descriptiveName"` TimeZone string `json:"timeZone"` Manager bool `json:"manager"` } func main() { // 模擬返回的數據 responseStr := `[ { "results": [ { "customer": { "resourceName": "customers/xxx", "id": "xxx", "descriptiveName": "hhh-自投-jjja", "timeZone": "Asia/Shanghai", "manager": true, "testAccount": false } } ], "fieldMask": "customer.timeZone,customer.id,customer.manager,customer.testAccount,customer.optimizationScore,customer.descriptiveName" } ]` // 將結果解析為 切片套結構體的形式 var responseSlice []*ResultsResponse // 將字符串解析為 存放map的切片 if err := json.Unmarshal([]byte(responseStr), &responseSlice); err == nil { fmt.Printf("rep: %v \n", responseSlice) // rep: [0xc000090180] 切片中存的是地址 // 從切片中獲取數據 for _, retObj := range responseSlice { resultsLst := retObj.Results // 獲取resultsLst中的數據:先找父親再找兒子 for _, customerParentObj := range resultsLst { customerObj := customerParentObj.CustomerChildren id := customerObj.Id name := customerObj.Name manager := customerObj.Manager fmt.Printf("id: %T, %v \n", id, id) fmt.Printf("name: %T, %v \n", name, name) fmt.Printf("manager: %T, %v \n", manager, manager) /* id: string, xxx name: string, hhh-自投-jjja manager: bool, true */ } } } else { fmt.Println("解析失敗!") } }
遇到數字與string類型無需添加額外方法的轉換方案 ***
參考我的這篇博客:Golang結構體與JSON相互轉換時的小技巧
基礎:JSON的序列化
map轉JSON
package main import ( "encoding/json" "fmt" ) func main() { // 定義一個map變量並初始化 m := map[string][]string{ "level": {"debug"}, "message": {"File Not Found","Stack OverFlowe"}, } // 將map解析為JSON格式 if data, err := json.Marshal(m);err == nil{ fmt.Printf("%s\n",data)// {"level":["debug"],"message":["File Not Found","Stack OverFlowe"]} } // 生成便於閱讀的格式 if data, err := json.MarshalIndent(m,""," ");err == nil{ fmt.Printf("%s\n",data) /* { "level": [ "debug" ], "message": [ "File Not Found", "Stack OverFlowe" ] } */ } }
結構體與JSON的互相轉換
結構體轉換成JSON在開發中經常會用到。
json包是通過反射機制來實現編解碼的,因此結構體必須導出所轉換的字段,沒有導出的字段不會被json包解析。
package main import ( "encoding/json" "fmt" ) type Student struct{ Id int64 `json:"id,string"` // 注意這里的 ,string Name string `json:"name"` msg string // 小寫的不會被json解析 } func main() { // 定義一個結構體切片初始化 stucent := Student{"whw",123123,"666"} // 將結構體轉成json格式 data, err := json.Marshal(stucent) if err == nil{ // 注意這里將 Id轉換為了string fmt.Printf("%s\n",data)//{"name":"whw","id":"123123"} } // json反序列化為結構體 這里的id是 字符串類型的。。。 s := `{"name":"whw","id":"123123"}` var StuObj Student if err := json.Unmarshal([]byte(s),&StuObj);err != nil{ fmt.Println("err>>",err) }else{ // 反序列化后 成了 int64 (,string 的作用) fmt.Printf("%T \n",StuObj.Id)// int64 fmt.Printf("%v \n",StuObj)// {whw 123123 } } }
序列化時的結構體字段標簽
json包在解析結構體時,如果遇到key為JSON的字段標簽,則會按照一定規則解析該標簽:第一個出現的是字段在JSON串中使用的名字,之后為其他選項,例如omitempty指定空值字段不出現在JSON中。如果整個value為“-”,則不解析該字段。
package main import ( "encoding/json" "fmt" ) type Student struct{ Name string `json:"__name"` Id int64 `json:"id"` Age int `json:"-"` // 不解析該字段 msg string // 小寫的不會被json解析 } func main() { // 定義一個結構體切片初始化 stucent := Student{"whw",123123,12,"阿斯頓發送到發"} // 將結構體轉成json格式 data, err := json.Marshal(stucent) if err == nil{ // 注意這里將 Id轉換為了string fmt.Printf("%s\n",data)//{"__name":"whw","id":123123} } }
序列化時的匿名字段
json包在解析匿名字段時,會將匿名字段的字段當成該結構體的字段處理:
package main import ( "encoding/json" "fmt" ) type Point struct{ X, Y int } type Student struct{ Point Name string `json:"__name"` Id int64 `json:"id"` Age int `json:"-"` // 不解析該字段 msg string // 小寫的不會被json解析 } func main() { // 定義一個結構體切片初始化 po := Point{1,2} stucent := Student{po,"whw",123123,12,"阿斯頓發送到發"} // 將結構體轉成json格式 data, err := json.Marshal(stucent) if err == nil{ // 注意這里將 Id轉換為了string fmt.Printf("%s\n",data)// {"X":1,"Y":2,"__name":"whw","id":123123} } }
Marshal()注意事項
-
-
JSON對象只支持string作為key,所以要編碼一個map,必須是map[string]T這種類型(T是Go語言中的任意類型)
-
channel、complex和function是不能被編碼成JSON的。
-
基礎:JSON的返序列化(解析)
JSON轉切片
package main import ( "encoding/json" "fmt" ) func main() { data := `[{"level":"debug","msg":"filexxx"},{"naem":"whw"}]` var dpInfos []map[string]string // 將字符串解析為map切片 if err := json.Unmarshal([]byte(data),&dpInfos);err == nil{ fmt.Println(dpInfos)// [map[level:debug msg:filexxx] map[naem:whw]] } }
JSON轉結構體
JSON可以轉換成結構體。同編碼一樣,json包是通過反射機制來實現解碼的,因此結構體必須導出所轉換的字段,不導出的字段不會被json包解析。另外解析時不區分大小寫。
package main import ( "encoding/json" "fmt" ) type DebugInfo struct { Level string Msg string author string // 首字母小寫不會被json解析 } func (d *DebugInfo) JsonDump() string{ return fmt.Sprintf("Level:%s,Msg:%s",d.Level,d.Msg) } func main() { // 定義JSON格式字符串 data := `[{"level":"debug","msg":"hahaha"},` + `{"level":"error","msg":"hehehe"}]` var dbgInfos []DebugInfo // 轉成結構體切片 if err := json.Unmarshal([]byte(data),&dbgInfos); err == nil{ fmt.Printf("%T %v \n",dbgInfos,dbgInfos)//[]main.DebugInfo [{debug hahaha } {error hehehe }] } }
反序列化時結構體字段標簽
解碼時依然支持結構體字段標簽,規則和編碼時一樣:
package main import ( "encoding/json" "fmt" ) type DebugInfo struct { Level string `json:"level"` Msg string `json:"message"` // json里面的為message Author string `json:"-"` // 不會被解析 } func (d *DebugInfo) JsonDump() string{ return fmt.Sprintf("Level:%s,Msg:%s",d.Level,d.Msg) } func main() { // 定義JSON格式字符串 data := `[{"level":"debug","message":"hahaha"},` + `{"level":"error","message":"hehehe"}]` var dbgInfos []DebugInfo // 轉成結構體切片 if err := json.Unmarshal([]byte(data),&dbgInfos); err == nil{ fmt.Printf("%T %v \n",dbgInfos,dbgInfos)//[]main.DebugInfo [{debug hahaha } {error hehehe }] } }
反序列化時的匿名字段
package main import ( "encoding/json" "fmt" ) type Point struct { X, Y int } type DebugInfo struct { Point Level string `json:"level"` Msg string `json:"message"` // json里面的為message Author string `json:"-"` // 不會被解析 } func (d *DebugInfo) JsonDump() string{ return fmt.Sprintf("Level:%s,Msg:%s",d.Level,d.Msg) } func main() { // 定義JSON格式字符串 data := `{"level":"debug","message":"hahaha","X":1,"Y":222}` var dbgInfos DebugInfo // 轉成結構體切片 if err := json.Unmarshal([]byte(data),&dbgInfos); err == nil{ fmt.Printf("%T %v \n",dbgInfos,dbgInfos)//main.DebugInfo {{1 222} debug hahaha } }else{ fmt.Println(err) } }
~~~