前言
本文总结一下自己这一个多月写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类型无需添加额外方法的转换方案 ***
参考我的这篇博客:
基础: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()注意事项
-
Marshal()函数只有在转换成功的时候才会返回数据
-
JSON对象只支持string作为key,所以要编码一个map,必须是map[string]T这种类型(T是Go语言中的任意类型)
-
channel、complex和function是不能被编码成JSON的。
-
指针在编码的时候会输出指针指向的内容,而空指针会输出null。
基础: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)
}
}
~~~