gjson 代表的是【get json】,sjson 代表的则是【set json】,也就是对 json 进行更新。
有的时候我们希望对一个大 json 文档中的局部进行修改,这一点利用官方 encoding/json 也是可以完成的,但还是和 gjson 类似的问题。你需要这样几步:
- 定义一个 struct,对应到 json 的结构;
- json => 结构体的反序列化,得到一个带数据的结构体;
- 更新你希望修改的局部数据;
- 结构体重新序列化为 json。
1 带来的开发成本可能是一次性的,但 2 和 4 这里的序列化成本却是每次都需要付出的,这一点很痛。
所以,tidwall 在 gjson 之外补充了 sjson 来弥补对 json 文档进行局部更新的能力。
sjson
SJSON is a Go package that provides a very fast and simple way to set a value in a json document. For quickly retrieving json values check out GJSON.
sjson 支持以简单快速的方式来设置 json 中的值。是不是感觉似曾相识,其实跟 gjson 是一样的,二者也经常配合使用。这个系列的两兄弟最大的特点就是:
- 简单:不需要你定义结构体,有 json 文档,定义好规则就能读写;
- 快速:性能上优势巨大,只依赖原生 Golang 库,做到了局部读写,不用对整个文档进行序列化和反序列化。
Demo
首先我们用 go get 给自己的工程添加 sjson 依赖:
go get -u github.com/tidwall/sjson
执行如下代码:
package main
import "github.com/tidwall/sjson"
const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}`
func main() {
value, _ := sjson.Set(json, "name.last", "Anderson")
println(value)
}
打印出来的结果为:
{"name":{"first":"Janet","last":"Anderson"},"age":47}
gjson.Get(json, path)sjson.Set(json, path, value)
Path
gjson.Getsjson.Set
{
"name": {"first": "Tom", "last": "Anderson"},
"age":37,
"children": ["Sara","Alex","Jack"],
"fav.movie": "Deer Hunter",
"friends": [
{"first": "James", "last": "Murphy"},
{"first": "Roger", "last": "Craig"}
]
}
示例 path 对应 结果如下:
"name.last" >> "Anderson"
"age" >> 37
"children.1" >> "Alex"
"friends.1.last" >> "Craig"
有两个点需要注意:
- 在更新的场景下,有时候我们需要【插入】一个新的元素,这个时候可以用 index 下标 -1 来代表这个语义:
"children.-1" >> appends a new value to the end of the children array
- a.num.b 这种场景下,中间的 num 数字可能有歧义,可能代表 array 或 object 中的第 num 个元素,也可能指代一个 key,这个时候可以加上 : 来指定为 key:
{
"users":{
"2313":{"name":"Sara"},
"7839":{"name":"Andy"}
}
}
"users.:2313.name" >> "Sara"
支持的类型
sjson 支持将下面这些类型更新到 json 文档中:
- nil
- boolean: true, false
- 整数
- 浮点数
- 字符串
- 数组
- map[string]interface{}
若 sjson 未识别到,将会 fallback 到 encoding/json 的 Marshaller 进行序列化。
sjson.Set(`{"key":true}`, "key", nil)
sjson.Set(`{"key":true}`, "key", false)
sjson.Set(`{"key":true}`, "key", 1)
sjson.Set(`{"key":true}`, "key", 10.5)
sjson.Set(`{"key":true}`, "key", "hello")
sjson.Set(`{"key":true}`, "key", []string{"hello", "world"})
sjson.Set(`{"key":true}`, "key", map[string]interface{}{"hello":"world"})
常见用法
初始化一个 json 文档
value, _ := sjson.Set("", "name", "Tom")
println(value)
// Output:
// {"name":"Tom"}
value, _ := sjson.Set("", "name.last", "Anderson")
println(value)
// Output:
// {"name":{"last":"Anderson"}}
新增属性
value, _ := sjson.Set(`{"name":{"last":"Anderson"}}`, "name.first", "Sara")
println(value)
// Output:
// {"name":{"first":"Sara","last":"Anderson"}}
更新已经存在的属性
value, _ := sjson.Set(`{"name":{"last":"Anderson"}}`, "name.last", "Smith")
println(value)
// Output:
// {"name":{"last":"Smith"}}
往 array 中新增一个元素
value, _ := sjson.Set(`{"friends":["Andy","Carol"]}`, "friends.2", "Sara")
println(value)
// Output:
// {"friends":["Andy","Carol","Sara"]
或者使用我们前面提到的 -1 下标
value, _ := sjson.Set(`{"friends":["Andy","Carol"]}`, "friends.-1", "Sara")
println(value)
// Output:
// {"friends":["Andy","Carol","Sara"]
注意,-1 是自动往末尾加,如果明确下标,如上面的 friends.2,那么一定要保证 2 就是最后的下标,否则会拆入 null:
value, _ := sjson.Set(`{"friends":["Andy","Carol"]}`, "friends.4", "Sara")
println(value)
// Output:
// {"friends":["Andy","Carol",null,null,"Sara"]
删除属性
value, _ := sjson.Delete(`{"name":{"first":"Sara","last":"Anderson"}}`, "name.first")
println(value)
// Output:
// {"name":{"last":"Anderson"}}
删除 array 元素
指定下标
value, _ := sjson.Delete(`{"friends":["Andy","Carol"]}`, "friends.1")
println(value)
// Output:
// {"friends":["Andy"]}
指定最后一个
value, _ := sjson.Delete(`{"friends":["Andy","Carol"]}`, "friends.-1")
println(value)
// Output:
// {"friends":["Andy"]}
高级用法
除了类似 gjson 中的 Bytes 函数,减少内存分配外,sjson 还支持了一些 option 来细粒度控制,我们来看一下:
// Options represents additional options for the Set and Delete functions.
type Options struct {
// Optimistic is a hint that the value likely exists which
// allows for the sjson to perform a fast-track search and replace.
Optimistic bool
// ReplaceInPlace is a hint to replace the input json rather than
// allocate a new json byte slice. When this field is specified
// the input json will not longer be valid and it should not be used
// In the case when the destination slice doesn't have enough free
// bytes to replace the data in place, a new bytes slice will be
// created under the hood.
// The Optimistic flag must be set to true and the input must be a
// byte slice in order to use this field.
ReplaceInPlace bool
}
- Optimistic: 指明操作的值很大可能是已经存在的,这样允许 sjson 针对性地做一些优化,性能更优;
- ReplaceInPlace: 指明更新 json 在原地完成,而不是创建出来新的 []byte 来承接 json,这样会有更好的性能收益。若开启,不能再依赖原来的 input json 内存地址,可能出现扩容。强依赖 Optimistic 为 true 才能启用。
事实上,这两个选项都是开发者声明,用以表明自己能接受多大程度的优化,通过官方benchmark我们也能看到,性能收益还是很可观的:
Benchmark_SJSON-8 3000000 805 ns/op 1077 B/op 3 allocs/op
Benchmark_SJSON_ReplaceInPlace-8 3000000 449 ns/op 0 B/op 0 allocs/op
Benchmark_JSON_Map-8 300000 21236 ns/op 6392 B/op 150 allocs/op
Benchmark_JSON_Struct-8 300000 14691 ns/op 1789 B/op 24 allocs/op
Benchmark_Gabs-8 300000 21311 ns/op 6752 B/op 150 allocs/op
Benchmark_FFJSON-8 300000 17673 ns/op 3589 B/op 47 allocs/op
Benchmark_EasyJSON-8 1500000 3119 ns/op 1061 B/op 13 allocs/op
开启了 ReplaceInPlace 后,耗时几乎变成了原来的一半,还是很厉害的。感兴趣的同学可以看一下 sjson-benchmark。