Go中的JSON序列化与反序列化

Wednesday, September 13, 2023

JSON 是一种流行的数据交换格式,在Go中经常需要将结构体实例序列化为JSON字符串保存和传输。本文介绍使用Go中的encoding/json包将结构体序列化为JSON数据。

目录

  1. JSON序列化概述
  2. 定义可序列化的结构体
  3. 使用json.Marshal序列化
  4. 定制序列化逻辑
  5. 使用json.Unmarshal反序列化
  6. 处理空值和默认值
  7. 嵌套对象和匿名字段
  8. 序列化interface{}
  9. stream编码器
  10. JSON与XML对比
  11. 文本格式的优点
  12. 使用规范
  13. 序列化性能优化

JSON序列化概述

JSON是一种非常流行的结构化数据格式,被广泛用于网络传输和数据存储中。Go主要通过json包来处理JSON数据,有两个核心的函数:

  • json.Marshal 序列化Go对象为JOSN格式字节流
  • json.Unmarshal JSON数据解析为Go对象

定义可序列化的结构体

想要将一个结构体实例序列化为JSON,需要确保结构体字段可被json包访问,通常有两种方式:

  • 字段首字母答谢,可以被外部包访问
  • 通过tag指定字段名

比如:

type User struct {
	ID int `json:"id"`
	Name string `json:"name"`
	Age int
}

使用序列化

package main

import (
	"encoding/json"
	"fmt"
)

type User struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	user := User{"John", 20}

	// 序列化
	buf, err := json.Marshal(user)
	if err != nil {
		fmt.Println(err)
		return
	}

	// 打印JSON
	fmt.Println(string(buf))
}

输出:

{"name":"John","age":20}

定制序列化逻辑

若需要控制序列化的细节,可以实现Marshaler接口,这样可以灵活控制各字段编码过程。

package main

import (
	"encoding/json"
	"fmt"
)

type User struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

// 实现Marshaler接口
func (u User) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf(`{"name":"%s"}`, u.Name)), nil
}

func main() {
	u := User{"John", 20}

	jsonBytes, _ := json.Marshal(u)
	fmt.Println(string(jsonBytes)) // {"name":"John"}
}

使用反序列化

package main

import (
	"encoding/json"
	"fmt"
)

type User struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	jsonStr := `{"name":"John","age":20}`

	// 反序列化到user
	var user User
	err := json.Unmarshal([]byte(jsonStr), &user)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(user.Name) // John
	fmt.Println(user.Age)  // 20
}

传入JSON数据以及对象指针,即可解析。

处理空值和默认值

对于空值的序列化,默认会忽略空值字段:

type User struct {
  Name string
  Age  int
}

u := User{"John", 0} 

jsonBytes, _ := json.Marshal(u)

fmt.Println(string(jsonBytes)) // {"Name":"John"}

可以通过定义 MarshalJSON 方法自定义处理空值的编码。

反序列化时,需要处理不存在的字段,可以使用指针或默认值。

嵌套对象和匿名字段

对于嵌套的对象字段和内嵌匿名类型,也可以正常序列化和反序列化:

type Info struct {
  Addr string
}

type User struct {
  Info // 嵌套Info
  
  Name string
}

u := User{Info{"Beijing"}, "Tom"}

jsonBytes, _ := json.Marshal(u)

fmt.Println(string(jsonBytes)) // {"Addr":"Beijing","Name":"Tom"}

编码后嵌套的类型将被内联到结果中。

序列化空接口

json包可以处理任意的 interface{} 值:

data := map[string]interface{}{
  "name": "John",
  "age": 20,
}

jsonBytes, _ := json.Marshal(data)

fmt.Println(string(jsonBytes))

但编码结果会包含类型信息和Base64编码后的值,这比定制序列化更低效。

stream编码器

也可以使用streaming模式的encoder来进行序列化:

package main

import (
  "encoding/json"
  "os"
)

func main() {
  type User struct {
    Name string
    Age  int
  }
  
  user := User{"John", 20}
  
  // 使用stream encoder
  enc := json.NewEncoder(os.Stdout)
  enc.Encode(user)
}

这种编码方式很灵活,可以实现各种自定义输出格式。

JSON与XML对比

JSON对比XML优点:

  • 读取和解析更加方便快捷
  • 存储占用更小且具有可读性
  • 支持更多语言与平台
  • 性能和解析速度更快

文本格式的优点

JSON与其他二进制序列化格式相比,文本格式JSON优点:

  • 可读性强,便于打印调试
  • 可复用现有文本处理工具
  • 不依赖语言和平台
  • 可支持部分更新修改

使用规范

  • 格式化打印 JSON 数据
  • 为类型和字段定义 Canonical 名称
  • 提供完整的文档
  • 版本控制 JSON 接口
  • 采用更灵活的 schema 描述

序列化性能优化

  • 提前指定好对象大小
  • 重用分配的内存
  • 尽量避免解析嵌套数据
  • 按需完成自定义编码
Golang修炼

Go编程陷阱

Go语言Mutex互斥锁

comments powered by Disqus