附带一个 json 解析遇到的坑

经常会遇到 json 中的某个 key 是动态的情况, 比如下面这样:

{
    "friends": [
        {
            "id": 0,
            "name": "Robinson Woods"
        }
    ],
    "parent": [
        {
            "id": 1,
            "name": "Alejandra Mcdaniel"
        }
    ]
}

遇到这种, 只需要当成 map 类型来解析即可:

package main

import (
	"encoding/json"
	"log"
)

func main() {
	data := `{
		"friends": [
			{
				"id": 0,
				"name": "Robinson Woods"
			}
		],
		"parent": [
			{
				"id": 1,
				"name": "Alejandra Mcdaniel"
			}
		]
	}`
	type User struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}
	dataMap := make(map[string][]User)
	json.Unmarshal([]byte(data), &dataMap)
	for k, v := range dataMap {
		log.Printf(`%v,%v`, k, v)
	}
}

点我运行这段代码

而如果 json 返回格式不固定的时候, 这时就应该使用json.RawMessage 类型了, 官方解释如下

RawMessage is a raw encoded JSON value. It implements Marshaler and Unmarshaler and can be used to delay JSON decoding or precompute a JSON encoding.

大致意思就是暂时不解析这部分, 之后这部分可以进行二次解析, 最适合那种返回的内容格式不固定的情况. 之后的处理, 如果知道固定的几种格式可以通过断言来判断返回的是哪种, 不然的话就只能断言成 interface 类型然后一层层解析了.

而更复杂一点的情况, 动态 key 后面可能是一个没有 key 的数组, 这种目前 go 不支持, 只能通过类型断言来解决了

断言注意事项

interface{}类型在Unmarshal时,会自动将JSON转换为对应的数据类型:

JSON的boolean 转换为bool

JSON的数值 转换为float64

JSON的字符串 转换为string

JSON的Array 转换为[]interface{}

JSON的Object 转换为map[string]interface{}

JSON的null 转换为nil

所以如果你已经把 json 解析过一次了,是不可能再断言成 struct 类型的, struct统统变成了 map 类型, 这时有俩办法, 在解析时就用对应的 struct 类型,而不是写成 interface, 第二种就是把 map 再 Marshal 成 byte数组 类型, 然后再Unmarshal回 struct, 不用脑子想就知道不要用第二种…..

踩坑

json解析tag不区分大小写

遇到过各种奇奇怪怪的 json ,在解析下面这种, key 仅仅大小写不一样的时候就要注意了, 如果你平时写 struct 喜欢简洁, 就是用不到的字段不写的话, 这时可能就有问题了, 因为 标准库的json在解析 tag 时,是不区分大小写的, 会出现如下图的覆盖情况

忽略 json 字段别写错 tag

json:"omitempty" tag 的意思是如果该值为该类型的零值时会忽略,

想完全忽略某个 json 字段返回,应该使用json:"-"