JSON格式化与结构对比

说明

功能

  1. 格式化json字符串为最简格式,并标识值类型;

  2. 比对json字符串结构。

第三方依赖

  1. fastjson: 用于解析json、判断json值类型;

  2. springframework自带的字符串判断,可以不依赖该方法,改为自行实现;

  3. slf4j: 用于打印日志,可以不依赖该方法,改为其它方法。

json结构对比规则

  1. null与任何类型相等;

  2. 空对象{}与任何对象{}相等;

  3. 空数组[]与任何数组[]相等。


代码

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;

@Slf4j
public 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字符串
     * <p>
     * 说明:
     * 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 error
com.alibaba.fastjson.JSONException: unclosed jsonArray
	at 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"}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值