在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分之一左右。