这样对比json,你试过吗

1,702 阅读1分钟

原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。

简介

近期,为了给一个核心系统减负,组内决定将一些调用量大的查询接口迁移到另一个系统,由于接口逻辑比较复杂,为了保证接口逻辑一致,我们决定将一周内的请求参数在两个接口重放,并用脚本校验两边接口的响应结果。
接口返回数据是json结构的,本想用icdiff、jq来实现,但时间紧迫一时没想出来,同事用python脚本实现了这个对比,但后来又仔细想了想,找出了使用icdiff、jq实现的方法。 

icdiff对比文本

对于普通的文本对比,使用diff命令即可完成,但icdiff比diff更好用,对比结果也会更加直观。

$ text1='{"name":"apple","item_no":2}'
$ text2='{"name":"a apple","item_no":2}'
$ icdiff <(echo "$text1") <(echo "$text2")

icdiff1.png

但如果用jq格式化一下再对比,看起来会更加清晰一些。

$ icdiff <(echo "$text1"|jq .) <(echo "$text2"|jq .)

icdiff2.png

但是有时json数据可能会出现数据一样,但key的顺序不一致的问题,比如:

$ text1='{"name":"apple","item_no":2}'
$ text2='{"item_no":2,"name":"apple"}'
$ icdiff <(echo "$text1"|jq .) <(echo "$text2"|jq .)

icdiff3.png
像对象转json这种操作,是有可能出现不同机器上返回json的key顺序不同的。

好在jq提供了-S选项,可对输出json的key排序,如下:

$ icdiff <(echo "$text1"|jq -S .) <(echo "$text2"|jq -S .)

icdiff4.png

本以为这样就解决了所有问题,但在脚本运行过程中,我们又发现了新情况,json中的数组项,顺序可能不一致,如下:

$ text1='{
    "order_id": 121345435624,
    "waybills": [
      {
        "waybill_id": 1,
        "name": "package1",
        "items": [
            {
                "name": "orange",
                "item_no": 1
            },
            {
                "name": "apple",
                "item_no": 2
            }
        ]
      },
      {
        "waybill_id": 2,
        "name": "package2",
        "items": [
            {
                "name": "pear",
                "item_no": 3
            },
            {
                "name": "banana",
                "item_no": 4
            }
        ]
      }
    ]
  }'
$ text2='{
    "order_id": 121345435624,
    "waybills": [
      {
        "waybill_id": 2,
        "name": "package2",
        "items": [
          {
              "name": "banana",
              "item_no": 4
          },
          {
              "name": "pear",
              "item_no": 3
          }
        ]
      },
      {
        "waybill_id": 1,
        "name": "package1",
        "items": [
          {
              "name": "orange",
              "item_no": 1
          },
          {
              "name": "apple",
              "item_no": 2
          }
        ]
      }
    ]
  }'
$ icdiff <(echo "$text1"|jq -S .) <(echo "$text2"|jq -S .)  

icdiff5.png
因为icdiff没法识别这种顺序区别,它只是逐行进行对比的,虽然两个json逻辑上是一样的,但它区别不出来。
程序中以下情况会造成这种顺序区别:

  1. 从数据库中查询数据时,如果SQL没有order by语句,那么返回结果的顺序是不确定的。
  2. 如果数据是在Map中,当用values()变换成List返回数据时,返回结构的顺序也是不确定的。

要解决这种情况还真挺麻烦的,因为需要对json中的数组进行排序,然后再进行对比,之前就是这里想不出来怎么实现。
但在后来,我又仔细的看了看jq的man文档,发现了一个有用的函数walk,然后就试了试,还真发现可以实现。

$ json_sort='walk(if type == "array" and length >0 then (if .[0].item_no then sort_by(.item_no) elif .[0].waybill_id then sort_by(.waybill_id) else. end) else . end)'
$ icdiff <(echo "$text1"|jq -S "$json_sort") <(echo "$text2"|jq -S "$json_sort")

icdiff6.png

总结

Linux命令结合起来,总能产生更大的作用,当然这也需要付出一些折腾时间啊。

往期内容

Linux文本命令技巧(下)
Linux文本命令技巧(上)
原来awk真是神器啊
好用的parallel命令
常用网络命令总结