RFC 6902 的 Go 语言实现
go get -u github.com/520MianXiangDuiXiang520/json-diff
与官方 json 包的序列化和反序列化不同,官方包序列化需要指定一个 interface{}
, 像:
package main
import "json"
func main() {
jsonStr := "{}"
var jsonObj interface{}
node := json.Unmarshal(&jsonObj, []byte(jsonStr))
// ...
}
这样不方便编辑反序列化后的 json 对象, json-diff 可以将任意的 json 串转换成统一的 JsonNode
类型,并且提供一系列的增删查改方法,方便操作对象:
func ExampleUnmarshal() {
json := `{
"A": 2,
"B": [1, 2, 4],
"C": {
"CA": {"CAA": 1}
}
}`
jsonNode := Unmarshal([]byte(json))
fmt.Println(jsonNode)
}
通过对比两个 Json 串,输出他们的差异或者通过差异串得到修改后的 json 串
func ExampleAsDiffs() {
json1 := `{
"A": 1,
"B": [1, 2, 3],
"C": {
"CA": 1
}
}`
json2 := `{
"A": 2,
"B": [1, 2, 4],
"C": {
"CA": {"CAA": 1}
}
}`
res, _ := AsDiffs([]byte(json1), []byte(json2), UseMoveOption, UseCopyOption, UseFullRemoveOption)
fmt.Println(res)
}
func ExampleMergeDiff() {
json2 := `{
"A": 1,
"B": [1, 2, 3, {"BA": 1}],
"C": {
"CA": 1,
"CB": 2
}
}`
diffs := `[
{"op": "move", "from": "/A", "path": "/D"},
{"op": "move", "from": "/B/0", "path": "/B/1"},
{"op": "move", "from": "/B/2", "path": "/C/CB"}
]`
res, _ := MergeDiff([]byte(json2), []byte(diffs))
fmt.Println(res)
}
输出一个 json 格式的字节数组,类似于:
[
{ "op": "test", "path": "/a/b/c", "value": "foo" },
{ "op": "remove", "path": "/a/b/c" },
{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
{ "op": "replace", "path": "/a/b/c", "value": 42 },
{ "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]
其中数组中的每一项代表一个差异点,格式由 RFC 6902 定义,op 表示差异类型,有六种:
add
: 新增replace
: 替换remove
: 删除move
: 移动copy
: 复制test
: 测试
其中 move 和 copy 可以减少差异串的体积,但会增加差异比较的时间, 可以通过修改 AsDiff()
的 options 指定是否开启,options 的选项和用法如下:
// 返回差异时使用 Copy, 当发现新增的子串出现在原串中时,使用该选项可以将 Add 行为替换为 Copy 行为
// 以减少差异串的大小,但这需要额外的计算,默认不开启
UseCopyOption JsonDiffOption = 1 << iota
// 仅在 UseCopyOption 选项开启时有效,替换前会添加 Test 行为,以确保 Copy 的路径存在
UseCheckCopyOption
// 返回差异时使用 Copy, 当发现差异串中两个 Add 和 Remove 的值相等时,会将他们合并为一个 Move 行为
// 以此减少差异串的大小,默认不开启
UseMoveOption
// Remove 时除了返回 path, 还返回删除了的值,默认不开启
UseFullRemoveOption
对于一个对象,其内部元素的顺序不作为相等判断的依据,如
{
"a": 1,
"b": 2,
}
和
{
"b": 2,
"a": 1,
}
被认为是相等的。
对于一个列表,元素顺序则作为判断相等的依据,如:
{
"a": [1, 2]
}
和
{
"a": [2, 1]
}
被认为不相等。
只有一个元素的所有子元素全部相等,他们才相等
根据 RFC 6092,差异合并应该具有原子性,即列表中有一个差异合并失败,之前的合并全部作废,而 test 类型就用来在合并差异之前检查路径和值是否正确,你可以通过选项开启它,但即便不使用 test,合并也是原子性的。
json-diff 在合并差异前会深拷贝源数据,并使用拷贝的数据做差异合并,一旦发生错误,将会返回 nil, 任何情况下都不会修改原来的数据。