一款高效的json对比工具-低依赖,高兼容

avatar
作者
筋斗云
阅读量:2

一、摘要

今天推荐的是一款java中,对比两个json-diff对象是否一致的工具包 json-diff` 。他可以对比任何结构的两个json数据,并且将其中的不一致信息反馈给用户。工具还内置了很多配置可以来控制对比过程中的行为。目前已经补充大量单测,稳定性还是比较好的。

二、背景

公司最近在重构一个核心系统,至于为什么重构原因很多,就不说明了。但是这个核心系统承载较多的线上业务。为了不影响依赖该服务的应用,所以我们重构的最核心就是完全兼容老系统接口。

为了保证平滑上线,并且测试新系统与老系统是否一致,我们决定系统并行一段时间,并且在这段时间之中验证新接口对老接口的兼容性。我们新起一个代理服务,他会将我们的用户流量分别转发到新老接口,然后拿到两个结果,将老接口结果直接返回;异步去比较新老结果是否符合预期,进行记录或者报警。

这样系统在经过一段时间的测试,稳定性更高,出错的概率更小。

图片

因为系统都是采用http接口对外提供服务,且返回数据格式统一的是json格式。所以我们急需一款强大的Java语言的Json对比工具来帮助我们发现新老系统的不兼容之处。

三、工具介绍

1. 介绍

json-diff 是一款功能强大的json差异发现工具,支持任何结构的json对比,并且可以将对比结果返给用户。目前该工具更新到了 4.1.1-RC1-RELEASE 版本。最新版可以查看 版本列表 。建议使用最新版,旧版可能存在缺陷。

1.优点

  • 高效的
  • 精准定位差异
  • 轻量级
  • 依赖非常干净,只依赖fastJson,fastjson2,gson,jackson其中之一

目前支持功能

  • 支持json-path表示路径
  • 支持配置化比较行为。详情见下文
  • 支持拓展自定义比较器,用于满足特殊需求
  • 支持不同json框架选择。

二、使用文档

1.快速开始

引入依赖:

<dependency>     <groupId>cn.xiaoandcai</groupId>     <artifactId>json-diff</artifactId>     <!-- 旧版本可能存在某些缺陷。版本请以maven仓库最版为准。 -->     <version>${version}</version> </dependency>          <!-- 选择json解析框架。fastjson, fastjson2,gson,jackson 之一 --> <dependency>     <groupId>cn.xiaoandcai</groupId>     <artifactId>json-diff-impl-fastjson</artifactId>     <version>${version}</version> </dependency> 

版本查看
2024-04-11 最新版本:4.1.1-RC1-RELEASE

/**  * @author: codeleep  * @createTime: 2022/11/22 16:57  * @description: 使用示例  */ public class UseExample {      public static void main(String[] args) {         String array1 = "[1, 2, 3, 4, 5]";         String array2 = "[1, 3, 9, 4, 5]";          JsonComparedOption jsonComparedOption = new JsonComparedOption().setIgnoreOrder(true);         JsonCompareResult jsonCompareResult = new DefaultJsonDifference()                 .option(jsonComparedOption)                 .detectDiff(array1, array2);         System.out.println(JSON.toJSONString(jsonCompareResult));     } } 

主要分为两个对象 JsonComparedOption.class 和 JsonCompareResult.class;分别是配置对象和处理对象。

返回结果:

{     "defectsList": [         {             "actual": 9,             "expect": 2,             "illustrate": "The expect('2') data is inconsistent with the actual('9') data",             "indexPath": "root[]"         }     ],     "match": false } 

工具会返回两个json对象的差异。

2.配置

配置类型备注
ignoreOrderboolean是否比较过程中忽略数组顺序
mappingMap<String, String>将真实字段映射到期望字段,key是真实字段name,value是期望的字段name
ignorePathSet当对比的路径完全匹配时会被跳过。遇到数组使用 [] 即可。无需填入下标
ignoreKeySet对比object时。或忽略该key。对整个json生效

2.0.1-RC1-RELEASE 之后版本中移除了 keyFunction 配置参数。可以使用 ignorePath 来代替达到同样的效果。

工具提供了四个配置,来之对比过程中一些其他的要求。工具还在积极开发中,如果有新的需求,可以给作者提一个issuse。

在开发中。很多时候对比配置一致。可以使用 JsonDiffOption 进行开启唯一配置。这样也将获取更好的性能;

3.进阶

3.1. 全局使用固定配置

由于在设计中考虑到各线程比较配置相互独立。所以默认将配置防止在 ThreadLocal 中进行存储。但在大多数情况下,我们在全局比较时,配置并不会发生变化。

工具提供了全局配置方式。采用的方式是静态类属性。这样也会获得更好的性能。

// 开启并设置全局配置 JsonDiffOption.openUniqueOption(); JsonDiffOption.setGloballyUniqueOption(new JsonComparedOption()); // 不想使用时可以调用调整回线程独有模式  JsonDiffOption.closeUniqueOption(); 
3.2. 数组元素为对象关联

当我们在遇到数组元素是一个对象时。如下:

[     {         "date": "23日星期五",         "sunrise": "06:16",         "high": "高温 18.0℃"     },     {         "date": "24日星期六",         "sunrise": "06:14",         "high": "高温 21.0℃"     } ] 

在比较时, 如果希望 date 字段一致,则认为两个对象一致。那么可以将 sunrise, high 字段都配置到 ignorePath 中。如:

HashSet<String> ignorePath = new HashSet<>(); ignorePath.add("root[].sunrise"); ignorePath.add("root[].high"); 

如果只是不想关注某个字段。即是 ignorePath 正常用法。配置如上。

3.3. 字段映射

在比较两个对象时。也许由于字段名变更。导致校验不通过。这时可以使用 mapping 配置。将 真实字段名称映射至期望字段名称。在比较过程中会将

actual.mappingKey 与 expect.mappingValue 认为是应该比较的对象。具体配置如下

// mapping key 是 actual 键名 // mapping value 是 expect 键名 HashMap<String, String> mapping = new HashMap<>(); mapping.put("date", "sunrise"); 
3.4. 字段忽略

如果有一些字段是想在整个json都进行忽略的,可以使用 ignoreKey 进行全局忽略。当然如果不想全局忽略,但是配置了该项,还是会被忽略掉。

HashSet<String> ignoreKey = new HashSet<>(); ignoreKey.add("sunrise"); ignoreKey.add("high"); 
3.5 自定义比较器

在某些特俗场景下,我们需要自己实现比较器。这时可以通过 me.codeleep.jsondiff.core.handle.HandleFactory 来进行实现。可以参考 me.codeleep.jsondiff.core.handle.AbstractHandleFactory 实现

将我们的实现设置到JsonComparedOption中即可生效。

对于各个类型的比较器可以尝试继承系统比较器来进行使用:

  • me.codeleep.jsondiff.core.handle.array.ComplexArrayJsonNeat
  • me.codeleep.jsondiff.core.handle.object.ComplexObjectJsonNeat
  • me.codeleep.jsondiff.core.handle.other.ComplexOtherJsonNeat
  • me.codeleep.jsondiff.core.handle.primitive.ComplexPrimitiveJsonNeat

在工具中,目前提供了一个 me.codeleep.jsondiff.core.handle.custom.AlignArrayJsonDiff 来实现当数组长度不一致时,进行补齐,从而避免直接返回错误信息。

4.其他说明

前面提到工具几乎可以支持所有json结果的对比校验,并且发现差异。那它到底可以支持哪些呢,不知道是否符合你的需求呢?

  • 对象 ✅

    这是最简单的数据结构了,其中元素都以key-value构成,key是字符串,value可以是任何数据结构。

  • 数组 ✅

    支持严格顺序对比和忽略顺序对比,可以细化数组元素的类型

    • 基本类型 ✅

    • 对象类型 ✅

      该类型在对比时,可以通过ignorePath参数进行元素是否进行比较,将不关心的元素忽略掉。当然ignoreKey也可以,但其是全局生效

    • 数组类型 ✅

      元素也是数组,这样就形成了多维数组,工具理论上来说支持n维数组的对比

    • 元素类型不统一 ✅

      数组中,类型可能包含前面三种类型,这时工具会按照类型分类进行匹配,最后找不到的元素再反馈给用户。

由于json结构在单个看来,就只有对象和数组两种类型,该工具完美支持了所有类型。

三、交流群:710435809

  • 如果长时间未通过可发邮件至 codeleep@163.com 与我联系
  • 如果觉得对你有帮助,欢迎star。你的start是对我最大的鼓励。

四、测试用例编写规范

1.测试用例文件字段

文件中数据结构

{   "right": [     {       "actual": [{"a": 1}],       "expect": [{"b": 2}],       "ret": {},       "option": {         "ignoreOrder": false,         "mapping": {"a": "b"},         "ignorePath": ["root[].a"],         "ignoreKey": ["b"]       }     }   ],   "err"[.....],   "optionRight":[......],   "optionErr":[........], } 

每一个测试文件结构都如上,right,err,optionRight,optionErr分别为测试种类名称,测试名称下对应的是每一个测试用例[]中每一个object都是对应的一个用例每一测试用例为如下类似的object

名称类型备注
right数组正常用例
err数组异常用例
optionRight数组配置正常用例
optionErr数组配置异常用例
注: right,err,不会加载配置
    {       "actual": [{"a": 1}],       "expect": [{"b": 2}],       "ret": {},       "option": {         "ignoreOrder": false,         "mapping": {"a": 1},         "ignorePath": ["root[].a"],         "ignoreKey": ["b"]       } } 

“actual”: 实际需要对比的值 类型:测试array文件为array 测试object文件为object
“expect”: 期望该值为什么 类型:测试array文件为array 测试object文件为object
“ret”: 返回内容,如果用例为right中的自动判断返回内容中match是否为true进行判断 类型:object
“option”: 配置信息 对应本项目中的配置文件 类型:Object
   “ignoreOrder”: 是否忽略数组书序 类型:Boolean
   “mapping”: key 是 actual value 是 expect 映射 类型:Object
   “ignorePath”: 忽略的path. 以 . 来区分json层级; 会精准匹配路径 类型:array
   “ignoreKey”: 忽略的key。actual中有的字段,但expect没有的,会被忽略掉 类型:array

2.执行测试

直接进入测试代码执行

进入src/test/java/me/codeleep/jsondiff/test/ 中有两个后缀为Test的java文件,可直接运行该文件,也能进入其中运行指定方法
注:每一个方法运行都会把对应集合的方法全部运行一遍

通过TestNG xml文件执行

进入src/test/resources/testNG 其中有三个文件,分别是执行全部的case,执行全部异常或者匹配错误错误的用例,执行全部应当匹配正确

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!