JSON格式化与结构对比
说明
功能
第三方依赖
-
fastjson: 用于解析json、判断json值类型;
-
springframework自带的字符串判断,可以不依赖该方法,改为自行实现;
-
slf4j: 用于打印日志,可以不依赖该方法,改为其它方法。
json结构对比规则
-
null与任何类型相等;
-
空对象{}与任何对象{}相等;
-
空数组[]与任何数组[]相等。
代码
JSON工具类
import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONException;import com.alibaba.fastjson.JSONObject;import lombok.extern.slf4j.Slf4j;import org.springframework.util.StringUtils;import java.util.Map;import java.util.Set;@Slf4jpublic class JSONUtil { private static final String NULL = \"Null\"; private static final String OBJECT = \"Object\"; private static final String ARRAY = \"Array\"; private static final String EQUAL = \"=\"; private static final String ADD = \"+\"; private static final String DELETE = \"-\"; private static final String MODIFY = \"%s -> %s\"; private static final String CAN_NOT_COMPARE = \"not json, can\'t compare!\"; private static final String CAN_NOT_FORMAT = \"not json, can\'t format!\"; /** * 格式化json字符串为最简格式,并标识值类型 * * @param json json字符串 * @return json结构 */ public static String format(String json) { if (!StringUtils.hasText(json)) { return CAN_NOT_FORMAT; } try { Object formatJson = null; if (json.trim().startsWith(\"{\")) { formatJson = JSON.parseObject(json); formatJSONObject((JSONObject) formatJson); } else if (json.trim().startsWith(\"[\")) { formatJson = JSON.parseArray(json); formatJSONArray((JSONArray) formatJson); } return JSON.toJSONString(formatJson); } catch (JSONException exception) { log.warn(\"compareStruct error\", exception); } return CAN_NOT_FORMAT; } private static Object formatJSONObject(JSONObject json) { if (json == null) { return null; } if (json.isEmpty()) { return OBJECT; } for (Map.Entry<String, Object> entry : json.entrySet()) { Object value = entry.getValue(); if (value instanceof JSONObject) { entry.setValue(formatJSONObject((JSONObject) value)); } else if (value instanceof JSONArray) { entry.setValue(formatJSONArray((JSONArray) value)); } else { entry.setValue(getTypeName(value)); } } return json; } private static Object formatJSONArray(JSONArray json) { if (json == null) { return null; } if (json.isEmpty()) { return ARRAY; } Object typical = json.get(0); if (typical instanceof JSONObject) { typical = formatJSONObject((JSONObject) typical); } else if (typical instanceof JSONArray) { typical = formatJSONArray((JSONArray) typical); } else { typical = getTypeName(typical); } json.clear(); json.add(typical); return json; } /** * 比对json字符串 * * 说明: * 1、null与任何类型相等; * 2、{}与任何{}相等; * 3、[]与任何[]相等; * * @param oldJson 旧json字符串 * @param newJson 新json字符串 * @return 新旧json字符串差异 */
public static Object compareStruct(String oldJson, String newJson) { if (!StringUtils.hasText(oldJson) || !StringUtils.hasText(newJson)) { return CAN_NOT_COMPARE; } try { if (oldJson.trim().startsWith(\"{\")) { if (newJson.trim().startsWith(\"{\")) { JSONObject oldJsonObject = JSON.parseObject(oldJson); JSONObject newJsonObject = JSON.parseObject(newJson); if (oldJsonObject == null || newJsonObject == null || oldJsonObject.isEmpty() || newJsonObject.isEmpty()) { // null与任何类型相等;{}与任何{}相等 return EQUAL; } JSONObject result = new JSONObject(); compareJSONObject(oldJsonObject, newJsonObject, result); return result; } else { return String.format(MODIFY, OBJECT, ARRAY); } } else if (oldJson.trim().startsWith(\"[\")) { if (newJson.trim().startsWith(\"[\")) { JSONArray oldJsonArray = JSON.parseArray(oldJson); JSONArray newJsonArray = JSON.parseArray(newJson); if (oldJsonArray == null || newJsonArray == null || oldJsonArray.isEmpty() || newJsonArray.isEmpty()) { // null与任何类型相等;[]与任何[]相等 return EQUAL; } JSONArray result = new JSONArray(); compareJSONArray(oldJsonArray, newJsonArray, result); if (result.size() == 1 && EQUAL.equals(result.get(0))) { return EQUAL; } else { return result; } } else { return String.format(MODIFY, ARRAY, OBJECT); } } } catch (JSONException exception) { log.warn(\"compareStruct error\", exception); } return CAN_NOT_COMPARE; } private static void compareJSONObject(JSONObject oldJson, JSONObject newJson, JSONObject result) { if (oldJson == null || newJson == null) { // 该空校验可以去掉,调用的地方已经校验过了 return; } Set<String> oldKeySet = oldJson.keySet(); Set<String> newKeySet = newJson.keySet(); for (Map.Entry<String, Object> entry : newJson.entrySet()) { if (!oldKeySet.contains(entry.getKey())) { result.put(entry.getKey(), ADD); continue; } Object newValue = entry.getValue(); Object oldValue = oldJson.get(entry.getKey()); if (oldValue == null || newValue == null) { result.put(entry.getKey(), EQUAL); continue; } if (!newValue.getClass().equals(oldValue.getClass())) { result.put(entry.getKey(), String.format(MODIFY, getTypeName(oldValue), getTypeName(newValue))); continue; } if (newValue instanceof JSONObject) { JSONObject oldValueJson = (JSONObject) oldValue; JSONObject newValueJson = (JSONObject) newValue; if (oldValueJson.isEmpty() || newValueJson.isEmpty()) { result.put(entry.getKey(), EQUAL); continue; } JSONObject subResult = new JSONObject(); result.put(entry.getKey(), subResult); compareJSONObject(oldValueJson, newValueJson, subResult); } else if (newValue instanceof JSONArray) { JSONArray subResult = new JSONArray(); compareJSONArray((JSONArray) oldValue, (JSONArray) newValue, subResult); if (subResult.size() == 1 && EQUAL.equals(subResult.get(0))) { // 嵌套数组,如果内层结构相同,那么本层结构也相同 result.put(entry.getKey(), EQUAL); } else { result.put(entry.getKey(), subResult); } } else { result.put(entry.getKey(), EQUAL); } } for (Map.Entry<String, Object> entry : oldJson.entrySet()) { if (!newKeySet.contains(entry.getKey())) { result.put(entry.getKey(), DELETE); } } } private static void compareJSONArray(JSONArray oldJson, JSONArray newJson, JSONArray result) { if (oldJson == null || newJson == null || oldJson.isEmpty() || newJson.isEmpty()) { result.add(EQUAL); return; } // 取第一个元素对比 Object oldTypical = oldJson.get(0); Object newTypical = newJson.get(0); if (oldTypical == null || newTypical == null) { result.add(EQUAL); return; } if (!newTypical.getClass().equals(oldTypical.getClass())) { result.add(String.format(MODIFY, getTypeName(oldTypical), getTypeName(newTypical))); return; } if (newTypical instanceof JSONObject) { JSONObject subResult = new JSONObject(); result.add(subResult); compareJSONObject((JSONObject) oldTypical, (JSONObject) newTypical, subResult); } else if (newTypical instanceof JSONArray) { JSONArray subResult = new JSONArray(); compareJSONArray((JSONArray) oldTypical, (JSONArray) newTypical, subResult); if (subResult.size() == 1 && EQUAL.equals(subResult.get(0))) { // 嵌套数组,如果内层结构相同,那么本层结构也相同 result.add(EQUAL); } else { result.add(subResult); } } else { result.add(EQUAL); } } private static Object getTypeName(Object obj) { if (obj == null) { return NULL; } if (obj instanceof JSONObject) { return OBJECT; } if (obj instanceof JSONArray) { return ARRAY; } return obj.getClass().getSimpleName(); }}
测试
测试代码
import com.alibaba.fastjson.JSON;import com.example.study.util.JSONUtil;public class Test { public static void main(String[] args) { System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"{}\", \"{}\"))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"[]\", \"[]\"))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"{}\", \"[]\"))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"[]\", \"{}\"))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"[]\", null))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(null, \"{}\"))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"[]\", \"\"))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"\", \"{}\"))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"[[]]\", \"[[]]\"))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"[[2]]\", \"[[1]]\"))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"[[]]\", \"[[[]]]\"))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"[[]]\", \"[[[1]]]\"))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"[[1]]\", \"[[[1]]]\"))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"[\", \"[[[1]]]\"))); String oldJsonObj = \"{\\\"id\\\":1,\\\"isMan\\\":true,\\\"name\\\":\\\"testName\\\",\\\"testNull\\\":null,\\\"testEmptyObject\\\":{},\" + \"\\\"testObject\\\":{\\\"id\\\":1,\\\"arr\\\":[]},\\\"testEmptyArr\\\":[],\\\"testIntegerArr\\\":[1,2,3],\\\"testObjectArr\\\":[{\\\"id\\\":1,\\\"arr\\\":[\\\"a\\\"]},{\\\"id\\\":2,\\\"arr\\\":[\\\"b\\\"]}],\\\"testNestingArr\\\":[[[1,2,3]]],\\\"testNestingArrEqual\\\":[[[\\\"1\\\"]]]}\"; String newJsonObj = \"{\\\"id\\\":\\\"1\\\",\\\"isMan\\\":true,\\\"name\\\":\\\"testName\\\",\\\"testNull\\\":{},\" + \"\\\"testEmptyObject\\\":{},\\\"testObject\\\":{\\\"id\\\":1,\\\"testAdd\\\":\\\"add\\\",\\\"arr\\\":[]},\\\"testEmptyArr\\\":[],\\\"testIntegerArr\\\":[1,2,3],\\\"testObjectArr\\\":[{\\\"arr\\\":[\\\"a\\\",\\\"b\\\",\\\"c\\\",\\\"d\\\"]},{\\\"arr\\\":[\\\"b\\\",\\\"b\\\",\\\"c\\\",\\\"d\\\"]}],\\\"testNestingArr\\\":[[[\\\"a\\\",\\\"b\\\",\\\"c\\\",\\\"d\\\"]]],\\\"testNestingArrEqual\\\":[[[\\\"a\\\",\\\"b\\\",\\\"c\\\",\\\"d\\\"]]]}\"; System.out.println(JSON.toJSONString(JSONUtil.compareStruct(oldJsonObj, newJsonObj))); System.out.println(JSON.toJSONString(JSONUtil.compareStruct(\"[\" + oldJsonObj + \"]\", \"[\" + newJsonObj + \"]\"))); oldJsonObj = \"{\\\"id\\\":1,\\\"isMan\\\":true,\\\"name\\\":\\\"testName\\\",\\\"testNull\\\":null,\\\"testEmptyObject\\\":{},\" + \"\\\"testObject\\\":{\\\"id\\\":1,\\\"arr\\\":[]},\\\"testEmptyArr\\\":[],\\\"testIntegerArr\\\":[1,2,3],\\\"testObjectArr\\\":[{\\\"id\\\":1,\\\"arr\\\":[\\\"a\\\"]},{\\\"id\\\":2,\\\"arr\\\":[\\\"b\\\"]}],\\\"testNestingArr\\\":[[[1,2,3]]],\\\"testNestingArrEqual\\\":[[[\\\"1\\\"]]],\\\"nullArr0\\\":[null],\\\"nullArr1\\\":[[[null]]],\\\"emptyArr0\\\":[],\\\"emptyArr1\\\":[[[]]],\\\"nestingArr0\\\":[[[1,2,3]]],\\\"nestingArr1\\\":[[[\\\"a\\\"]]],\\\"nestingArr2\\\":[[[{\\\"id\\\":2,\\\"arr\\\":[\\\"b\\\"],\\\"arr2\\\":[[]]}]]]}\"; System.out.println(JSON.toJSONString(JSONUtil.compareStruct(oldJsonObj, newJsonObj))); System.out.println(JSONUtil.format(oldJsonObj)); }}
输出
\"=\"\"=\"\"Object -> Array\"\"Array -> Object\"\"not json, can\'t compare!\"\"not json, can\'t compare!\"\"not json, can\'t compare!\"\"not json, can\'t compare!\"\"=\"\"=\"\"=\"\"=\"[[\"Integer -> Array\"]]18:08:25.205 [main] WARN com.example.study.util.JSONUtil -- compareStruct errorcom.alibaba.fastjson.JSONException: unclosed jsonArrayat com.alibaba.fastjson.parser.DefaultJSONParser.parseArray(DefaultJSONParser.java:1266)at com.alibaba.fastjson.parser.DefaultJSONParser.parseArray(DefaultJSONParser.java:1169)at com.alibaba.fastjson.JSON.parseArray(JSON.java:612)at com.alibaba.fastjson.JSON.parseArray(JSON.java:592)at com.example.study.util.JSONUtil.compareStruct(JSONUtil.java:124)at com.example.study.controller.Test.main(Test.java:21)\"not json, can\'t compare!\"{\"testObjectArr\":[{\"arr\":\"=\",\"id\":\"-\"}],\"testObject\":{\"arr\":\"=\",\"testAdd\":\"+\",\"id\":\"=\"},\"name\":\"=\",\"testIntegerArr\":\"=\",\"id\":\"Integer -> String\",\"testNull\":\"=\",\"testEmptyObject\":\"=\",\"testNestingArrEqual\":\"=\",\"isMan\":\"=\",\"testEmptyArr\":\"=\",\"testNestingArr\":[[[\"Integer -> String\"]]]}[{\"testObjectArr\":[{\"arr\":\"=\",\"id\":\"-\"}],\"testObject\":{\"arr\":\"=\",\"testAdd\":\"+\",\"id\":\"=\"},\"name\":\"=\",\"testIntegerArr\":\"=\",\"id\":\"Integer -> String\",\"testNull\":\"=\",\"testEmptyObject\":\"=\",\"testNestingArrEqual\":\"=\",\"isMan\":\"=\",\"testEmptyArr\":\"=\",\"testNestingArr\":[[[\"Integer -> String\"]]]}]{\"nullArr0\":\"-\",\"nullArr1\":\"-\",\"testIntegerArr\":\"=\",\"emptyArr0\":\"-\",\"nestingArr1\":\"-\",\"emptyArr1\":\"-\",\"nestingArr2\":\"-\",\"testNestingArrEqual\":\"=\",\"nestingArr0\":\"-\",\"isMan\":\"=\",\"testEmptyArr\":\"=\",\"testNestingArr\":[[[\"Integer -> String\"]]],\"testObjectArr\":[{\"arr\":\"=\",\"id\":\"-\"}],\"testObject\":{\"arr\":\"=\",\"testAdd\":\"+\",\"id\":\"=\"},\"name\":\"=\",\"id\":\"Integer -> String\",\"testNull\":\"=\",\"testEmptyObject\":\"=\"}{\"nullArr0\":[\"Null\"],\"nullArr1\":[[[\"Null\"]]],\"testIntegerArr\":[\"Integer\"],\"emptyArr0\":\"Array\",\"nestingArr1\":[[[\"String\"]]],\"emptyArr1\":[[\"Array\"]],\"nestingArr2\":[[[{\"arr\":[\"String\"],\"id\":\"Integer\",\"arr2\":[\"Array\"]}]]],\"testNestingArrEqual\":[[[\"String\"]]],\"nestingArr0\":[[[\"Integer\"]]],\"isMan\":\"Boolean\",\"testEmptyArr\":\"Array\",\"testNestingArr\":[[[\"Integer\"]]],\"testObjectArr\":[{\"arr\":[\"String\"],\"id\":\"Integer\"}],\"testObject\":{\"arr\":\"Array\",\"id\":\"Integer\"},\"name\":\"String\",\"id\":\"Integer\",\"testNull\":\"Null\",\"testEmptyObject\":\"Object\"}