一、JSON 与 序列化和反序列化
在 Go 编程 | 连载 17 - 结构体方法 中层简单的介绍过 JSON 序列化与反序列化,在本文中将更详细的讲述 JSON 以及结构体标签是如何细致的控制 JSON 的,以及如何使用 HTTP 获取 API 的 JSON Response Body。
JSON 既 JavaScript Object Notation,JavaScript 对象表示法是一种用于存储和交换数据的格式,JSON 可以以键值对的方式表示数据,也可以通过数组的方式博鳌是数据。
JSON 最初是 JavaScript 的一个子集,现在 JSON 已经独立在语言之外,并且大多数语言都支持 JSON 编码和解码,事实上 JSON 格式的数据已经成为标准并且已经取代 XML 既可扩展标记性语言,尤其是 Web 开发中前后端交互都是通过 JSON 格式数据实现的。
比如掘金创作者中心页面的 API /content_api/v1/author_center/data/card
的响应数据为 JSON 格式
{ "err_no": 0, "err_msg": "success", "data": { "date": "2022-08-31", "datas": { "all_article_collect": { "cnt": 269, "than_before": 6 }, "all_article_comment": { "cnt": 95, "than_before": 5 }, "all_article_digg": { "cnt": 6780, "than_before": 15 }, "all_article_display": { "cnt": 2338241, "than_before": 115639 }, "all_article_view": { "cnt": 150116, "than_before": 3734 }, "all_follower": { "cnt": 208, "than_before": 6 } } } } 复制代码
json Tag 与序列化
Go 语言的标准库 encoding\json
提供了结构体编码和解码(序列化和反序列化)的函数 Marshal 和 Unmarshal 函数。
package main import ( "encoding/json" "fmt" "log" ) func main(){ adds := []string{"NYC", "BOS", "CHA", "WDC"} tony := Human{"Tony", 33, adds} // %+v 占位符会输出结构体时会带上 K,%v 输出结构体只有 V 没有 K fmt.Printf("%+v\n", tony) fmt.Printf("tony 变量的数据类型是:%T\n", tony) // 序列化 tonyByte, err := json.Marshal(tony) if err != nil { // 报错会终止程序 log.Fatal(err) } else { tonyJson := string(tonyByte) fmt.Println(tonyJson) fmt.Printf("tonyJson 数据的格式为:%T\n", tonyJson) } } type Human struct { Name string Age int Address []string } 复制代码
执行上述代码,输出结果如下:
{Name:Tony Age:33 Address:[NYC BOS CHA WDC]} tony 变量的数据类型是:main.Human {"Name":"Tony","Age":33,"Address":["NYC","BOS","CHA","WDC"]} tonyJson 变量的数据类型是:string 复制代码
根据输出结果可以确定已经成功的将一个结构体数据转换成字符串类型的 JSON 格式的数据,但是还有一个问题,在获取的 JSON 格式的数据中所有的 Key 的首字母都是大写的。
虽然 JSON 没有官方标准说 Key 的首字母一定要小写,但是在实际应用中的习惯都是将 Key 的首字母小写,Key 有多个单词可以使用下划线或者驼峰命名法来命名。
对于这个问题可以使用结构体的 JSON 标签来解决,修改结构体为
type Human struct { Name string `json:"name"` Age int `json:"age"` Address []string `json:"address"` } 复制代码
保持 main 函数不变再次执行,输出结果如下:
{Name:Tony Age:33 Address:[NYC BOS CHA WDC]} tony 变量的数据类型是:main.Human {"name":"Tony","age":33,"address":["NYC","BOS","CHA","WDC"]} tonyJson 变量的数据类型是:string 复制代码
可以看出 Key 的首字母已经变为 json 标签指定的内容,除此之外 json 标签还可以指定为 omitempty
,该标签值表示当结构体字段为空时就忽略该字段。
type Human struct { // 注意 json 标签中的内容使用逗号隔开,逗号后面不要有空格,否则会失效 Name string `json:"name,omitempty"` Age int `json:"age,omitempty"` Address []string `json:"address,omitempty"` } 复制代码
修改 main 函数,将 adds 列表置空
adds := []string{} 复制代码
再次执行 main 函数,输出结果如下:
{Name:Tony Age:33 Address:[]} tony 变量的数据类型是:main.Human {"name":"Tony","age":33} tonyJson 变量的数据类型是:string 复制代码
可以看出在添加了 omitempty
后,空列表在序列化时被忽略,不再显示在 JSON 格式的数据中。
当结构体中的某些字段不想被序列化时,可以使用 json:"-"
,在序列化时不论内容是否为空,都会忽略
type Human struct { Name string `json:"name,omitempty"` Age int `json:"age,omitempty"` Address []string `json:"address,omitempty"` Password string `json:"-"` } 复制代码
main 函数中重新实例化一个结构体
tony := Human{"Tony", 33, adds, "12138"} 复制代码
再次执行 main 函数,输出结果如下:
{Name:Tony Age:33 Address:[NYC BOS] Password:12138} tony 变量的数据类型是:main.Human {"name":"Tony","age":33,"address":["NYC","BOS"]} tonyJson 变量的数据类型是:string 复制代码
序列化后的 JSON 数据中没有 Password 字段。
反序列化
JSON 反序列化也非常长江,在 Server 端可能收到来自 API、数据库或者配置文件中的 JSON 格式数据。
在 Go 中可以表示为字符串,encoding/json
标准库中的函数 Unmarshal
可以接收一个字节切片以及值,这个值就是 JSON 格式要封装的结构体的实例,由于结构体是值类型数据,所以这里一定要传递一个结构体指针。
func main(){ // 反序列化 tonyJson := "{"name":"Tony","age":33,"address":["NYC","BOS"]}" tonyByte := []byte(tonyJson) h := Human{} json.Unmarshal(tonyByte, &h) fmt.Printf("%+v\n", h) fmt.Printf("%T\n", h) } type Human struct { Name string `json:"name,omitempty"` Age int `json:"age,omitempty"` Address []string `json:"address,omitempty"` } 复制代码
执行上述代码,输出结果如下:
{Name:Tony Age:33 Address:[NYC BOS]} main.Human 复制代码
注意如果在反序列化时 Umarshal 函数的第二个参数是一个结构体而不是结构体指针,那么输出的结果将是一个空的结构体实例。
{Name: Age:0 Address:[]}