持续创作,加速成长!这是我参与「日新方案 10 月更文挑战」的第3天,点击检查活动概况

上一篇文章中 解析 Golang 读取 json 神器 gjson 中咱们学习了 gjson。今日这篇咱们来看看它的好伙伴:sjson。二者都是出自 tidwall 开源的项目。强烈建议咱们看了前一篇后再来看 sjson,这样了解起来会快许多。

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 avery fastand simple way to set a value in a json document. For quickly retrieving json values check outGJSON.

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) 而已。

咱们拿到的 value 是个 string,语义上代表被更新后的 json 字符串。

Path

有了 gjson 的根底,了解 sjson 这里的 Set 逻辑就简略多了。他们共享了同一套 path 体系,你用什么办法从 gjson.Get 中查找元素,就用什么办法在 sjson.Set 中更新,差异在于 Set 中你需求供给一个更新的值算了。

咱们复习一下,假设有如下 json 文档:

{
  "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。