“镜” 寓意是凡事都有两面性,Json 对比也不例外!
因公司业务功能当中有一个履历的功能,它有多个版本的 JSON 数据需要对比出每个版本的不同差异节点并且将差异放置在一个新的 JSON 当中原有结构不能变动,差异节点使用数组对象的形式存储,前端点击标红即可显示多个版本的节点差异数据如下图
banner 履历查看// JSON One
{
"employee":
{
"id": "1212",
"fullName":"John Miles",
"age": 34,
"contact":
{
"email": "[email protected]",
"phone": "9999999999"
}
}
}
// Json Two
{
"employee":
{
"id": "1212",
"ae86": "12162",
"age": 34,
"fullName": "John Miles111",
"contact":
{
"email": "[email protected]",
"phone": "我是改了的",
"668": "999999991199"
}
}
}
可以看到
employee.ae86
是新增的。contact.668
也是新增的phone
字段是修改了的
// 获取差异的节点 使用数组对象表示
{
"employee/fullName/": [{
"old": "John Miles"
}, {
"new": "John Miles111"
}],
"employee/contact/phone/": [{
"old": "9999999999"
}, {
"new": "我是改了的"
}],
"employee/contact/668": [{
"new": "999999991199"
}],
"employee/ae86": [{
"new": "12162"
}]
}
// 将差异节点的数据覆盖上去
{
"employee" : {
"id" : "1212",
"fullName" : [ {
"old" : "John Miles"
}, {
"new" : "John Miles111"
} ],
"age" : 34,
"contact" : {
"email" : "[email protected]",
"phone" : [ {
"old" : "9999999999"
}, {
"new" : "我是改了的"
} ],
"668" : [ {
"new" : "999999991199"
} ]
},
"ae86" : [ {
"new" : "12162"
} ]
}
}
package com.yby6;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.lang3.ObjectUtils;
import java.io.IOException;
import java.util.*;
/**
* @author Yang Shuai
* Create By 2023/8/26
*/
public class JsonComparerUtils2 {
static ObjectMapper mapper = new ObjectMapper();
public static void main(String[] args) {
String s1 = "{ \n" +
" \"employee\":\n" +
" {\n" +
" \"id\": \"1212\",\n" +
" \"fullName\":\"John Miles\",\n" +
" \"age\": 34,\n" +
" \"contact\":\n" +
" {\n" +
" \"email\": \"[email protected]\",\n" +
" \"phone\": \"9999999999\"\n" +
" }\n" +
" }\n" +
"}";
String s2 = "{\n" +
" \"employee\":\n" +
" {\n" +
" \"id\": \"1212\",\n" +
" \"ae86\": \"12162\",\n" +
" \"age\": 34,\n" +
" \"fullName\": \"John Miles111\",\n" +
" \"contact\":\n" +
" {\n" +
" \"email\": \"[email protected]\",\n" +
" \"phone\": \"我是改了的\",\n" +
" \"668\": \"999999991199\"\n" +
" }\n" +
" }\n" +
"}";
try {
// 将json转Json节点树
JsonNode node1 = mapper.readTree(s1);
JsonNode node2 = mapper.readTree(s2);
List ignoreKey = new ArrayList<>();
// 获取两个JSON之间的差异
Map nodesDiff = getNodesDiff(node1, node2,
"", ignoreKey);
System.out.println(mapper.writeValueAsString(nodesDiff));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 得到节点差异
*
* @param node1 node1
* @param node2 node2
* @param path 路径
* @param ignoreKey 忽略关键
* @return {@link Map}<{@link String}, {@link Object}>
*/
private static Map getNodesDiff(JsonNode node1, JsonNode node2, String path, List ignoreKey) {
Map diff = new LinkedHashMap<>();
String[] split = path.split("/");
String filed = split[split.length - 1];
if (!node1.getNodeType().equals(node2.getNodeType())) {
addToMap(path, node1, node2, diff, "update");
} else {
switch (node1.getNodeType()) {
case OBJECT:
if (node1.isObject() && !node1.isEmpty()) {
for (Iterator it = node1.fieldNames(); it.hasNext(); ) {
String fieldName = it.next();
JsonNode childNode1 = node1.get(fieldName);
JsonNode childNode2 = node2.get(fieldName);
// 忽略指定字段不对比
if (ignoreKey.contains(fieldName)) {
continue;
}
if (childNode2 != null) {
Map nestedDiff = getNodesDiff(childNode1, childNode2, path + fieldName + "/", ignoreKey);
if (!nestedDiff.isEmpty()) {
diff.putAll(nestedDiff);
}
} else {
// 旧的存在新的则不存在表示删除
addToMap(path + fieldName, childNode1, childNode1, diff, "delete");
}
}
for (Iterator it = node2.fieldNames(); it.hasNext(); ) {
String fieldName = it.next();
if (ignoreKey.contains(fieldName)) {
continue;
}
// 如果旧的没有这个数据那么表示新增
if (node1.get(fieldName) == null) {
addToMap(path + fieldName, null, node2.get(fieldName), diff, "add");
}
}
}
break;
case ARRAY:
// 判断两个数组的长度不一样则需要将两个数组的长度补齐
if (node1.size() > 0 && node2.size() > 0 && node1.size() != node2.size()) {
try {
String m1 = mapper.writeValueAsString(node1);
String m2 = mapper.writeValueAsString(node2);
List list1 = mapper.readValue(m1, List.class);
List list2 = mapper.readValue(m2, List.class);
if (list1.size() > list2.size()) {
for (int i = list2.size(); i < list1.size(); i++) {
String o = mapper.writeValueAsString(list1.get(i));
JsonNode jsonNode = mapper.readTree(o);
// 清空的
clearNodeValues(jsonNode, ignoreKey);
// 将jsonNode2添加到jsonNode1中
((ArrayNode) node2).add(jsonNode);
}
} else {
for (int i = list1.size(); i < list2.size(); i++) {
String o = mapper.writeValueAsString(list2.get(i));
JsonNode jsonNode = mapper.readTree(o);
// 清空的
clearNodeValues(jsonNode, ignoreKey);
((ArrayNode) node1).add(jsonNode);
}
}
// 排序数组
List firstList = mapper.readValue(node1.traverse(), new TypeReference>() {
});
List secondList = mapper.readValue(node2.traverse(), new TypeReference>() {
});
// 补齐后递归对比
for (int i = 0; i < firstList.size(); i++) {
Map nestedDiff = getNodesDiff(firstList.get(i), secondList.get(i), path + "[" + i + "]/", ignoreKey);
if (!nestedDiff.isEmpty()) {
diff.putAll(nestedDiff);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
// 判断数组里面是不是对象
if (node1.size() > 0 && node1.get(0).getNodeType().equals(JsonNodeType.OBJECT)) {
for (int i = 0; i < node1.size(); i++) {
if (ignoreKey.contains(filed)) {
break;
}
Map nestedDiff = getNodesDiff(node1.get(i), node2.get(i), path + "[" + i + "]/", ignoreKey);
if (!nestedDiff.isEmpty()) {
diff.putAll(nestedDiff);
}
}
} else {
if (!node1.equals(node2)) {
if (ignoreKey.contains(filed)) {
break;
}
addToMap(path, node1, node2, diff, "update");
}
}
}
break;
case STRING:
case BOOLEAN:
case NUMBER:
if (ignoreKey.contains(filed)) {
break;
}
// 如果新的为空则为删除
if (ObjectUtils.isEmpty(node2)) {
addToMap(path, node1, node2, diff, "delete");
}
if (!node1.equals(node2)) {
addToMap(path, node1, node2, diff, "update");
}
break;
default:
throw new IllegalArgumentException("Unsupported JSON type:" + node1.getNodeType().name());
}
}
return diff;
}
/**
* 清空节点参数
*
* @param node 节点
*/
private static void clearNodeValues(JsonNode node, List ignoreKey) {
// 忽略部分清空
if (node.isObject()) {
ObjectNode objectNode = (ObjectNode) node;
objectNode.fields().forEachRemaining(entry -> {
if (!ignoreKey.contains(entry.getKey())) {
objectNode.replace(entry.getKey(), null);
}
});
} else if (node.isArray()) {
for (JsonNode childNode : node) {
clearNodeValues(childNode, ignoreKey);
}
}
}
/**
* 将两个json的差异添加到Map
*
* @param path 路径
* @param oldValue 旧值
* @param newValue 新值
* @param diff diff
*/
private static void addToMap(String path, JsonNode oldValue, JsonNode newValue, Map diff, String diffType) {
List
{
"employee/fullName/": [{
"new": "John Miles111",
"old": "John Miles",
"diffType": "update"
}],
"employee/contact/phone/": [{
"new": "我是改了的",
"old": "9999999999",
"diffType": "update"
}],
"employee/contact/668": [{
"new": "999999991199",
"old": "",
"diffType": "add"
}],
"employee/ae86": [{
"new": "12162",
"old": "",
"diffType": "add"
}]
}
这段代码是一个处理两个 JSON 节点之间差异的方法,以及一些辅助方法。下面我将解释每个方法的作用和代码逻辑:
getNodesDiff
方法该方法用于比较两个 JSON 节点(node1
和 node2
)之间的差异,包括子节点差异,并返回一个表示差异的 Map
。
private static Map getNodesDiff(JsonNode node1, JsonNode node2, String path, List ignoreKey)
diff
是一个用于存储差异的 LinkedHashMap
。 path
中的最后一个部分( field
)来确定节点的类型。 node1
和 node2
的节点类型是否相同,如果不同,将差异添加到 diff
中。 isValid
字段为 0 的情况。 diff
中。 clearNodeValues
方法这是一个辅助方法,用于清空节点的值,但保留节点结构。
private static void clearNodeValues(JsonNode node, List ignoreKey)
ignoreKey
中的字段。 addToMap
方法这是一个辅助方法,用于将差异信息添加到差异 Map
中。
private static void addToMap(String path, JsonNode oldValue, JsonNode newValue, Map diff, String diffType)
diff
中,包括路径 path
、旧值 oldValue
、新值 newValue
和差异类型 diffType
。 getContent
方法这是一个辅助方法,用于从 JsonNode
中提取内容。
private static Object getContent(JsonNode node)
JsonNode
的类型提取内容,可能是布尔值、整数、字符串、对象、数组或 null 值。 /**
* 将差异应用到指定的 JSON 字符串,并返回处理后的字符串。
*
* @param json 要应用差异的原始 JSON 字符串
* @param diff 差异内容,即 {@link #getNodesDiff} 返回的 Map 对象
* @return 经过差异处理后的 JSON 字符串
*/
public static String applyDiff(String json, Map diff) throws IOException {
JsonNode node = mapper.readTree(json);
for (Map.Entry entry : diff.entrySet()) {
String[] path = entry.getKey().split("/");
JsonNode parentNode = node;
for (int i = 0; i < path.length - 1; i++) {
// 如果是null则跳过
if (parentNode == null) {
continue;
}
// 如果该节点是数组那么解析一下
if (parentNode.isArray()) {
int index = getIndexFromPath(path[i]);
parentNode = parentNode.get(index);
} else {
parentNode = parentNode.get(path[i]);
}
}
// 如果拿到的父节点是null则跳过
if (parentNode == null) {
continue;
}
String propertyName = path[path.length - 1];
JsonNode childNode = parentNode.get(propertyName);
if (entry.getValue() == null) {
if (parentNode.isArray()) {
((ArrayNode) parentNode).remove(Integer.parseInt(propertyName.substring(1, propertyName.length() - 1)));
} else {
((ObjectNode) parentNode).remove(propertyName);
}
} else {
Object value = entry.getValue();
// 是否是数组
if (ArrayUtil.isArray(value)) {
// ArrayNode arrayNode = mapper.createArrayNode(); // 新建一个空的数组节点
// arrayNode.addPOJO(value);
ObjectMapper objectMapper = new ObjectMapper();
JsonNode arrayNode = objectMapper.valueToTree(value);
// Object[] arr = (Object[]) value;
// for (Object item : arr) {
// if (item != null) {
// // 将数组元素依次加入新建的数组节点中,不需要处理逗号问题
// arrayNode.addPOJO(item);
// }
// }
if (childNode != null && !childNode.isMissingNode()) { // 已经存在该属性,需要替换
((ObjectNode) parentNode).replace(propertyName, arrayNode);
} else { // 不存在该属性,直接应用差异
// 如果父节点是数组,在数组末尾添加新元素
// 如果父节点是对象,在该对象中添加新属性,值为空
if (parentNode.isArray()) {
int position = 0;
if (StringUtils.isNotBlank(propertyName)) {
position = Integer.parseInt(propertyName.substring(1, propertyName.length() - 1));
}
while (position > parentNode.size()) {
((ArrayNode) parentNode).add(mapper.createObjectNode().put("", ""));
}
((ArrayNode) parentNode).add(arrayNode);
} else {
((ObjectNode) parentNode).set(propertyName, arrayNode);
}
}
} else {
String newValue = entry.getValue().toString();
if (childNode == null || childNode.isNull() || childNode.isMissingNode()) {
if (parentNode.isArray()) { // 如果父节点是数组,在数组末尾添加新元素
((ArrayNode) parentNode).add(mapper.createObjectNode().put(propertyName, ""));
} else { // 如果父节点是对象,在该对象中添加新属性,值为空
((ObjectNode) parentNode).put(propertyName, "");
}
childNode = parentNode.get(propertyName);
}
if (childNode.isValueNode()) {
if (childNode.isBoolean()) {
((ObjectNode) parentNode).put(propertyName, Boolean.parseBoolean(newValue));
} else if (childNode.isIntegralNumber()) {
((ObjectNode) parentNode).put(propertyName, newValue);
} else if (childNode.isFloatingPointNumber()) {
((ObjectNode) parentNode).put(propertyName, Double.parseDouble(newValue));
} else if (childNode.isTextual()) {
try {
((ObjectNode) parentNode).put(propertyName, newValue.substring(1, newValue.length() - 1)); // 去掉 JSON 字符串外层的双引号
} catch (Exception e) {
((ObjectNode) parentNode).put(propertyName, newValue);
}
}
} else {
((ObjectNode) parentNode).set(propertyName, mapper.readTree(newValue));
}
}
}
}
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
}
/**
* 移除方括号并将剩余字符串解析为整数索引
*
* @param path 路径
* @return int
*/
private static int getIndexFromPath(String path) {
return Integer.parseInt(path.substring(1, path.length() - 1));
}
{
"employee": {
"id": "1212",
"ae86": [{
"new": "12162",
"old": "",
"diffType": "add"
}],
"age": 34,
"fullName": [{
"new": "John Miles111",
"old": "John Miles",
"diffType": "update"
}],
"contact": {
"email": "[email protected]",
"phone": [{
"new": "我是改了的",
"old": "9999999999",
"diffType": "update"
}],
"668": [{
"new": "999999991199",
"old": "",
"diffType": "add"
}]
}
}
}
applyDiff
方法该方法将差异应用到指定的 JSON 字符串,并返回处理后的字符串。它接受一个原始的 JSON 字符串和一个差异的 Map,通常是从 getNodesDiff
方法获取的。
public static String applyDiff(String json, Map diff) throws IOException
mapper
将输入的 JSON 字符串 json
解析为一个 JsonNode
对象。 - 如果值为 null,它会从 JSON 结构中移除节点。如果父节点是数组,则移除指定索引处的元素;否则,从对象中移除指定属性。
- 如果值不为 null,它会检查值是否为数组。如果是数组,它会创建一个新的 JSON 数组节点,并根据属性是否已存在,要么替换要么添加到父节点中。如果值不是数组,则根据其类型(布尔值、数字、字符串或 JSON 对象)更新 JSON 结构中的属性。
mapper
将修改后的 JsonNode
转换回 JSON 字符串,并返回结果的 JSON 字符串。 getIndexFromPath
方法这是一个私有的实用方法,用于移除字符串中的方括号,并将剩余的字符串解析为整数索引。
private static int getIndexFromPath(String path)
path
字符串作为输入。 path
字符串的首尾字符(假设它们是方括号),然后将剩余的子串解析为整数索引。 本期结束咱们下次再见 ~
,关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~
【选题思路】
基于两串不同的 JSON 数据进行对比出来差异再将差异应用到最新的 Json 字符串当中.
【写作提纲】
一、前言
因公司业务功能当中有一个履历的功能,它有多个版本的 JSON 数据需要对比出每个版本的不同差异节点并且将差异放置在一个新的 JSON 当中原有结构不能变动,差异节点使用数组对象的形式存储,前端点击标红即可显示多个版本的节点差异数据
二、示例
介绍两个 Json 的差异对比效果
三、实现
先得到两个 Json 的差异节点集合、接着在最新的 Json 中转换 json 节点对象进行判断每个节点的字段是否符合则插入到对应的字段当中!
本文由 mdnice 多平台发布