Golang序列化与反序列化操作总结及一些实践经验
前言
本文总结一下自己这一个多月写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) } }
~~~