前言

本文总结一下自己这一个多月写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)
    }
}

~~~