当前位置:   article > 正文

Java实现JSON数据的差异对比(转载)_比较两个json对象字段的差异

比较两个json对象字段的差异

原文链接:https://blog.csdn.net/hi_bigbai/article/details/128162687

背景
        之前有类似接口diff对比,数据对比的测试需求,涉及到json格式的数据对比,调研了几个大神们分享的代码,选了一个最符合自己需求的研究了下。(可惜原文链接找不到了,原始作者看到了可以私信我下)

说明
这个对比方法,支持JsonObject和JsonArray类型的数据对比,支持:

深度的对比:list变化(个数、内容)、层级结构变化
字段的对比:新增、修改、删除数据可察觉,能找到对应的旧数据
支持特定字段忽略对比
输出的对比结果格式为:

源码分为JsonCompareUtils, JsonAndMapSortUtils两个类,对比入口是compareTwoJson方法

核心逻辑在JsonCompareUtils类中,JsonAndMapSortUtils主要做过程中的数据排序功能,相对独立。

源码

 JsonCompareUtils 类

  1. package com.xhzyqa.transcodetest.utils;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONArray;
  4. import com.alibaba.fastjson.JSONObject;
  5. import org.apache.commons.lang.ArrayUtils;
  6. import org.apache.commons.lang.StringUtils;
  7. import java.util.HashMap;
  8. import java.util.Iterator;
  9. import java.util.LinkedHashMap;
  10. import java.util.Map;
  11. import java.util.stream.Stream;
  12. public class JsonCompareUtils {
  13. //标志位:对json报文中含有JsonArray类型的数据是否进行排序
  14. private static boolean isSort;
  15. private Map<String, Object> oldJsonToMap = new LinkedHashMap<>();
  16. private Map<String, Object> newJsonToMap = new LinkedHashMap<>();
  17. //每一个实体里的排序字段
  18. private static Map<String, String> filedNameMap = new HashMap<>();
  19. static {
  20. filedNameMap.put("dogs", "dogNo");
  21. filedNameMap.put("cats", "catNo");
  22. }
  23. //可以跳过比对的字段
  24. // private static String[] skipCompareFiledNameMap = {"dogAge", "catAge", "catName"};
  25. private static String[] skipCompareFiledNameMap = {"key3"};
  26. /**
  27. * 两json报文比对入口
  28. *
  29. * @param oldJsonStr
  30. * @param newJsonStr
  31. * @return
  32. */
  33. public String compareTwoJson(String oldJsonStr, String newJsonStr) {
  34. /**
  35. * 递归遍历json对象所有的key-value,以map形式的path:value进行存储
  36. * 然后对两个map进行比较
  37. */
  38. convertJsonToMap(JSON.parseObject(oldJsonStr), "", false);
  39. convertJsonToMap(JSON.parseObject(newJsonStr), "", true);
  40. //获取比较结果
  41. Map<String, Object> differenceMap = compareTwoMaps(oldJsonToMap, newJsonToMap);
  42. String diffJsonResult = convertMapToJson(differenceMap);
  43. return diffJsonResult;
  44. }
  45. /**
  46. * 将json数据转换为map存储--用于后续比较map
  47. *
  48. * @param json
  49. * @param root
  50. * @param isNew 区别新旧报文
  51. */
  52. private void convertJsonToMap(Object json, String root, boolean isNew) {
  53. if (json instanceof JSONObject) {
  54. JSONObject jsonObject = ((JSONObject) json);
  55. Iterator iterator = jsonObject.keySet().iterator();
  56. while (iterator.hasNext()) {
  57. Object key = iterator.next();
  58. Object value = jsonObject.get(key);
  59. String newRoot = "".equals(root) ? key + "" : root + "." + key;
  60. fillInResultMap(value, newRoot, isNew);
  61. }
  62. } else if (json instanceof JSONArray) {
  63. JSONArray jsonArray = (JSONArray) json;
  64. //将jsonArray进行排序
  65. if (isSort) {
  66. //需要排序
  67. String sortEntityName = root.substring(root.lastIndexOf(".") + 1);
  68. String sortFiledName = filedNameMap.get(sortEntityName);//需要排序 获取排序字段
  69. if (!StringUtils.isEmpty(sortFiledName)) {
  70. jsonArray = JsonAndMapSortUtils.jsonArrayToSort(jsonArray, sortFiledName, true);
  71. }
  72. }
  73. final JSONArray jsonArray1 = jsonArray;
  74. Stream.iterate(0, integer -> integer + 1).limit(jsonArray1.size()).forEach(index -> {
  75. Object value = jsonArray1.get(index);
  76. String newRoot = "".equals(root) ? "[" + index + "]" : root + ".[" + index + "]";
  77. fillInResultMap(value, newRoot, isNew);
  78. });
  79. }
  80. }
  81. /**
  82. * 封装json转map后的数据
  83. *
  84. * @param value
  85. * @param newRoot
  86. * @param isNew 区别新旧json
  87. */
  88. public void fillInResultMap(Object value, String newRoot, boolean isNew) {
  89. if (value instanceof JSONObject || value instanceof JSONArray) {
  90. convertJsonToMap(value, newRoot, isNew);
  91. } else {
  92. //设置跳过比对的字段,直接不装入map
  93. boolean check = ArrayUtils.contains(JsonCompareUtils.skipCompareFiledNameMap, newRoot);
  94. if (!check){
  95. if (!isNew) {
  96. oldJsonToMap.put(newRoot, value);
  97. } else {
  98. newJsonToMap.put(newRoot, value);
  99. }
  100. }
  101. }
  102. }
  103. /**
  104. * 比较两个map,将不同的数据以map形式存储并返回
  105. *
  106. * @param oldJsonMap
  107. * @param newJsonMap
  108. * @return
  109. */
  110. private Map<String, Object> compareTwoMaps(Map<String, Object> oldJsonMap, Map<String, Object> newJsonMap) {
  111. //1.将newJsonMap的不同数据装进oldJsonMap,同时删除oldJsonMap中与newJsonMap相同的数据
  112. newJsonMap.forEach((k, v) -> {
  113. Map<String, Object> differenceMap = new HashMap<>();
  114. String lastFieldKey = k.substring(k.lastIndexOf(".") + 1);
  115. // boolean check = ArrayUtils.contains(JsonCompareUtils.skipCompareFiledNameMap, lastFieldKey);
  116. // if (!check){
  117. if (oldJsonMap.containsKey(k)) {
  118. // boolean check = ArrayUtils.contains(JsonCompareUtils.skipCompareFiledNameMap, lastFieldKey);
  119. Object oldValue = oldJsonMap.get(k);
  120. if (v.equals(oldValue)) {
  121. oldJsonMap.remove(k);
  122. } else {
  123. differenceMap.put("oldValue", oldValue);
  124. differenceMap.put("newValue", v);
  125. oldJsonMap.put(k, differenceMap);
  126. }
  127. } else {
  128. differenceMap.put("oldValue", "no exists " + k);
  129. differenceMap.put("newValue", v);
  130. oldJsonMap.put(k, differenceMap);
  131. }
  132. // }else {
  133. // oldJsonMap.remove(k);
  134. // }
  135. });
  136. //2.统一oldJsonMap中newMap不存在的数据的数据结构,便于解析
  137. oldJsonMap.forEach((k, v) -> {
  138. String lastFieldKey = k.substring(k.lastIndexOf(".") + 1);
  139. // boolean check = ArrayUtils.contains(JsonCompareUtils.skipCompareFiledNameMap, lastFieldKey);
  140. // if (!check && !(v instanceof Map)) {
  141. if (!(v instanceof Map)) {
  142. Map<String, Object> differenceMap = new HashMap<>();
  143. differenceMap.put("oldValue", v);
  144. differenceMap.put("newValue", "no exists " + k);
  145. oldJsonMap.put(k, differenceMap);
  146. }
  147. });
  148. return oldJsonMap;
  149. }
  150. /**
  151. * 将已经找出不同数据的map根据key的层级结构封装成json返回
  152. *
  153. * @param map
  154. * @return
  155. */
  156. private String convertMapToJson(Map<String, Object> map) {
  157. JSONObject resultJSONObject = new JSONObject();
  158. for (Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator(); it.hasNext(); ) {
  159. Map.Entry<String, Object> item = it.next();
  160. String key = item.getKey();
  161. Object value = item.getValue();
  162. String[] paths = key.split("\\.");
  163. int i = 0;
  164. Object remarkObject = null;//用於深度標識對象
  165. int indexAll = paths.length - 1;
  166. while (i <= paths.length - 1) {
  167. String path = paths[i];
  168. if (i == 0) {
  169. //初始化对象标识
  170. if (resultJSONObject.containsKey(path)) {
  171. remarkObject = resultJSONObject.get(path);
  172. } else {
  173. if (indexAll > i) {
  174. if (paths[i + 1].matches("\\[[0-9]+\\]")) {
  175. remarkObject = new JSONArray();
  176. } else {
  177. remarkObject = new JSONObject();
  178. }
  179. resultJSONObject.put(path, remarkObject);
  180. } else {
  181. resultJSONObject.put(path, value);
  182. }
  183. }
  184. i++;
  185. continue;
  186. }
  187. if (path.matches("\\[[0-9]+\\]")) {//匹配集合对象
  188. int startIndex = path.lastIndexOf("[");
  189. int endIndext = path.lastIndexOf("]");
  190. int index = Integer.parseInt(path.substring(startIndex + 1, endIndext));
  191. if (indexAll > i) {
  192. if (paths[i + 1].matches("\\[[0-9]+\\]")) {
  193. while (((JSONArray) remarkObject).size() <= index) {
  194. if (((JSONArray) remarkObject).size() == index) {
  195. ((JSONArray) remarkObject).add(index, new JSONArray());
  196. } else {
  197. ((JSONArray) remarkObject).add(null);
  198. }
  199. }
  200. } else {
  201. while (((JSONArray) remarkObject).size() <= index) {
  202. if (((JSONArray) remarkObject).size() == index) {
  203. ((JSONArray) remarkObject).add(index, new JSONObject());
  204. } else {
  205. ((JSONArray) remarkObject).add(null);
  206. }
  207. }
  208. }
  209. remarkObject = ((JSONArray) remarkObject).get(index);
  210. } else {
  211. while (((JSONArray) remarkObject).size() <= index) {
  212. if (((JSONArray) remarkObject).size() == index) {
  213. ((JSONArray) remarkObject).add(index, value);
  214. } else {
  215. ((JSONArray) remarkObject).add(null);
  216. }
  217. }
  218. }
  219. } else {
  220. if (indexAll > i) {
  221. if (paths[i + 1].matches("\\[[0-9]+\\]")) {
  222. if (!((JSONObject) remarkObject).containsKey(path)) {
  223. ((JSONObject) remarkObject).put(path, new JSONArray());
  224. }
  225. } else {
  226. if (!((JSONObject) remarkObject).containsKey(path)) {
  227. ((JSONObject) remarkObject).put(path, new JSONObject());
  228. }
  229. }
  230. remarkObject = ((JSONObject) remarkObject).get(path);
  231. } else {
  232. ((JSONObject) remarkObject).put(path, value);
  233. }
  234. }
  235. i++;
  236. }
  237. }
  238. return JSON.toJSONString(resultJSONObject);
  239. }
  240. public boolean isSort() {
  241. return isSort;
  242. }
  243. public void setSort(boolean sort) {
  244. isSort = sort;
  245. }
  246. public static void main(String[] args) {
  247. String oldStr = "{key1:'aaa',key2:'bbb'}";
  248. String newStr = "{key1:'aaa',key2:'bbb',key3:'c'}";
  249. System.out.println(new JsonCompareUtils().compareTwoJson(oldStr, newStr));
  250. System.out.println("\n========测试复杂json的比对============");
  251. }
  252. /**
  253. * 测试类
  254. static class JsonCompareTest {
  255. public static void compareTest() {
  256. String oldJson = MakeJsonCompareDatas.getJsonDataOldStr();
  257. String newJson = MakeJsonCompareDatas.getJsonDataNewStr();
  258. //对json报文中含有JsonArray类型的数据是否进行排序
  259. JsonCompareUtils.isSort = true;
  260. String compareResult = new JsonCompareUtils().compareTwoJson(oldJson, newJson);
  261. System.out.println("oldJson==>" + oldJson);
  262. System.out.println("newJson==>" + newJson);
  263. System.out.println("\nisSort==" + isSort + "-->compareResult==>\n" + compareResult);
  264. }
  265. }
  266. */
  267. }

