gjson 代表的是【get json】,sjson 代表的则是【set json】,也就是对 json 进行更新。

有的时候我们希望对一个大 json 文档中的局部进行修改,这一点利用官方 encoding/json 也是可以完成的,但还是和 gjson 类似的问题。你需要这样几步:

  1. 定义一个 struct,对应到 json 的结构;
  2. json => 结构体的反序列化,得到一个带数据的结构体;
  3. 更新你希望修改的局部数据;
  4. 结构体重新序列化为 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"

有两个点需要注意:

  1. 在更新的场景下,有时候我们需要【插入】一个新的元素,这个时候可以用 index 下标 -1 来代表这个语义:
"children.-1"  >> appends a new value to the end of the children array
  1. 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。