【Golang】Go 语言 JSON 的序列化与反序列化实践

Go 语言 JSON 的序列化与反序列化实践


导读

本文使用 Go 原生支持的包,对 JSON 字符串以及 .json 文件进行序列化与反序列化实践。

  • 使用到的包是 encoding/json。详细文档可查看官方中文文档,链接为: https://studygolang.com/pkgdoc

  • 本文中所有代码块的 Go 代码均经过运行测试,可以直接复制运行。

  • 请留意代码块中的注释,注释中有表示出一些非常重要的细节!



一. 简述序列化与反序列化

序列化(Serialization),有的人也称之为反解析。指的是将对象的状态信息转换为可以存储或传输的形式的过程

反序列化,有的人成为解析。指的就是序列化的逆过程。

图1. 序列化与反序列化

例如,定义如下结构体 book,我们可以创建一个实体对象,bookObj,此时 bookObj 是一个在程序中的实体对象。若我要存储这个对象,或者将其通过 HTTP 进行传输,我就需要将其进行序列化为可以进行存储或者传输的文件形式,例如 json。

package main

import (
	"fmt"
	"time"
)

type Book struct {
	Name 		string
	Author		string
	PublishTime	time.Time
}

func main() {
	bookObj := &Book{
		Name:        "Go 语言基础入门",
		Author:      "fxtack",
		PublishTime: time.Now(),
	}
	fmt.Println(bookObj)
}

若进行序列化,序列化后的 bookObj 对象的 JSON 格式为(注意,结构体字段名与 JSON 中的字段名并不是完全相等的):

{
	"name": "Go 语言基础入门",
	"author": "fxtack",
	"publishTime": "2021-07-03T18:23:57.7451495+08:00"
}

这样我就可以通过存储和传输 json 来实现对 bookObj 对象的存储和传输了。

相应的,如果需要这个对象而读取 json 文件,或者传输的接收者收到这个对象的 json 并打算使用这个对象时,就需要通过反序列化,将其从 json 的格式重新转化为程序中的对象。


二. JSON 的序列化

1. 序列化简单示例

先写一个简单测试,示例如何使用 Go 进行序列化。代码如下:

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type Book struct {
	Name 		string
	Author		string
	PublishTime	time.Time
}

func main() {
	bookObj := &Book{
		Name:        "Go 语言基础入门",
		Author:      "fxtack",
		PublishTime: time.Now(),
	}
	// 使用 encoding/json 包进行序列化
	// json.Marshal 序列化函数可以接受对象实例也可以接受对象指针
	// 返回 byte[] 与 error,使用 error 可以对序列化是否成功进行判断,此处省略
	jsonBytes, _ := json.Marshal(bookObj)
	// 以字符串形式将序列化结果输出
	fmt.Println(string(jsonBytes))
}

结果如下,已经符合 JSON 格式,序列化成功。

{"Name":"Go 语言基础入门","Author":"fxtack","PublishTime":"2021-07-03T19:00:04.1716455+08:00"}

注意


结构体中每个字段首字母必须是大写,这样各个字段才是可被访问的,也才能顺利完成序列化。下文中的反序列化也同理。

我们可以注意到,虽然输出的字符串已经符合 JSON 的语法要求,但其格式还不够规范。Go 中还有一个序列化函数为 json.MarshalIndent 可以输出更好的格式。使用如下:

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type Book struct {
	Name 		string
	Author		string
	PublishTime	time.Time
}

func main() {
	bookObj := &Book{
		Name:        "Go 语言基础入门",
		Author:      "fxtack",
		PublishTime: time.Now(),
	}
	
	// 注意 json.MarshalIndent 的参数多了两个
	jsonBytes, _ := json.MarshalIndent(bookObj, "", "\t")
	fmt.Println(string(jsonBytes))
}

此时输出为规范的 JSON 内容,如下:

{
	"Name": "Go 语言基础入门",
	"Author": "fxtack",
	"PublishTime": "2021-07-03T19:07:19.8107909+08:00"
}

此时我们可以看到 JSON 内容中的字段与结构体中的字段名是完全相同的。在本文第一章中展示的 JSON 内容则是首字母小写的驼峰命名法。

若要将结构体字段映射到新的名称,可以在结构体中使用 json 标签来实现。

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type Book struct {
	// 注意,每个字段都使用了对 json 标签,标签中的内容将会映射为 JSON 的字段名
	Name 		string		`json:"name"`
	Author		string		`json:"author"`
	PublishTime	time.Time	`json:"publishTime"`
}

func main() {
	bookObj := &Book{
		Name:        "Go 语言基础入门",
		Author:      "fxtack",
		PublishTime: time.Now(),
	}
	jsonBytes, _ := json.MarshalIndent(bookObj, "", "\t")
	fmt.Println(string(jsonBytes))
}

结果如下:

{
	"name": "Go 语言基础入门",
	"author": "fxtack",
	"publishTime": "2021-07-04T15:18:45.9907985+08:00"
}

如果需要,可以通过 json 标签映射为任意需要的名称。