JsonAndMapSortUtils 类

  1. package com.xhzyqa.transcodetest.utils;
  2. import com.alibaba.fastjson.JSONArray;
  3. import com.alibaba.fastjson.JSONObject;
  4. import sun.misc.ASCIICaseInsensitiveComparator;
  5. import java.util.*;
  6. import java.util.function.Function;
  7. import java.util.stream.Collectors;
  8. public class JsonAndMapSortUtils {
  9. /**
  10. * map排序
  11. * @param map
  12. * @param keySort
  13. * @param <k>
  14. * @param <v>
  15. * @return
  16. */
  17. public static <k,v> List mapByKeyToSort(Map<k,v> map , final Comparator keySort){
  18. List<Map.Entry<k,v>> entryList = new ArrayList<Map.Entry<k, v>>(map.entrySet());
  19. Collections.sort(entryList, new Comparator<Map.Entry<k, v>>() {
  20. public int compare(Map.Entry<k, v> o1, Map.Entry<k, v> o2) {
  21. return keySort.compare(o1.getKey(),o2.getKey());
  22. }
  23. });
  24. //return (Map<k,v>)entryList.stream().collect(Collectors.toMap(Map.Entry<k,v>::getKey, Function.identity(), (key1, key2) -> key2));
  25. Map<String,String> afterToSortMap = new HashMap<>();
  26. /*for (Map.Entry<k,v> m : entryList){
  27. System.out.println(m.getKey()+"===>"+m.getValue());
  28. }*/
  29. System.out.println("排序=====");
  30. entryList.forEach(m->{
  31. System.out.println(m.getKey()+"===>"+m.getValue());
  32. });
  33. return entryList;
  34. }
  35. /**
  36. * JSONArray排序
  37. * @param jsonArray
  38. * @param fildName
  39. * @param isAsc
  40. * @return
  41. */
  42. public static JSONArray jsonArrayToSort(JSONArray jsonArray,final String fildName,final boolean isAsc){
  43. JSONArray afterSortJsonArray = new JSONArray();
  44. List<JSONObject> objectList = new ArrayList<JSONObject>();
  45. jsonArray.forEach(obj ->{
  46. objectList.add((JSONObject)obj);
  47. });
  48. Collections.sort(objectList, new Comparator<JSONObject>() {
  49. @Override
  50. public int compare(JSONObject o1, JSONObject o2) {
  51. String fildValueA = o1.getString(fildName);
  52. String fildValueB = o2.getString(fildName);
  53. if (isAsc)
  54. return fildValueA.compareTo(fildValueB);
  55. return fildValueB.compareTo(fildValueA);
  56. }
  57. });
  58. objectList.forEach(obj->{
  59. afterSortJsonArray.add(obj);
  60. });
  61. return afterSortJsonArray;
  62. }
  63. /**
  64. *准备map测试数据
  65. */
  66. public static Map<String,String> getMapData(){
  67. LinkedHashMap<String,String> map = new LinkedHashMap<>();
  68. map.put("key1","麦兜");
  69. map.put("key3","贝塔");
  70. map.put("key5","酥妮");
  71. map.put("key2","小H");
  72. map.put("key4","小O");
  73. return map;
  74. }
  75. /**
  76. *准备json测试数据
  77. */
  78. public static JSONArray getJsonArrayData(){
  79. JSONArray jsonArray = new JSONArray();
  80. JSONObject jsonObject1 = new JSONObject();
  81. jsonObject1.put("userId","1001");
  82. jsonObject1.put("name","麦兜");
  83. jsonArray.add(jsonObject1);
  84. JSONObject jsonObject3 = new JSONObject();
  85. jsonObject3.put("userId","1003");
  86. jsonObject3.put("name","酥妮");
  87. jsonArray.add(jsonObject3);
  88. JSONObject jsonObject2 = new JSONObject();
  89. jsonObject2.put("userId","1002");
  90. jsonObject2.put("name","贝塔");
  91. jsonArray.add(jsonObject2);
  92. return jsonArray;
  93. }
  94. public static void main(String[] args) {
  95. Map<String,String> map = JsonAndMapSortUtils.getMapData();
  96. JSONArray jsonArray = JsonAndMapSortUtils.getJsonArrayData();
  97. List afterSortMap = JsonAndMapSortUtils.mapByKeyToSort(map,new ASCIICaseInsensitiveComparator());
  98. JSONArray afterSortJsonArray_isAsc = JsonAndMapSortUtils.jsonArrayToSort(jsonArray,"userId",true);
  99. JSONArray afterSortJsonArray_noAsc = JsonAndMapSortUtils.jsonArrayToSort(jsonArray,"userId",false);
  100. System.out.println("map排序前:"+map);
  101. System.out.println("map排序后:"+afterSortMap+"\n");
  102. System.out.println("JsonArray排序前:"+jsonArray);
  103. System.out.println("JsonArray排序后==》升序:"+afterSortJsonArray_isAsc);
  104. System.out.println("JsonArray排序后==》降序:"+afterSortJsonArray_noAsc);
  105. }
  106. }

源码走读

整个源码调用链路如下图,简单来说过程就是:object拆分解析-新旧数据逐个对比-结果信息组装三个步骤

其他

原始代码中有些小bug,已修复。目前这个工具主要被我拿来用在了一个接口数据对比工具中,来检测迭代前后的接口协议数据变更,以完善迭代变更范围来确认测试范围。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/1014744
推荐阅读
相关标签
  

闽ICP备14008679号