JSON 是一种轻量级的数据交换格式,常用作前后端数据交换,Go 在 encoding/json 包中提供了对 JSON 的支持。
在线支持JSON转换Go的网站 https://mholt.github.io/json-to-go/
序列化
序列化在计算机科学中通常有以下定义:
- 对同步控制而言,表示强制在同一时间内进行单一访问。
- 在数据储存与发送的部分是指将一个对象存储至一个存储介质,例如文件或是存储器缓冲等,或者透过网络发送数据时进行编码的过程,可以是字节或是JSON等格式。而字节的或JSON编码格式可以还原完全相等的对象。这程序被应用在不同应用程序之间发送对象,以及服务器将对象存储到文件或数据库。相反的过程又称为反序列化。
把 Go struct 序列化成 JSON 对象,Go 提供了 Marshal 方法,正如其含义所示表示编排序列化,函数签名如下:
func Marshal(v interface{}) ([]byte, error)
举例来说,比如下面的 Go struct:
type Message struct {Name stringBody stringTime int64}
使用 Marshal 序列化:
m := Message{"Alice", "Hello", 1294706395881547000}b, err := json.Marshal(m)fmt.Println(b) //{"Name":"Alice","Body":"Hello","Time":1294706395881547000}
序列化类型
在 Go 中并不是所有的类型都能进行序列化:
JSON object key 只支持 string,如果要对一个 Go map 类型进行编码,map 必须是 map[string]T(T是 json 包中支持的任何类型)
- Channel、复杂(complex)、函数(function) 等 type 无法进行序列化
- 数据中如果存在循环引用,则不能进行序列化,因为序列化时会进行递归
- 指针(Pointer) 序列化之后是其指向的值或者是 nil
JSON类型对应关系
| Go 类型 | json类型 |
|---|---|
| bool | booleans |
| float64 | numbers |
| string | strings |
| nil | null |
反序列化
反序列化函数是 Unmarshal ,其函数签名如下:
func Unmarshal(data []byte, v interface{}) error
如果要进行反序列化,我们首先需要创建一个可以接受序列化数据的 Go struct:
var m Messageerr := json.Unmarshal(b, &m)
JSON 对象一般都是小写表示,Marshal 之后 JSON 对象的首字母依然是大写,如果序列化之后名称想要改变如何实现,答案就是 struct tags。
Struct Tag
Struct tag 可以决定 Marshal 和 Unmarshal 函数如何序列化和反序列化数据。
指定 JSON filed name
JSON object 中的 name 一般都是小写,我们可以通过 struct tag 来实现:
type MyStruct struct {SomeField string `json:"some_field"`}
SomeField 序列化之后会变成 some_field。
package mainimport ("encoding/json""fmt")type StuGender intconst (MALE = 1 << iotaFEMALE)type student struct {Id int64 `json:"id"`Name string `json:"name"`Gender StuGender `json:"gender"`Exams []exam `json:"exams"`}type exam struct {Lesson string `json:"lesson"`Score int `json:"score"`}func main() {ming := student{Id: 101, Name: "小明", Gender: MALE, Exams: []exam{{Lesson: "Chinese", Score: 98}, {Lesson: "Math", Score: 100}}}data, _ := json.Marshal(ming)var s studentif err := json.Unmarshal(data, &s); err == nil {fmt.Println(s)}}
结果输出
{101 小明 1 [{Chinese 98} {Math 100}]}
指定 field 是 empty 时的行为
使用 omitempty 可以告诉 Marshal 函数如果 field 的值是对应类型的 zero-value,那么序列化之后的 JSON object 中不包含此 field:
type MyStruct struct {SomeField string `json:"some_field,omitempty"`}m := MyStruct{}b, err := json.Marshal(m) //{}
如果 SomeField == “” ,序列化之后的对象就是 {}。
跳过 field
Struct tag “-” 表示跳过指定的 filed:
type MyStruct struct {SomeField string `json:"some_field"`Passwd string `json:"-"`}m := MyStruct{}b, err := json.Marshal(m) //{"some_feild":""}
即序列化的时候不输出,这样可以有效保护需要保护的字段不被序列化。
反序列化任意 JSON 数据
在序列化之前如果不知道 JSON 数据格式,我们使用 interface{} 来存储。encoding/json 包使用 map[string]interface{} 和 []interface{} 储存任意的 json 对象和数组;其可以被反序列化为任何的 json blob 存储到接口值中。直接使用 Unmarshal 把这个数据反序列化,并保存在map[string]interface{} 中,要访问这个数据,我们可以使用类型断言。反序列化任意json数据:
package mainimport ("encoding/json""fmt")type StuGender intconst (MALE = 1 << iotaFEMALE)type student struct {Id int64 `json:"id"`Name string `json:"name"`Gender StuGender `json:"gender"`Exams []exam `json:"exams"`}type exam struct {Lesson string `json:"lesson"`Score int `json:"score"`}func main() {ming := student{Id: 101, Name: "小明", Gender: MALE, Exams: []exam{{Lesson: "Chinese", Score: 98}, {Lesson: "Math", Score: 100}}}data, _ := json.Marshal(ming)var m map[string]interface{}if err := json.Unmarshal(data, &m); err == nil {fmt.Println(m)}for k,v :=range m{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, "未知数据类型")}}}
switch-type中,根据 json和Go数据类型对照表。比如Score是float64而不是int类型。
反序列化对 slice、map、pointer 的处理
我们定义一个 struct 继续对上面例子中的 b 进行反序列化:
type FamilyMember struct {Name stringAge intParents []string}var m FamilyMembererr := json.Unmarshal(b, &m)
这个例子是能够正常工作的,你一定也注意到了,struct 中包含一个 slice Parents ,slice 默认是 nil,之所以反序列化可以正常进行就是因为 Unmarshal 在序列化时进行了对 slice Parents 做了初始化,同理,对 map 和 pointer 都会做类似的工作,比如序列化如果 Pointer 不是 nil 首先进行 dereference 获得其指向的值,然后再进行序列化,反序列化时首先对 nil pointer 进行初始化
Stream JSON
除了 marshal 和 unmarshal 函数,Go 还提供了 Decoder 和 Encoder 对 stream JSON 进行处理,常见 request 中的 Body、文件等:
jsonFile, err := os.Open("post.json")if err != nil {fmt.Println("Error opening json file:", err)return}defer jsonFile.Close()decoder := json.NewDecoder(jsonFile)for {var post Posterr := decoder.Decode(&post)if err == io.EOF {break}if err != nil {fmt.Println("error decoding json:", err)return}fmt.Println(post)}
// json.gopackage mainimport ("encoding/json""fmt""net/http")type User struct {Firstname string `json:"firstname"`Lastname string `json:"lastname"`Age int `json:"age"`}func main() {http.HandleFunc("/decode", func(w http.ResponseWriter, r *http.Request) {var user Userjson.NewDecoder(r.Body).Decode(&user)fmt.Fprintf(w, "%s %s is %d years old!", user.Firstname, user.Lastname, user.Age)})http.HandleFunc("/encode", func(w http.ResponseWriter, r *http.Request) {peter := User{Firstname: "John",Lastname: "Doe",Age: 25,}json.NewEncoder(w).Encode(peter)})http.ListenAndServe(":8080", nil)}
分别请求api
$ go run json.go$ curl -s -XPOST -d'{"firstname":"Elon","lastname":"Musk","age":48}' http://localhost:8080/decodeElon Musk is 48 years old!$ curl -s http://localhost:8080/encode{"firstname":"John","lastname":"Doe","age":25}
NewDecoder 和 NewEncoder 函数分别封装了 io.Reader 和 io.Writer 接口。
func NewDecoder(r io.Reader) *Decoderfunc NewEncoder(w io.Writer) *Encoder
把 json 直接写入文件,可以使用 json.NewEncoder 初始化文件(或者任何实现 io.Writer 的类型),并调用 Encode();从文件中读取json可以使用 json.Decoder 和 Decode() 函数:
package mainimport ("encoding/json""fmt""os")type StuGender intconst (MALE = 1 << iotaFEMALE)type Student struct {Id int64 `json:"id"`Name string `json:"name"`Gender StuGender `json:"gender"`Exams []exam `json:"exams"`}type exam struct {Lesson string `json:"lesson"`Score int `json:"score"`}func main() {stu := Student{Id: 101, Name: "小明", Gender: MALE, Exams: []exam{{Lesson: "Chinese", Score: 98}, {Lesson: "Math", Score: 100}}}// 存储文件f, err := os.Create("./student.json")if err != nil {fmt.Println(err)}if err = json.NewEncoder(f).Encode(stu);err!=nil{fmt.Println(err)}// 读取文件file, err := os.Open("./student.json")if err != nil {fmt.Println(err)}defer file.Close()var s Studentif err = json.NewDecoder(file).Decode(&s);err==nil{fmt.Println(s)}else {fmt.Println(err)}}
输出结果
{101 小明 1 [{Chinese 98} {Math 100}]}
嵌入式 struct 的序列化
Go 支持对 nested struct 进行序列化和反序列化:
type App struct {Id string `json:"id"`}type Org struct {Name string `json:"name"`}type AppWithOrg struct {AppOrg}func main() {data := []byte(`{"id": "k34rAT4","name": "My Awesome Org"}`)var b AppWithOrgjson.Unmarshal(data, &b)fmt.Printf("%#v", b)a := AppWithOrg{App: App{Id: "k34rAT4",},Org: Org{Name: "My Awesome Org",},}data, _ = json.Marshal(a)fmt.Println(string(data))}
Nested struct 虽然看起来有点怪异,有些时候它将非常有用。
自定义序列化函数
Go JSON package 中定了两个 Interface Marshaler 和 Unmarshaler ,实现这两个 Interface 可以让你定义的 type 支持序列化操作。
func NewDecoder(r io.Reader) *Decoderfunc NewEncoder(w io.Writer) *Encoder