2. 序列化完整代码

接下来考虑完整的过程,将对象序列化为 .json 文件,其流程如下:

构建序列化对象的结构体
由结构体创建对象
序列化得到 JSON 格式字符串
写入 .json 文件

代码如下:

package main

import (
	"encoding/json"
	"log"
	"os"
	"time"
)

type Book struct {
	// 使用 json 标签
	Name 		string		`json:"name"`
	Author		string		`json:"author"`
	PublishTime	time.Time	`json:"publishTime"`
}

func main() {
	bookObj := &Book{
		Name:        "Go 语言基础入门",
		Author:      "fxtack",
		PublishTime: time.Now(),
	}
	
	// 调用序列化函数,序列化结果存储到 ./book.json
	WriteJson("./book.json", bookObj)
}

// 序列化为 .json 文件函数
// filePath 为存储到的路径,ref 为需要序列化的对象或其应用
func WriteJson(filePath string, ref interface{}) {

	// 判断提供的路径文件是否存在,避免直接覆盖已有的文件
	f, err := os.Stat(filePath)
	if (err == nil || os.IsExist(err)) && !f.IsDir(){
		log.Println("文件已经存在或存在同名文件夹")
		return
	}
	
	// 创建 .json 文件
	file, err := os.Create(filePath)
	if err != nil {log.Println("文件创建失败")}
	defer file.Close()
	
	// 序列化
	json, err := json.MarshalIndent(ref, "", "\t")
	if err != nil {
		log.Println("序列化失败")
	}
	
	// 写入 .json 文件
	file.Write(json)
}

运行后会在 Go 模块下多出一个 book.json 的文件,说明序列化成功。


二. JSON 的反序列化

1. 反序列化简单示例

先写一个简单测试,演示 Go 是如何完成 JSON 的反序列化。
这里用我们在上文中序列化得到的 JSON 字符串来做演示。

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type Book struct {
	// 注意,使用了 json 标签,则 JSON 内容的字段名必须与 json 标签的值一致才能被解析
	Name 		string		`json:"name"`
	Author		string		`json:"author"`
	PublishTime	time.Time	`json:"publishTime"`
}

func main() {

	// 序列化示例中得到的 JSON 字符串
	jsonStr := "{\"name\":\"Go 语言基础入门\",\"author\":\"fxtack\",\"publishTime\":" +
		"\"2021-07-03T19:00:04.1716455+08:00\"}"

	// 声明一个空的 book 来接受结果
	var bookObj Book
	
	// 注意!反序列化函数 json.Unmarshal 的参数一个是 JSON 字符串的字节切片
	// 一个是用于接受反序列化结果的对象引用。或者说传入的对象必须是可寻址的
	json.Unmarshal([]byte(jsonStr), &bookObj)
	fmt.Printf("书名为 《%s》, 作者是 %s, 发布时间为 %s",bookObj.Name, bookObj.Author, bookObj.PublishTime)
}

运行结果如下。

书名为 《Go 语言基础入门》, 作者是 fxtack, 发布时间为 2021-07-03 19:00:04.1716455 +0800 CST

结果输出是通过 bookObj 对象引用其属性来完成的,其字段不是空值,可见序列化完成。

注意


  • 如果存在无法匹配到结构体字段的 JSON 字段,Go 反序列化时会无视这些字段。
  • 如果结构体字段在 JSON 中没有字段与之匹配,Go 反序列化时也会无视这些字段,其值为结构体字段类型的默认值。

2. 反序列化完整代码

接下来考虑完整的过程,将上文中序列化的 .json 文件进行读取,实现反序列化,其流程如下:

读取 .json 文件得到 JSON 字符串
创建可寻址的结构体对象实例
反序列化并由对象实例接收结果
使用对象实例

代码如下:

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"time"
)

type Book struct {
	Name 		string		`json:"name"`
	Author		string		`json:"author"`
	PublishTime	time.Time	`json:"publishTime"`
}

func main() {
	var bookObj Book

	// 调用反序列化函数对上文中生成的 ./book.json 文件进行读取与反序列化
	ReadJson("./book.json", &bookObj)
	fmt.Printf("书名为 《%s》, 作者是 %s, 发布时间为 %s",bookObj.Name, bookObj.Author, bookObj.PublishTime)
}

func ReadJson(filePath string, ref interface{}) {

	// 检查文件是否不存在,或者存在的是同名文件夹
	f, err := os.Stat(filePath)
	if err != nil && os.IsNotExist(err) || f.IsDir(){
		log.Println("文件不存在")
		return
	}
	
	// 读取文件内容
	data, err := ioutil.ReadFile(filePath)
	if err != nil {
		log.Println("io 错误")
		return
	}
	
	// 反序列化
	err = json.Unmarshal(data, ref)
	if err != nil {
		log.Println("反序列化失败")
		return
	}
}

其结果与测试示例结果一致,则反序列化成功。


三. 序列化与反序列化中的多种类型

