38.2 JSON数据格式

在Go语言中,利用encoding/json标准包将数据序列化为JSON数据格式这个过程简单直接,直接使用json.Marshal(v)来处理任意类型,序列化成功后得到一个字节数组。

反过来我们将一个JSON数据来反序列化或解码,则就不那么容易了,下面我们一一来说明。

(一)将JSON数据反序列化到结构体:

这种需求是最常见的,在我们知道 JSON 的数据结构前提情况下,我们完全可以定义一个或几个适当的结构体并对 JSON 数据反序列化。例如:

package main
import (
	"encoding/json"
	"fmt"
)
type Human struct {
	name   string `json:"name"` // 姓名
	Gender  string `json:"s"`    // 性别,性别的tag表明在json中为s字段
	Age    int    `json:"Age"`  // 年龄
	Lesson
}
type Lesson struct {
	Lessons []string `json:"lessons"`
}
func main() {
	jsonStr := `{"Age": 18,"name": "Jim" ,"s": "男",
	"lessons":["English","History"],"Room":201,"n":null,"b":false}`
	var hu Human
	if err := json.Unmarshal([]byte(jsonStr), &hu); err == nil {
		fmt.Println("\n结构体Human")
		fmt.Println(hu)
	}
	var le Lesson
	if err := json.Unmarshal([]byte(jsonStr), &le); err == nil {
		fmt.Println("\n结构体Lesson")
		fmt.Println(le)
	}
	jsonStr = `["English","History"]`
	var str []string
	if err := json.Unmarshal([]byte(jsonStr), &str); err == nil {
		fmt.Println("\n字符串数组")
		fmt.Println(str)
	} else {
		fmt.Println(err)
	}
}
程序输出:
结构体Human
{ 男 18 {[English History]}}
结构体Lesson
{[English History]}
字符串数组
[English History 

我们定义了2个结构体Human和Lesson,结构体Human的Gender字段tag标签为:json:"s",表明这个字段在JSON中的名字对应为s。而且结构体Human中嵌入了Lesson结构体。

jsonStr 我们可以认作为一个JSON数据,通过json.Unmarshal,我们可以把JSON中的数据反序列化到了对应结构体,由于结构体Human的name字段不能导出,所以并不能实际得到JSON中的值,这是我们在定义结构体时需要注意的,字段首字母大写。

对JSON中的Age,在结构体Human对应Age int,不能是string。另外,如果是JSON数组,可以把数据反序列化给一个字符串数组。

总之,知道JSON的数据结构很关键,有了这个前提做反序列化就容易多了。而且结构体的字段并不需要和JSON中所有数据都一一对应,定义的结构体字段可以是JSON中的一部分。

(二)反序列化任意JSON数据:

encoding/json 包使用 map[string]interface{} 和 []interface{} 储存任意的 JSON 对象和数组;其可以被反序列化为任何的 JSON blob 存储到接口值中。

直接使用 Unmarshal 把这个数据反序列化,并保存在map[string]interface{} 中,要访问这个数据,我们可以使用类型断言:

package main
import (
	"encoding/json"
	"fmt"
)
func main() {
	jsonStr := `{"Age": 18,"name": "Jim" ,"s": "男","Lessons":["English","History"],"Room":201,"n":null,"b":false}`
	var data map[string]interface{}
	if err := json.Unmarshal([]byte(jsonStr), &data); err == nil {
		fmt.Println("map结构")
		fmt.Println(data)
	}
	for k, v := range data {
		switch vv := v.(type) {
		case string:
			fmt.Println(k, "是string", vv)
		case bool:
			fmt.Println(k, "是bool", vv)
		case float64:
			fmt.Println(k, "是float64", vv)
		case nil:
			fmt.Println(k, "是nil", vv)
		case []interface{}:
			fmt.Println(k, "是array:")
			for i, u := range vv {
				fmt.Println(i, u)
			}
		default:
			fmt.Println(k, "未知数据类型")
		}
	}
}
程序输出:
map结构
map[n:<nil> b:false Age:18 name:Jim s:男 Lessons:[English History] Room:201]
name 是string Jim
s 是string 男
Lessons 是array:
0 English
1 History
Room 是float64 201
n 是nil <nil>
b 是bool false
Age 是float64 18

通过这种方式,即使是未知 JSON 数据结构,我们也可以反序列化,同时可以确保类型安全。在switch-type中,我们可以根据表16-3 JSON与Go数据类型对照表来做选择。比如Age是float64而不是int类型,另外JSON的booleans、null类型在JSON也常常出现,在这里都做了case。

(三)JSON数据编码和解码:

JSON 包提供 Decoder 和 Encoder 类型来支持常用 JSON 数据流读写。NewDecoder 和 NewEncoder 函数分别封装了 io.Reader 和 io.Writer 接口。

func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder

如果要想把 JSON 直接写入文件,可以使用 json.NewEncoder 初始化文件(或者任何实现 io.Writer 的类型),并调用 Encode();反过来与其对应的是使用 json.Decoder 和 Decode() 函数:

func NewDecoder(r io.Reader) *Decoder
func (dec *Decoder) Decode(v interface{}) error

由于 Go 语言中很多标准包都实现了 Reader 和 Writer接口,因此 Encoder 和 Decoder 使用起来非常方便。

例如,下面例子使用 Decode方法解码一段JSON格式数据,同时使用Encode方法将我们的结构体数据保存到文件t.json中:

package main
import (
	"encoding/json"
	"fmt"
	"os"
	"strings"
)
type Human struct {
	name   string `json:"name"` // 姓名
	Gender string `json:"s"`    // 性别,性别的tag表明在json中为s字段
	Age    int    `json:"Age"`  // 年龄
	Lesson
}
type Lesson struct {
	Lessons []string `json:"lessons"`
}
func main() {
	// json数据的字符串
	jsonStr := `{"Age": 18,"name": "Jim" ,"s": "男",
	"lessons":["English","History"],"Room":201,"n":null,"b":false}`
	strR := strings.NewReader(jsonStr)
	h := &Human{}
	// Decode 解码json数据到结构体Human中
	err := json.NewDecoder(strR).Decode(h)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(h)
	// 定义Encode需要的Writer
	f, err := os.Create("./t.json")
	// 把保存数据的Human结构体对象编码为json保存到文件
	json.NewEncoder(f).Encode(h)
}
程序输出:
&{ 男 18 {[English History]}}

我们调用json.NewDecoder 函数构造了 Decoder 对象,使用这个对象的 Decode方法解码给定义好的结构体对象h。对于字符串,使用 strings.NewReader 方法,让字符串变成一个 Reader。

类似解码过程,我们通过json.NewEncoder()函数来构造Encoder对象,由于os中文件操作已经实现了Writer接口,所以可以直接使用,把h结构体对象编码为JSON数据格式保存在文件t.json中。

文件t.json中内容为:{"s":"男","Age":18,"lessons":["English","History"]}

(四)JSON数据延迟解析

Human.Name字段,由于可以等到使用的时候,再根据具体数据类型来解析,因此我们可以延迟解析。当结构体Human的Name字段的类型设置为 json.RawMessage 时,它将在解码后继续以 byte 数组方式存在。

package main
import (
	"encoding/json"
	"fmt"
)
type Human struct {
	Name   json.RawMessage `json:"name"` // 姓名,json.RawMessage 类型不会进行解码
	Gender string          `json:"s"`    // 性别,性别的tag表明在json中为s字段
	Age    int             `json:"Age"`  // 年龄
	Lesson
}
type Lesson struct {
	Lessons []string `json:"lessons"`
}
func main() {
	jsonStr := `{"Age": 18,"name": "Jim" ,"s": "男",
	"lessons":["English","History"],"Room":201,"n":null,"b":false}`
	var hu Human
	if err := json.Unmarshal([]byte(jsonStr), &hu); err == nil {
		fmt.Printf("\n 结构体Human \n")
		fmt.Printf("%+v \n", hu) // 可以看到Name字段未解码,还是字节数组
	}
	// 对延迟解码的Human.Name进行反序列化
	var UName string
	if err := json.Unmarshal(hu.Name, &UName); err == nil {
		fmt.Printf("\n Human.Name: %s \n", UName)
	}
}
程序输出:
 结构体Human 
{Name:[34 74 105 109 34] Gender:男 Age:18 Lesson:{Lessons:[English History]}} 
 Human.Name: Jim 

在对JSON数据第一次解码后,保存在Human的hu.Name的值还是二进制数组,在后面对hu.Name进行解码后才真正发序列化为string类型的真实字符串对象。

除了Go标准库外,还有很多的第三方库也能较好解析JSON数据。这里我推荐一个第三方库:https://github.com/buger/jsonparser

如同 encoding/json 包一样,在Go语言中XML也有 Marshal() 和 UnMarshal() 从 XML 中编码和解码数据;也可以从文件中读取和写入(或者任何实现了 io.Reader 和 io.Writer 接口的类型)。和 JSON 的方式一样,XML 数据可以序列化为结构,或者从结构反序列化为 XML 数据。

下一节:Protocol Buffer 简单称为protobuf(Pb),是Google开发出来的一个语言无关、平台无关的数据序列化工具,在rpc或tcp通信等很多场景都可以使用。在服务端定义一个数据结构,通过protobuf转化为字节流,再传送到客户端解码,就可以得到对应的数据结构。它的通信效率极高,同一条消息数据,用protobuf序列化后的大小是JSON的10分之一左右。