共计 12918 个字符,预计需要花费 33 分钟才能阅读完成。
Go 的“玩家”们看到这个题目可能会很纳闷——对于 JSON 而言,Go 原生库 encoding/json
曾经是提供了足够舒服的 JSON 解决工具,广受 Go 开发者的好评。它还能有什么问题?然而,实际上在业务开发过程中,咱们遇到了不少原生 json
做不好甚至是做不到的问题,还真是不能齐全满足咱们的要求。
那么,它有什么问题吗?什么状况下应用第三方库?如何选型?性能如何?
不过呢,在抛出具体问题之前,咱们先来尽可能简略地理解一下 Go 目前在解决 JSON 中罕用的一些库,以及对这些库的测试数据分析。如果读者感觉上面的文字太长了,也能够间接跳到论断局部。
局部罕用的 Go JSON 解析库
Go 原生 encoding/json
这应该是宽广 Go 程序员最相熟的库了,应用 json.Unmarshal
和 json.Marshal
函数,能够轻松将 JSON 格局的二进制数据反序列化到指定的 Go 构造体中,以及将 Go 构造体序列化为二进制流。而对于未知构造或不确定构造的数据,则反对将二进制反序列化到 map[string]interface{}
类型中,应用 KV 的模式进行数据的存取。
这里我提两个大家可能不会留意到的额定个性:
- json 包解析的是一个 JSON 数据,而 JSON 数据既能够是对象(object),也能够是数组(array),同时也能够是字符串(string)、数值(number)、布尔值(boolean)以及空值(null)。而上述的两个函数,其实也是反对对这些类型值的解析的。比方上面段代码也是能用的
var s string
err := json.Unmarshal([]byte(`"Hello, world!"`), &s)
// 留神字符串中的双引号不能缺,如果仅仅是 `Hello, world`,则这不是一个非法的 JSON 序列,会返回谬误。
- json 在解析时,如果遇到大小写问题,会尽可能地进行大小写转换。即使是一个 key 与构造体中的定义不同,但如果疏忽大小写后是雷同的,那么仍然可能为字段赋值。比方上面的例子能够阐明:
cert := struct {
Username string `json:"username"`
Password string `json:"password"`
}{}
err := json.Unmarshal([]byte(`{"UserName":"root","passWord":"123456"}`), &cert)
if err != nil {fmt.Println("err =", err)
} else {fmt.Println("username =", cert.Username)
fmt.Println("password =", cert.Password)
}
// 理论输入:
// username = root
// password = 123456
jsoniter
关上 jsoniter 的 GitHub 主页,它一上来就鼓吹两个关键词:high-performance 以及 compatible。这也是这个包最大的两个卖点。
首先说兼容:jsoniter
最大的劣势就在于:可能 100% 兼容规范库,因而代码可能十分不便地进行迁徙。切实是不不便的。也能够用 Go Monkey 强行换掉 json 的相干函数入口。
接着看性能:与其余吹牛本人性能的开源库一样,它们本人的测试论断都不能无脑采信。这里我依据我集体的测试状况,先抛几个简略的论断吧:
- 在反序列化构造体这一繁多场景下,jsoniter 相比规范库的确有晋升,我本人测得的后果大概是晋升 1.4 倍左右
- 但同样是反序列化构造体这一繁多场景,jsoniter 远远不如 easyjson
- 其余场景则不见得,前面我会再做阐明
从性能上,jsoniter
可能比泛滥大神联合开发的官网库性能还快的次要起因,一个是尽量减少不必要的内存复制,另一个是缩小 reflect 的应用——同一类型的对象,jsoniter 只调用 reflect 解析一次之后即缓存下来。不过随着 go 版本的迭代,原生 json 库的性能也越来越高,jsonter 的性能劣势也越来越窄。
此外,jsoniter 还反对 Get
函数,反对间接从一个 []byte
二进制数据中读取响应的字段,这个后文再做阐明
easyjson
这是 GitHub 下面的另一个 json 解析包。相比起 jsoniter 多达 9k 的 star 而言,easyjson 仿佛少一点,有 3k,但其实也算是一个人气很高的开源我的项目。
这个包最次要的卖点,仍然是快。为什么 easyjson 比 jsoniter 还要快?因为 easyjson 的开发模式与 protobuf 相似,在程序运行之前须要应用其代码工具,为每一个构造体专门生成序列化 / 反序列化的程序代码。每一个程序都有定制化的解析函数。
但也因为这种开发模式,easyjson 对业务的侵入性比拟高。一方面,在 go build
之前须要学生成代码;另一方面,相干的 json 处理函数也不兼容原生 json 库。
jsonparser
这是我集体十分喜爱的一个 json 解析库,3.9k 的 star 数也能够看出它人气不低。它的 GitHub 主页题目就号称比官网库有高达 10x 的性能。
还是那句话:开源我的项目本人的测试论断都不能无脑采信。这个 10x 的性能我集体也测进去过,但不能代表所有的场景。
为什么 jsonparser 有那么高的性能呢?因为对于 jsonparser 自身,它只负责解构出一个二进制字节串中的一些要害边界字符,比如说:
- 找到
"
,那么就找到完结的"
,这两头就是一个字符串 - 找到
[
,那么就找到成对的]
,这两头就是一个数组 - 找到
{
,那么就找到成对的}
,这两头就是一个对象 - ……
而后,它将找到的数据两头的这段 []byte
数据交给调用方,由调用方进行进一步的解决。此时,对这些二进制数据的解析和合法性检查是须要调用方来负责的。
为什么看起来这么麻烦的开源库我会喜爱呢?因为开发者能够基于 jsonparser,构建非凡逻辑,甚至是构建本人的 json 解析库。我本人的开源我的项目 jsonvalue 在晚期也基于 jsonparser 实现,只管起初为了进一步优化性能而弃用了 jsonparser,但这不影响我对它的推崇。
jsonvalue
这个我的项目是我集体的 JSON 解析库,设计之初是为了代替原生 JSON 库应用 map[string]interface{}
来解决非结构化 JSON 数据的需要。为此我有另外一篇文章叙述了这个问题:《[还在用 map[string]interface{} 解决 JSON?通知你一个更高效的办法——jsonvalue][2]》。
我目前大抵实现了对库的优化(参见 master 分支),性能曾经远远高于原生 json 库,并且稍微优于 jsoniter。当然,这也是特定状况下的,针对各种天壤之别的场景,各种库的性能各不相同。这也是我撰写本文的目标之一。
惯例操作下的 JSON 解决
除了 struct 和 map 之外,还有别的?上面就我在理论业务开发中遇到的场景都列一下,以飨读者。所有测试代码均开源,读者能够查阅,也能够向我提出意见,提 issue、评论、私聊均可。
惯例操作:构造体解析
构造体解析,这是 Go 中解决 JSON 最最惯例的操作了。这里我定义了这样的一个构造体:
type object struct {
Int int `json:"int"`
Float float64 `json:"float"`
String string `json:"string"`
Object *object `json:"object,omitempty"`
Array []*object `json:"array,omitempty"`}
略微使了点坏——这个构造能够疯狂自嵌套。
而后呢,我再定义了一段二进制流,用 json.cn 能够看到,这是一个有 5 层构造的 json 对象。
{"int":123456,"float":123.456789,"string":"Hello, world!","object":{"int":123456,"float":123.456789,"string":"Hello, world!","object":{"int":123456,"float":123.456789,"string":"Hello, world!","object":{"int":123456,"float":123.456789,"string":"Hello, world!","object":{"int":123456,"float":123.456789,"string":"Hello, world!"},"array":[{"int":123456,"float":123.456789,"string":"Hello, world!"},{"int":123456,"float":123.456789,"string":"Hello, world!"}]}}},"array":[{"int":123456,"float":123.456789,"string":"Hello, world!"},{"int":123456,"float":123.456789,"string":"Hello, world!"}]}
应用这两个构造,别离对官网 encoding/json
,jsoniter
。easyjson
三个包进行 Marshal 和 Unmarshal 的测试。首先咱们看反序列化(Unmarshal)的测试后果:
包名 | 函数 | 每迭代耗时 | 内存占用 | alloc 数 | 性能评估 |
---|---|---|---|---|---|
encoding/json |
Unmarshal |
8775 ns/op | 1144 B/op | 25 allocs/op | ★★ |
jsoniter |
Unmarshal |
6890 ns/op | 1720 B/op | 56 allocs/op | ★★☆ |
easyjson |
UnmarshalJSON |
4017 ns/op | 784 B/op | 19 allocs/op | ★★★★★ |
上面是序列化的测试后果:
包名 | 函数 | 每迭代耗时 | 内存占用 | alloc 数 | 性能评估 |
---|---|---|---|---|---|
encoding/json |
Marshal |
6859 ns/op | 1882 B/op | 6 allocs/op | ★★ |
jsoniter |
Marshal |
6843 ns/op | 1882 B/op | 6 allocs/op | ★★ |
easyjson |
MarshalJSON |
2463 ns/op | 1240 B/op | 5 allocs/op | ★★★★★ |
纯正从性能上来看,easyjson
不愧是专门为每一个 struct 定制化了序列化和反序列化函数,它达到了最高的性能,比另外两个库均有 2.5~3 倍的效率。而 jsoniter 略高于官网 json,不过相差不大。
惯例的非常规操作: map[string]interface{}
说是“非常规”的起因是,在这种状况下,程序须要解决非结构化的 JSON 数据,或者是在一段函数中解决多种不同类型的数据结构,因此不能应用构造体模式来解决。官网 JSON 库的解决方案是,(对于对象类型)采纳 map[string]interface{}
来保留。这个场景下,只有官网 json 和 jsoniter 反对。
测试数据如下,首先是反序列化:
包名 | 函数 | 每迭代耗时 | 内存占用 | alloc 数 | 性能评估 |
---|---|---|---|---|---|
encoding/json |
Unmarshal |
13040 ns/op | 4512 B/op | 128 allocs/op | ★★ |
jsoniter |
Unmarshal |
9442 ns/op | 4521 B/op | 136 allocs/op | ★★ |
序列化状况测试数据如下:
包名 | 函数 | 每迭代耗时 | 内存占用 | alloc 数 | 性能评估 |
---|---|---|---|---|---|
encoding/json |
Marshal |
17140 ns/op | 5865 B/op | 121 allocs/op | ★★ |
jsoniter |
Marshal |
17132 ns/op | 5865 B/op | 121 allocs/op | ★★ |
可见,针对这种状况,大家都是一丘之貉,jsoniter 并没有什么显著劣势。即使是 jsoniter 作为卖点的大数据量解析,劣势也是微不足道。
在等同数据量状况下,两个库反序列化的耗时基本上是构造体状况的两倍,而序列化工夫则是构造体状况的约 2.5 倍。
Emmm……老铁们能不必这种操作就不要用了吧,更何况程序在解决 interface{}
时还须要各种断言,这种苦楚,各位能够看我的文章感受一下。
非常规操作——反序列化篇
真的波及到无奈应用 struct 的时候,就各种开源我的项目八仙过海各显神通了。每一个库其实都有十分具体和弱小的额定性能,单单本文必定无奈说完。这里我就几个库及其代表的思路列举一下吧,前面也会附上各种状况的测试数据。
jsoniter
在解决非结构化 JSON 中,如果要解析一段 []byte
数据并取得其中的某个值,jsoniter 有以下相相似的计划。
第一种计划是间接解析原文并返回所需数据:
// 读取二进制数据中 response.userList 数组中的第一个元素的 name 字段
username := jsoniter.Get(data, "response", "userList", 0, "name")
fmt.Println("username:", username.ToString())
也能够是间接返回一个对象,并且基于该对象能够持续操作:
obj := jsoniter.Get(data)
if obj.ValueType() == jsoniter.InvalidType {// err handling}
username := obj.Get("response", "userList", 0, "name")
fmt.Println("username:", username.ToString())
这个函数有一个十分大的特点,那就是按需解析。比如说在这个语句 obj := jsoniter.Get(data)
中,jsoniter 就只做了最低限度的数据查看,至多先解析出了以后是一个 object 类型的 JSON,其余局部的解析均不做。
而即使是到了第二个调用 obj.Get("response", "userList", 0, "name")
中,jsoniter 也是竭尽可能减少不必要的解析,只解析须要解析的局部。
比如说,申请参数中要求解析 response.userList
的值,那么 jsoniter 在遇到诸如 response.gameList
等无关字段的时候,那么 jsoniter 就回尽量绕开而不去解决,从而尽可能地缩小无关的 CPU 工夫。
不过须要留神的是,返回的这个 obj
对象,从接口性能来看,能够了解为它是只读的,无奈从新序列化为二进制序列。
jsonparser
绝对于 jsoniter,要解析一段 []byte
数据并取得其中的某个值,jsonparser 的反对比拟无限。
比如说,如果咱们可能实现晓得某一个值的类型,比如说下面的 username 字段,那么咱们能够这么获取:
username, err := jsonparser.GetString(data, "response", "userList", "[0]", "name")
if err != nil {// err handling}
fmt.Println("username:", username)
然而 jsonparser 的 Get 系列函数只能取得除了 null 之外的根本类型,也就是 number, boolean, string 三种。
如果要操作 object 和 array,就要相熟上面的两个函数,而这两个函数我集体感觉就是 jsonparser 的外围:
func ArrayEach(data []byte, cb func(value []byte, dataType ValueType, offset int, err error), keys ...string) (offset int, err error)
func ObjectEach(data []byte, callback func(key []byte, value []byte, dataType ValueType, offset int) error, keys ...string) (err error)
这两个函数按程序解析二进制数据,并将提取出的数据段通过回调函数返回给调用方,由调用方对数据进行操作。调用方能够组 map,能够组 slice,甚至能够做一些平时无奈操作的操作(后文会做阐明)
jsonvalue
这个是我自己开发的开源 Go JSON 操作库,在 Get 类操作的 API 设计格调上与 jsoniter 的第二种格调比拟相似。
比方咱们同样是要获取后面所说的 username 字段,那么咱们能够这么获取:
v, err := jsonvalue.Unmarshal(data)
if err != nil {// err handling}
username := v.GetString("response", "userList", 0, "name")
fmt.Println("username:", username)
性能测试比照
在本大节所说的“非常规操作”场景下,三个库中,jsoniter 和 jsonparser 在解析时都是“按需解析”,而 jsonvalue 则是全面解析。因而在制订测试计划的时候还是有所区别。
这里我先抛出测试数据,测试评估中有两局部:
- 性能评估: 示意在该场景下的性能评分,不思考是否好用,仅思考 CPU 执行效率高不高
- 性能评估: 示意在该场景下,取得数据之后,程序后续的解决是否不便。不思考反序列化性能高不高
包名 | 性能阐明 / 次要函数调用 | 每迭代耗时 | 内存占用 | alloc 数 | 性能评估 | 性能评估 |
---|---|---|---|---|---|---|
浅解析 | ||||||
jsoniter |
any := jsoniter.Get(raw); keys := any.Keys() |
9118 ns/op | 3024 B/op | 139 allocs/op | ☆ | ★★★ |
jsonvalue |
jsonvalue.Unmarshal() |
7684 ns/op | 9072 B/op | 61 allocs/op | ★ | ★★★★★ |
jsonparser |
jsonparser.ObjectEach(raw, objEach) |
853 ns/op | 0 B/op | 0 allocs/op | ★★★★★ | ★★ |
读取其中一个层级较深的数据 | ||||||
jsoniter |
any.Get("object", "object", "object", "array", 1) |
9118 ns/op | 3024 B/op | 139 allocs/op | ☆ | ★★★★★ |
jsonvalue |
jsonvalue.Unmarshal(); v.Get("object", "object", "object", "array", 1) |
7928 ns/op | 9072 B/op | 61 allocs/op | ★ | ★★★★★ |
jsonparser |
jsonparser.Get(raw, "object", "object", "object", "array", "[1]") |
917 ns/op | 0 B/op | 0 allocs/op | ★★★★★ | ★★☆ |
从大量(100x)数据中仅读取其中一个较深层级的值 | ||||||
jsoniter |
jsoniter.Get(raw, "10", "object", "object", "object", "array", 1) |
29967 ns/op | 4913 B/op | 469 allocs/op | ★ | ★★★★★ |
jsonvalue |
jsonvalue.Unmarshal(); v.Get("10", "object", "object", "object", "array", 1) |
799450 ns/op | 917030 B/op | 6011 allocs/op | ★★★★★ | |
jsonparser |
jsonparser.Get(raw, "10", "object", "object", "object", "array", "[1]") |
8826 ns/op | 0 B/op | 0 allocs/op | ★★★★★ | ★★☆ |
残缺遍历 | ||||||
jsoniter |
jsoniter.Get(raw) 并对每一个 child 递归解析 |
45237 ns/op | 12659 B/op | 671 allocs/op | ☆ | ★★ |
jsonvalue |
jsonvalue.Unmarshal() |
7928 ns/op | 9072 B/op | 61 allocs/op | ★★★ | ★★★★★ |
jsonparser |
jsonparser.ObjectEach(raw, objEach) 并对每一个 child 递归解析 |
3705 ns/op | 0 B/op | 0 allocs/op | ★★★★★ | ☆ |
encoding/json |
Unmarshal |
13040 ns/op | 4512 B/op | 128 allocs/op | ★ | ★ |
jsoniter |
Unmarshal |
9442 ns/op | 4521 B/op | 136 allocs/op | ★☆ | ★ |
可见,上述测试数据中将反序列化场景中分为四种。这里我具体阐明一下四种状况的利用场景,以及相应的技术选型倡议
浅解析
在测试代码中,浅解析指的是针对一个较深层级的构造,仅仅将其最浅一层的 key 列表解析进去。这个场景更多的是作为参考。能够看到,jsonparser 的性能完爆其余开源库,它能够以最快的速度将第一层的 key 列表解析进去。
然而在易用性不便,jsonparser 和 jsoniter 都须要开发者对取得的数据再做进一步的解决,因而 jsoniter 和 jsonparser 的易用性在这个场景下均略低。
获取注释中的具体某一个数据
这个场景是这样的:JSON 数据注释中,仅有一小部分数据对以后业务有用并须要获取。这里我分了两种状况:
-
有用数据占全副数据的比例较高(对应“读取其中一个层级较深的数据”):
- 这一场景下从性能上来看,jsonparser 的体现判若两人地优越
- 从易用性的角度,jsonparser 须要调用方再行解决一遍数据,因而 jsoniter 和 jsonvalue 更胜一筹
-
有用数据占全副数据的比例较低(对应”从大量(100x)数据中仅读取其中一个较深层级的值“):
- 这一场景从性能上看,jsonprser 仍然完爆
- 从易用性的角度,jsonparser 仍然很弱
- 综合易用性和性能,这一场景中,有用数据的比例越低,jsonparser 的价值就越高
-
业务须要残缺解析数据——这一场景是对各个计划综合性能最残缺的考量
- 从性能的角度,jsonparser 仍然优良,但在这一场景中,易用性其实很成问题——简单的遍历操作之中,还须要再封装逻辑去存储数据
-
性能第二是 jsonvalue,这也是笔者十分自信的中央
- jsonvalue 实现全副且残缺的解析,其耗时比号称高速的 jsoniter 更低
- 相比 jsonparser,尽管 jsonvalue 外表上的解决工夫是 jsonparser 的 2.5 倍,但后者只是实现了数据的半加工,而前者则是将成品拿了进去给调用方应用
- 至于 jsoniter,在这个场景下就不要用了——在须要对数据全解析的状况下,它的数据几乎没法看
- 最初还加上了官网 json 库和 jsoniter 解析 map 的数据,仅作参考——在这个场景下,也倡议是不要用了
非常规操作——序列化篇
这里指的是在没有构造体的状况下,序列化一段数据。这种场景个别产生在以下状况中:
- 须要序列化的数据格式不确定,可能会依据其余的参数来生成。
- 须要序列化的数据过多且过于琐碎,如果一一定义构造体并进行 marshal 的话,代码的可读性太差。
这个场景的第一个解决方案就是前文提到的“惯例的非常规操作”,也就是应用 map。
至于非常规操作嘛,咱们首先排除 jsoniter 和 jsonparser,因为他们没有间接的构建自定义 json 构造的办法。接着排除 easyjson,因为它无奈针对 map 操作。剩下的也就只有 jsonvalue 了。
比方咱们返回用户的昵称,假如返回格局是:{"code":0,"message":"success","data":{"nickname":"振兴中华"}}
。应用 map 的代码如下:
code := 0
nickname := "振兴中华"
res := map[string]interface{}{
"code": code,
"message": "success",
"data": map[string]string{"nickname": nickname},
}
b, _ := json.Marshal(&res)
而 jsonvalue 的办法为:
res := jsonvalue.NewObject()
res.SetInt(0).At("code")
res.SetString("success").At("message")
res.SetString(nickname).At("data", "nickname")
b := res.MustMarshal()
应该说易用性上,都十分不便。咱们针对官网 json、jsoniter、jsonvalue 别离进行序列化操作,测得的数据如下:
包名 | 函数 | 每迭代耗时 | 内存占用 | alloc 数 | 性能评估 |
---|---|---|---|---|---|
encoding/json |
Marshal |
16273 ns/op | 5865 B/op | 121 allocs/op | ★☆ |
jsoniter |
Marshal |
16616 ns/op | 5865 B/op | 121 allocs/op | ★☆ |
jsonvalue |
Marshal |
4521 ns/op | 2224 B/op | 5 allocs/op | ★★★★★ |
后果曾经非常明显了。这个起因大家也能明确,因为在解决 map 的时候,须要应用 reflect 机制去解决数据类型,这大大降低了程序的性能。
论断以及选型倡议
构造体序列化和反序列化
在这个场景中,我集体首推的是官网的 json 库。可能读者会比拟意外。以下是我的观点:
- 尽管 easyjson 的性能压倒其余所有开源我的项目,但它有一个最大的缺点,那就是须要额定应用工具来生成这段代码,而对这额定工具的版本控制就多了一分运维老本。当然如果读者的团队曾经可能很好地解决 protobuf 了的话,那么也是能够用同样的思路来治理 easyjson 的
- Go 1.8 之前,官网 json 库的性能就收到多方诟病。但现今(1.16.3)官网 json 库的性能已不可同日而语。此外,作为应用最为宽泛(没有之一)的 json 库,官网库的 bug 是起码的、兼容性也是最好的
- jsoniter 的性能尽管仍然优于官网,但没有达到逆天的水平,如果心愿有极致的性能,那么你应该抉择 easyjson 而不是 jsoniter
- jsoniter 近年曾经不沉闷了,笔者前段时间提了一个 issue 没人回复。起初在下来看了一下 issue 列表,发现竟然还遗留一些 2018 年的 issue
非结构化数据的序列化和反序列化
这个场景下,咱们要分高数据利用率和低数据利用率两种状况来看。所谓数据利用率,指的是 JSON 数据的注释中,如果说超过四分之一的数据都是业务须要关注和解决的,那就算是高数据利用率。
- 高数据利用率 – 这种状况下,我举荐应用 jsonvalue
-
低数据利用率 – 这里分两种状况:JSON 数据是否还须要从新序列化回去
- 无需从新序列化:这个时候,抉择 jsonparser 就行了,它的性能切实是夺目
- 须要从新序列化:这种状况,有两种抉择,如果对性能要求绝对较低,能够应用 jsonvalue;如果性能的要求要求高,并且只须要往二进制序列中仅仅插入一个数据(重要),那么能够采纳 jsoniter 的
Set
办法。读者能够查阅 godoc
实际操作中,超大 JSON 数据量、同时须要从新序列化的状况非常少。这种场景下往往是是代理服务器、网关、overlay 中继服务等,同时又须要往原数据中注入额定信息的时候应用。换句话说,jsoniter 的实用场景比拟无限。
上面是从 1% 到 60% 数据覆盖率下,不同库的操作效率比照(纵坐标单位:μs/op)
能够看到,当 jsoniter 的数据利用率达到 25% 时,相比 jsonvalue 就曾经没有任何劣势;而 jsonparser 则是 40% 左右。至于 jsonvalue,因为对数据作了一次性的全解析,因而解析后的数据存取耗时极少,因而在不同数据覆盖率下的耗时都很稳固。
其余正道操作
笔者在理论利用中还遇到过一些对于 JSON 奇奇怪怪的解决场景,也趁这个机会列出来,分享一下我的解决方案。
不辨别大小写的 JSON
前文说到:“json 在解析时,如果遇到大小写问题,会尽可能地进行大小写转换。即使是一个 key 与构造体中的定义不同,但如果疏忽大小写后是雷同的,那么仍然可能为字段赋值。”
然而呢,如果你应用的是 map、jsoniter、jsonparser,这就是个大问题了。咱们有两个服务,同时操作 MySQL 数据库中的同一个字段,然而两个 Go 服务所定义的构造体中,有一个字母的大小写不统一。这个问题是长期存在的,但因为官网 json 解析构造体时的上述个性,导致这个问题始终没有裸露。直到有一天,咱们写了一个脚本程序洗数据的时候,采纳了 map 形式来读取这个字段的时候,Bug 就曝光了
于是我起初把大小写反对的个性退出了 jsonvalue 中,解决了这个问题:
raw := `{"user":{"nickName":"pony"}}` // 留神当中的 N
v, _ := jsonvalue.UnmarshalString(raw)
fmt.Println("nickname:", v.GetString("user", "nickname"))
fmt.Println("nickname:", v.Caseless().GetString("user", "nickname"))
// 输入
// nickname:
// nickname: pony
有程序的 JSON 对象
在单干兄弟模块的接口时,对方推数据流的时候是以一个 JSON 对象的格局给到咱们的业务模块中的。起初依据需要,推过来的数据要求是有序的。如果接口格局改成数组,那么就须要对单方接口的数据结构作较大的改变。此外,咱们在滚动降级的时候势必遇到新旧模块同时存在的状况,所以接口须要同时兼容两套接口格局。
最初咱们采纳了一个很正道的形式——数据生产方是可能按程序将 KV 推出来的,而咱们作为生产方,应用 jsonparser
的 ObjectEach
函数,就可能按程序取得 kv 字节序列,从而也实现数据的程序获取。
跨语言 UTF-8 字符串对接
Go 算是一个很年老的语言,在它诞生的时候,互联网上的支流字符编码曾经是 unicode,编码格局则是 UTF-8 了。而其余辈分更老的语言,因为各种起因,可能采纳了不一样的编码格局。
这就导致了在进行跨语言 JSON 对接时,不同团队、不同公司针对 unicode 宽字符时,采纳的编码格局可能不同。如果遇到这种状况,那么解决办法就是对立采纳 ASCII 编码。如果是官网 json,能够参考这个问答本义宽字符。
如果是应用 jsonvalue,则默认就是 ascii 本义,比如说:
v := jsonvalue.NewObject()
v.SetString("中国").At("nation")
fmt.Println(v.MustMarshalString())
// 输入
// {"nation":"\u4E2D\u56FD"}
参考资料
-
本文中波及到的开源库:
- jsoniter
- rapidjson:这个库是在学习过程中理解到的另一个库,采纳 cgo 实现。集体感觉,这就有点过了,如果真的对性能要求这么高,不如间接用 C++。而且这个库也不再迭代了,读者理解一下就行
- jsonparser
- easyjson
- jsonvalue
- Go monkeypatching
-
本文波及的测试数据和测试方法参见:
- jsonvalue-test
- JSON 序列化中的本义和 Unicode 编码
- 号称全世界最快的 JSON 解析器,比别的快 10x
- json-iterator/go 应用笔记
- 如何评估 jsoniter 自称是最快的 JSON 解析器?
- JSON-ITERATOR 应用要留神的大坑
- Go 学习_28_应用 easyjson 高效解析 json 数据
本文章采纳 常识共享署名 - 非商业性应用 - 雷同形式共享 4.0 国内许可协定 进行许可。
本文链接:https://segmentfault.com/a/1190000039957766
原作者:amc,原文公布于云 + 社区,也是自己的博客。欢送转载,但请注明出处。
原文题目:《Go 语言原生的 json 包有什么问题?如何更好地解决 JSON 数据?》
公布日期:2021-05-06
原文链接:https://cloud.tencent.com/developer/article/1820473。