JSON数据的差异对比工具(Java版)
短信预约 -IT技能 免费直播动态提醒
目录
背景
之前有类似接口diff对比,数据对比的测试需求,涉及到json格式的数据对比,调研了几个大神们分享的代码,选了一个最符合自己需求的研究了下。(可惜原文链接找不到了,原始作者看到了可以私信我下)
说明
这个对比方法,支持JsonObject和JsonArray类型的数据对比,支持:
- 深度的对比:list变化(个数、内容)、层级结构变化
- 字段的对比:新增、修改、删除数据可察觉,能找到对应的旧数据
- 支持特定字段忽略对比
输出的对比结果格式为:
源码分为JsonCompareUtils, JsonAndMapSortUtils两个类,对比入口是compareTwoJson方法
核心逻辑在JsonCompareUtils类中,JsonAndMapSortUtils主要做过程中的数据排序功能,相对独立。
源码
package com.xhzyqa.transcodetest.utils;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import org.apache.commons.lang.ArrayUtils;import org.apache.commons.lang.StringUtils;import java.util.HashMap;import java.util.Iterator;import java.util.LinkedHashMap;import java.util.Map;import java.util.stream.Stream;public class JsonCompareUtils { //标志位:对json报文中含有JsonArray类型的数据是否进行排序 private static boolean isSort; private Map oldJsonToMap = new LinkedHashMap<>(); private Map newJsonToMap = new LinkedHashMap<>(); //每一个实体里的排序字段 private static Map filedNameMap = new HashMap<>(); static { filedNameMap.put("dogs", "dogNo"); filedNameMap.put("cats", "catNo"); } //可以跳过比对的字段// private static String[] skipCompareFiledNameMap = {"dogAge", "catAge", "catName"}; private static String[] skipCompareFiledNameMap = {"key3"}; public String compareTwoJson(String oldJsonStr, String newJsonStr) { convertJsonToMap(JSON.parseObject(oldJsonStr), "", false); convertJsonToMap(JSON.parseObject(newJsonStr), "", true); //获取比较结果 Map differenceMap = compareTwoMaps(oldJsonToMap, newJsonToMap); String diffJsonResult = convertMapToJson(differenceMap); return diffJsonResult; } private void convertJsonToMap(Object json, String root, boolean isNew) { if (json instanceof JSONObject) { JSONObject jsonObject = ((JSONObject) json); Iterator iterator = jsonObject.keySet().iterator(); while (iterator.hasNext()) { Object key = iterator.next(); Object value = jsonObject.get(key); String newRoot = "".equals(root) ? key + "" : root + "." + key; fillInResultMap(value, newRoot, isNew); } } else if (json instanceof JSONArray) { JSONArray jsonArray = (JSONArray) json; //将jsonArray进行排序 if (isSort) { //需要排序 String sortEntityName = root.substring(root.lastIndexOf(".") + 1); String sortFiledName = filedNameMap.get(sortEntityName);//需要排序 获取排序字段 if (!StringUtils.isEmpty(sortFiledName)) { jsonArray = JsonAndMapSortUtils.jsonArrayToSort(jsonArray, sortFiledName, true); } } final JSONArray jsonArray1 = jsonArray; Stream.iterate(0, integer -> integer + 1).limit(jsonArray1.size()).forEach(index -> { Object value = jsonArray1.get(index); String newRoot = "".equals(root) ? "[" + index + "]" : root + ".[" + index + "]"; fillInResultMap(value, newRoot, isNew); }); } } public void fillInResultMap(Object value, String newRoot, boolean isNew) { if (value instanceof JSONObject || value instanceof JSONArray) { convertJsonToMap(value, newRoot, isNew); } else { //设置跳过比对的字段,直接不装入map boolean check = ArrayUtils.contains(JsonCompareUtils.skipCompareFiledNameMap, newRoot); if (!check){ if (!isNew) { oldJsonToMap.put(newRoot, value); } else { newJsonToMap.put(newRoot, value); } } } } private Map compareTwoMaps(Map oldJsonMap, Map newJsonMap) { //1.将newJsonMap的不同数据装进oldJsonMap,同时删除oldJsonMap中与newJsonMap相同的数据 newJsonMap.forEach((k, v) -> { Map differenceMap = new HashMap<>(); String lastFieldKey = k.substring(k.lastIndexOf(".") + 1);// boolean check = ArrayUtils.contains(JsonCompareUtils.skipCompareFiledNameMap, lastFieldKey);// if (!check){ if (oldJsonMap.containsKey(k)) {// boolean check = ArrayUtils.contains(JsonCompareUtils.skipCompareFiledNameMap, lastFieldKey); Object oldValue = oldJsonMap.get(k); if (v.equals(oldValue)) { oldJsonMap.remove(k); } else { differenceMap.put("oldValue", oldValue); differenceMap.put("newValue", v); oldJsonMap.put(k, differenceMap); } } else { differenceMap.put("oldValue", "no exists " + k); differenceMap.put("newValue", v); oldJsonMap.put(k, differenceMap); }// }else {// oldJsonMap.remove(k);// } }); //2.统一oldJsonMap中newMap不存在的数据的数据结构,便于解析 oldJsonMap.forEach((k, v) -> { String lastFieldKey = k.substring(k.lastIndexOf(".") + 1);// boolean check = ArrayUtils.contains(JsonCompareUtils.skipCompareFiledNameMap, lastFieldKey);// if (!check && !(v instanceof Map)) { if (!(v instanceof Map)) { Map differenceMap = new HashMap<>(); differenceMap.put("oldValue", v); differenceMap.put("newValue", "no exists " + k); oldJsonMap.put(k, differenceMap); } }); return oldJsonMap; } private String convertMapToJson(Map map) { JSONObject resultJSONObject = new JSONObject(); for (Iterator> it = map.entrySet().iterator(); it.hasNext(); ) { Map.Entry item = it.next(); String key = item.getKey(); Object value = item.getValue(); String[] paths = key.split("\\."); int i = 0; Object remarkObject = null;//用於深度標識對象 int indexAll = paths.length - 1; while (i <= paths.length - 1) { String path = paths[i]; if (i == 0) { //初始化对象标识 if (resultJSONObject.containsKey(path)) { remarkObject = resultJSONObject.get(path); } else { if (indexAll > i) {if (paths[i + 1].matches("\\[[0-9]+\\]")) { remarkObject = new JSONArray();} else { remarkObject = new JSONObject();}resultJSONObject.put(path, remarkObject); } else {resultJSONObject.put(path, value); } } i++; continue; } if (path.matches("\\[[0-9]+\\]")) {//匹配集合对象 int startIndex = path.lastIndexOf("["); int endIndext = path.lastIndexOf("]"); int index = Integer.parseInt(path.substring(startIndex + 1, endIndext)); if (indexAll > i) { if (paths[i + 1].matches("\\[[0-9]+\\]")) {while (((JSONArray) remarkObject).size() <= index) { if (((JSONArray) remarkObject).size() == index) { ((JSONArray) remarkObject).add(index, new JSONArray()); } else { ((JSONArray) remarkObject).add(null); }} } else {while (((JSONArray) remarkObject).size() <= index) { if (((JSONArray) remarkObject).size() == index) { ((JSONArray) remarkObject).add(index, new JSONObject()); } else { ((JSONArray) remarkObject).add(null); }} } remarkObject = ((JSONArray) remarkObject).get(index); } else { while (((JSONArray) remarkObject).size() <= index) {if (((JSONArray) remarkObject).size() == index) { ((JSONArray) remarkObject).add(index, value);} else { ((JSONArray) remarkObject).add(null);} } } } else { if (indexAll > i) { if (paths[i + 1].matches("\\[[0-9]+\\]")) {if (!((JSONObject) remarkObject).containsKey(path)) { ((JSONObject) remarkObject).put(path, new JSONArray());} } else {if (!((JSONObject) remarkObject).containsKey(path)) { ((JSONObject) remarkObject).put(path, new JSONObject());} } remarkObject = ((JSONObject) remarkObject).get(path); } else { ((JSONObject) remarkObject).put(path, value); } } i++; } } return JSON.toJSONString(resultJSONObject); } public boolean isSort() { return isSort; } public void setSort(boolean sort) { isSort = sort; } public static void main(String[] args) { String oldStr = "{key1:'aaa',key2:'bbb'}"; String newStr = "{key1:'aaa',key2:'bbb',key3:'c'}"; System.out.println(new JsonCompareUtils().compareTwoJson(oldStr, newStr)); System.out.println("\n========测试复杂json的比对============"); } }
package com.xhzyqa.transcodetest.utils;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import sun.misc.ASCIICaseInsensitiveComparator;import java.util.*;import java.util.function.Function;import java.util.stream.Collectors;public class JsonAndMapSortUtils { public static List mapByKeyToSort(Map map , final Comparator keySort){ List> entryList = new ArrayList>(map.entrySet()); Collections.sort(entryList, new Comparator>() { public int compare(Map.Entry o1, Map.Entry o2) { return keySort.compare(o1.getKey(),o2.getKey()); } }); //return (Map)entryList.stream().collect(Collectors.toMap(Map.Entry::getKey, Function.identity(), (key1, key2) -> key2)); Map afterToSortMap = new HashMap<>(); System.out.println("排序====="); entryList.forEach(m->{ System.out.println(m.getKey()+"===>"+m.getValue()); }); return entryList; } public static JSONArray jsonArrayToSort(JSONArray jsonArray,final String fildName,final boolean isAsc){ JSONArray afterSortJsonArray = new JSONArray(); List objectList = new ArrayList(); jsonArray.forEach(obj ->{ objectList.add((JSONObject)obj); }); Collections.sort(objectList, new Comparator() { @Override public int compare(JSONObject o1, JSONObject o2) { String fildValueA = o1.getString(fildName); String fildValueB = o2.getString(fildName); if (isAsc) return fildValueA.compareTo(fildValueB); return fildValueB.compareTo(fildValueA); } }); objectList.forEach(obj->{ afterSortJsonArray.add(obj); }); return afterSortJsonArray; } public static Map getMapData(){ LinkedHashMap map = new LinkedHashMap<>(); map.put("key1","麦兜"); map.put("key3","贝塔"); map.put("key5","酥妮"); map.put("key2","小H"); map.put("key4","小O"); return map; } public static JSONArray getJsonArrayData(){ JSONArray jsonArray = new JSONArray(); JSONObject jsonObject1 = new JSONObject(); jsonObject1.put("userId","1001"); jsonObject1.put("name","麦兜"); jsonArray.add(jsonObject1); JSONObject jsonObject3 = new JSONObject(); jsonObject3.put("userId","1003"); jsonObject3.put("name","酥妮"); jsonArray.add(jsonObject3); JSONObject jsonObject2 = new JSONObject(); jsonObject2.put("userId","1002"); jsonObject2.put("name","贝塔"); jsonArray.add(jsonObject2); return jsonArray; } public static void main(String[] args) { Map map = JsonAndMapSortUtils.getMapData(); JSONArray jsonArray = JsonAndMapSortUtils.getJsonArrayData(); List afterSortMap = JsonAndMapSortUtils.mapByKeyToSort(map,new ASCIICaseInsensitiveComparator()); JSONArray afterSortJsonArray_isAsc = JsonAndMapSortUtils.jsonArrayToSort(jsonArray,"userId",true); JSONArray afterSortJsonArray_noAsc = JsonAndMapSortUtils.jsonArrayToSort(jsonArray,"userId",false); System.out.println("map排序前:"+map); System.out.println("map排序后:"+afterSortMap+"\n"); System.out.println("JsonArray排序前:"+jsonArray); System.out.println("JsonArray排序后==》升序:"+afterSortJsonArray_isAsc); System.out.println("JsonArray排序后==》降序:"+afterSortJsonArray_noAsc); }}
源码走读
整个源码调用链路如下图,简单来说过程就是:object拆分解析-新旧数据逐个对比-结果信息组装三个步骤
其他
原始代码中有些小bug,已修复。目前这个工具主要被我拿来用在了一个接口数据对比工具中,来检测迭代前后的接口协议数据变更,以完善迭代变更范围来确认测试范围
来源地址:https://blog.csdn.net/hi_bigbai/article/details/128162687
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341