目錄
背景
說明
源碼
源碼走讀
其他
背景
? ? ? ? 之前有類似接口diff對比,數(shù)據(jù)對比的測試需求,涉及到j(luò)son格式的數(shù)據(jù)對比,調(diào)研了幾個大神們分享的代碼,選了一個最符合自己需求的研究了下。(可惜原文鏈接找不到了,原始作者看到了可以私信我下)
說明
這個對比方法,支持JsonObject和JsonArray類型的數(shù)據(jù)對比,支持:
- 深度的對比:list變化(個數(shù)、內(nèi)容)、層級結(jié)構(gòu)變化
- 字段的對比:新增、修改、刪除數(shù)據(jù)可察覺,能找到對應(yīng)的舊數(shù)據(jù)
- 支持特定字段忽略對比
輸出的對比結(jié)果格式為:
源碼分為JsonCompareUtils, JsonAndMapSortUtils兩個類,對比入口是compareTwoJson方法
核心邏輯在JsonCompareUtils類中,JsonAndMapSortUtils主要做過程中的數(shù)據(jù)排序功能,相對獨立。
源碼
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 {
//標(biāo)志位:對json報文中含有JsonArray類型的數(shù)據(jù)是否進(jìn)行排序
private static boolean isSort;
private Map<String, Object> oldJsonToMap = new LinkedHashMap<>();
private Map<String, Object> newJsonToMap = new LinkedHashMap<>();
//每一個實體里的排序字段
private static Map<String, String> filedNameMap = new HashMap<>();
static {
filedNameMap.put("dogs", "dogNo");
filedNameMap.put("cats", "catNo");
}
//可以跳過比對的字段
// private static String[] skipCompareFiledNameMap = {"dogAge", "catAge", "catName"};
private static String[] skipCompareFiledNameMap = {"key3"};
/**
* 兩json報文比對入口
*
* @param oldJsonStr
* @param newJsonStr
* @return
*/
public String compareTwoJson(String oldJsonStr, String newJsonStr) {
/**
* 遞歸遍歷json對象所有的key-value,以map形式的path:value進(jìn)行存儲
* 然后對兩個map進(jìn)行比較
*/
convertJsonToMap(JSON.parseObject(oldJsonStr), "", false);
convertJsonToMap(JSON.parseObject(newJsonStr), "", true);
//獲取比較結(jié)果
Map<String, Object> differenceMap = compareTwoMaps(oldJsonToMap, newJsonToMap);
String diffJsonResult = convertMapToJson(differenceMap);
return diffJsonResult;
}
/**
* 將json數(shù)據(jù)轉(zhuǎn)換為map存儲--用于后續(xù)比較map
*
* @param json
* @param root
* @param isNew 區(qū)別新舊報文
*/
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進(jìn)行排序
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);
});
}
}
/**
* 封裝json轉(zhuǎn)map后的數(shù)據(jù)
*
* @param value
* @param newRoot
* @param isNew 區(qū)別新舊json
*/
public void fillInResultMap(Object value, String newRoot, boolean isNew) {
if (value instanceof JSONObject || value instanceof JSONArray) {
convertJsonToMap(value, newRoot, isNew);
} else {
//設(shè)置跳過比對的字段,直接不裝入map
boolean check = ArrayUtils.contains(JsonCompareUtils.skipCompareFiledNameMap, newRoot);
if (!check){
if (!isNew) {
oldJsonToMap.put(newRoot, value);
} else {
newJsonToMap.put(newRoot, value);
}
}
}
}
/**
* 比較兩個map,將不同的數(shù)據(jù)以map形式存儲并返回
*
* @param oldJsonMap
* @param newJsonMap
* @return
*/
private Map<String, Object> compareTwoMaps(Map<String, Object> oldJsonMap, Map<String, Object> newJsonMap) {
//1.將newJsonMap的不同數(shù)據(jù)裝進(jìn)oldJsonMap,同時刪除oldJsonMap中與newJsonMap相同的數(shù)據(jù)
newJsonMap.forEach((k, v) -> {
Map<String, Object> 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.統(tǒng)一oldJsonMap中newMap不存在的數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu),便于解析
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<String, Object> differenceMap = new HashMap<>();
differenceMap.put("oldValue", v);
differenceMap.put("newValue", "no exists " + k);
oldJsonMap.put(k, differenceMap);
}
});
return oldJsonMap;
}
/**
* 將已經(jīng)找出不同數(shù)據(jù)的map根據(jù)key的層級結(jié)構(gòu)封裝成json返回
*
* @param map
* @return
*/
private String convertMapToJson(Map<String, Object> map) {
JSONObject resultJSONObject = new JSONObject();
for (Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Object> item = it.next();
String key = item.getKey();
Object value = item.getValue();
String[] paths = key.split("\\.");
int i = 0;
Object remarkObject = null;//用於深度標(biāo)識對象
int indexAll = paths.length - 1;
while (i <= paths.length - 1) {
String path = paths[i];
if (i == 0) {
//初始化對象標(biāo)識
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========測試復(fù)雜json的比對============");
}
/**
* 測試類
static class JsonCompareTest {
public static void compareTest() {
String oldJson = MakeJsonCompareDatas.getJsonDataOldStr();
String newJson = MakeJsonCompareDatas.getJsonDataNewStr();
//對json報文中含有JsonArray類型的數(shù)據(jù)是否進(jìn)行排序
JsonCompareUtils.isSort = true;
String compareResult = new JsonCompareUtils().compareTwoJson(oldJson, newJson);
System.out.println("oldJson==>" + oldJson);
System.out.println("newJson==>" + newJson);
System.out.println("\nisSort==" + isSort + "-->compareResult==>\n" + compareResult);
}
}
*/
}
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 {
/**
* map排序
* @param map
* @param keySort
* @param <k>
* @param <v>
* @return
*/
public static <k,v> List mapByKeyToSort(Map<k,v> map , final Comparator keySort){
List<Map.Entry<k,v>> entryList = new ArrayList<Map.Entry<k, v>>(map.entrySet());
Collections.sort(entryList, new Comparator<Map.Entry<k, v>>() {
public int compare(Map.Entry<k, v> o1, Map.Entry<k, v> o2) {
return keySort.compare(o1.getKey(),o2.getKey());
}
});
//return (Map<k,v>)entryList.stream().collect(Collectors.toMap(Map.Entry<k,v>::getKey, Function.identity(), (key1, key2) -> key2));
Map<String,String> afterToSortMap = new HashMap<>();
/*for (Map.Entry<k,v> m : entryList){
System.out.println(m.getKey()+"===>"+m.getValue());
}*/
System.out.println("排序=====");
entryList.forEach(m->{
System.out.println(m.getKey()+"===>"+m.getValue());
});
return entryList;
}
/**
* JSONArray排序
* @param jsonArray
* @param fildName
* @param isAsc
* @return
*/
public static JSONArray jsonArrayToSort(JSONArray jsonArray,final String fildName,final boolean isAsc){
JSONArray afterSortJsonArray = new JSONArray();
List<JSONObject> objectList = new ArrayList<JSONObject>();
jsonArray.forEach(obj ->{
objectList.add((JSONObject)obj);
});
Collections.sort(objectList, new Comparator<JSONObject>() {
@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;
}
/**
*準(zhǔn)備map測試數(shù)據(jù)
*/
public static Map<String,String> getMapData(){
LinkedHashMap<String,String> map = new LinkedHashMap<>();
map.put("key1","麥兜");
map.put("key3","貝塔");
map.put("key5","酥妮");
map.put("key2","小H");
map.put("key4","小O");
return map;
}
/**
*準(zhǔn)備json測試數(shù)據(jù)
*/
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<String,String> 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);
}
}
源碼走讀
整個源碼調(diào)用鏈路如下圖,簡單來說過程就是:object拆分解析-新舊數(shù)據(jù)逐個對比-結(jié)果信息組裝三個步驟
文章來源:http://www.zghlxwxcb.cn/news/detail-539396.html
其他
原始代碼中有些小bug,已修復(fù)。目前這個工具主要被我拿來用在了一個接口數(shù)據(jù)對比工具中,來檢測迭代前后的接口協(xié)議數(shù)據(jù)變更,以完善迭代變更范圍來確認(rèn)測試范圍文章來源地址http://www.zghlxwxcb.cn/news/detail-539396.html
到了這里,關(guān)于JSON數(shù)據(jù)的差異對比工具(Java版)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!