Skip to content

520MianXiangDuiXiang520/json-diff

Repository files navigation

Json-Diff

RFC 6902 的 Go 语言实现

GoDoc

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 表示差异类型,有六种:

  1. add: 新增
  2. replace: 替换
  3. remove: 删除
  4. move: 移动
  5. copy: 复制
  6. 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, 任何情况下都不会修改原来的数据。

参考

https://github.com/flipkart-incubator/zjsonpatch