上文的例子中,我们仅涉及到了两种类型 string 类型和 time.Time。接下来通过一个例子,演示多种类型:整型、浮点数、布尔值、数组(或切片)、自定义结构体,在序列化与反序列化中的转换情况。

我们先从如下 library.json 文件考虑如何反序列化。

{
  "name": "Shanghai library",
  "location" : "Shanghai",
  "isPrivate": false,
  "score": 4.6,
  "date": {
    "open": "0001-01-01T08:15:00.000000+08:00",
    "close" : "0001-01-01T18:00:00.000000+08:00"
  },
  "bookInfo" : {
    "number": 1000,
    "bookList": [
      {
        "name": "Java programing",
        "author": "author1",
        "publishTime": "2020-07-31T14:27:10.035542+08:00"
      },{
        "name": "Go programing",
        "author": "author2",
        "publishTime" : "2020-07-31T14:27:10.035542+08:00"
      },{
        "name": "Go network programing",
        "author": "author2",
        "publishTime" : "2020-07-31T14:27:10.035542+08:00"
      }
    ]
  }
}

JSON 字段与其对应的 Go 类型情况如下表:

字段名对应 Go 类型字段名对应 Go 类型字段名对应 Go 类型字段名对应 Go 类型
namestring
locationstring
isPrivatebool
scorefloat64
dataData(自定义结构体)opentime.Time
closetime.Time
bookInfoBookInfo(自定义结构体)numberint
bookList[]Book(自定义结构体数组切片)-Book(自定义结构体)namestring
authorstring
publishTimestring
-Book(自定义结构体)namestring
authorstring
publishTimestring
-Book(自定义结构体)namestring
authorstring
publishTimestring

可见其中囊括了大部分常用的类型与情况。接下来为了能够反序列化该 JSON 文件。要在Go 中用结构体对其进行建模,并读取文件进行反序列化。

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"time"
)

// 整个 JSON 文件对应的结构体
type Library struct {
	Name		string		`json:"name"`
	Location 	string		`json:"location"`
	IsPrivate	bool		`json:"isPrivate"`
	Score		float64		`json:"score"`
	Date		Date		`json:"date"`
	BookInfo	BookInfo	`json:"bookInfo"`
}

// date 字段对应的结构体
type Date struct {
	Open	time.Time		`json:"open"`
	Close 	time.Time		`json:"close"`
}

// bookInfo 字段对应的结构体
type BookInfo struct {
	Number 		int			`json:"number"`
	BookList	[]Book		`json:"bookList"`
}

// book 字段对应的结构体
type Book struct {
	Name 		string		`json:"name"`
	Author		string		`json:"author"`
	PublishTime	time.Time	`json:"publishTime"`
}

func main() {
	var library Library
	ReadJson("./library.json", &library)
	fmt.Println(library)
	library.BookInfo.BookList[1].PublishTime = time.Now()
	WriteJson("./library_change_book.json", &library)
}

// 读入 JSON 文件,实现反序列化
func ReadJson(filePath string, ref interface{}) {
	f, err := os.Stat(filePath)
	if err != nil && os.IsNotExist(err) || f.IsDir(){
		log.Println("文件不存在")
		return
	}
	data, err := ioutil.ReadFile(filePath)
	if err != nil {
		log.Println("io 错误")
		return
	}
	err = json.Unmarshal(data, ref)
	if err != nil {
		log.Println("反序列化失败")
		return
	}
}

// 输出 JSON 文件,实现序列化
func WriteJson(filePath string, ref interface{}) {
	f, err := os.Stat(filePath)
	if (err == nil || os.IsExist(err)) && !f.IsDir(){
		log.Println("文件已经存在或存在同名文件夹")
		return
	}
	file, err := os.Create(filePath)
	if err != nil {log.Println("文件创建失败")}
	defer file.Close()
	json, err := json.MarshalIndent(ref, "", "\t")
	if err != nil {
		log.Println("序列化失败")
	}
	file.Write(json)
}

上述代码已经给出了反序列化函数和序列化函数,并读取 ./library.json 反序列化得到 library 对象并输出。结果为:

{Shanghai library Shanghai false 4.6 {0001-01-01 08:15:00 +0800 CST 0001-01-01 18:00:00 +0800 CST} {1000 [{Java programing author1 2020-07-31 14:27:10.035542 +0800 CST} {Go programing author2 2020-07-31 14:27:10.035542 +0800 CST} {Go network programing author2 2020-07-31 14:27:10.035542 +0800 CST}]}}

再修改图书馆的第二本书的发布日期为当天日期,并序列化为新的文件./library_change_book.json

执行完成,序列化与反序列化都成功了。


后续

本文对 JSON 的序列化与反序列化进行了探讨与实践。
后续将会继续更新 Go 对 xml 的序列化与反序列化实践教程。
欢迎关注,将会持续更新。


其他相关文章

文章名称更新时间
Go 语言 JSON 的序列化与反序列化实践2021-7-30
Go 语言 XML 的序列化与反序列化实践2021-9-27

文章内容来自个人学习总结
欢迎指出本文中存在的问题
未经本人同意禁止转载,不得用于商业用途
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值