作者:独钓寒江雪
一剑一酒一江湖, 便是此生
心中有图,何必点灯。装载请注明出处
最近休假在家。小无聊。一直在实践
Quarkus如何实际运用到我们的生产上。真是收益匪浅。 后续更新个Quarkus的专栏。写的Flowable也继续更新下。前段时间一直在忙着几个项目和自身Framework的升级,空下来好好搞一搞。
闲话不说,进入主题。写这个文章呢,主要是前几天一个朋友,问我如何比较一个复杂的json字符串的一致性,看样子应该是内部做
升级比对用的。刚听到的时候以为很简单啊,上代码。 如下2段json比较。
{
"group": "testGroup",
"school": "",
"students": [
{
"id": 1,
"name": "abc",
"age": 23,
"activeFlag": false
},
{
"id": 2,
"name": "efg",
"age": 26,
"activeFlag": true
},
{
"id": 3,
"name": "rst",
"age": 26,
"activeFlag": false
}
]
}
{
"group": "testGroup",
"school": "",
"students": [
{
"id": 2,
"name": "efg",
"age": 26,
"activeFlag": true
},
{
"id": 1,
"name": "abc",
"age": 23,
"activeFlag": false
},
{
"id": 3,
"name": "rst",
"age": 26,
"activeFlag": false
}
]
}
初版天真代码
脑子里第一思路就是
Jackson转成JsonNode直接equals一下不就好了。但是事实却是完全不一样。
public class Test {
public static void main(String[] args) {
try {
String jsonPath1 = Test.class.getResource("/json1").getPath();
String jsonPath2 = Test.class.getResource("/json2").getPath();
File file1 = new File(jsonPath1);
File file2 = new File(jsonPath2);
List<String> json1 = Files.readLines(file1, Charset.defaultCharset());
List<String> json2 = Files.readLines(file2, Charset.defaultCharset());
String jsonStr1 = StringUtils.join(json1, "");
String jsonStr2 = StringUtils.join(json2, "");
ObjectMapper objectMapper = getObjectMapper();
JsonNode doc1 = objectMapper.readTree(jsonStr1);
JsonNode doc2 = objectMapper.readTree(jsonStr2);
System.out.println(doc1.equals(doc2));
} catch (Exception e) {
e.printStackTrace();
}
}
public static ObjectMapper getObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.enable(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME);
objectMapper.setTimeZone(TimeZone.getDefault());
return objectMapper;
}
}
结果呢。直接输出了
false。从我一开始的理解上来说呢,不应该啊。想着跟顺序有关。就去看了下源码,果然发现是和顺序有关系的。
看到
equals方法,可以重写个Comparator进去。进行比较,结果呢,还是太天真了。代码被我删除了。大家看上面的源码也就知道了,Comparator只对ValueNode的子类生效如BooleanNode,TextNode,NumberNode等基础节点的比较进行接管。 此处真想喷一下坑爹的百度和一些作者,太基础了,每次都是一些误导人的博文。 很多写了一堆就很单纯的一个array的json,然后去处理他的顺序,但是很多时候上,我们都是对象包含数组的,而非一个简单的数组。下面这篇stackoverflow上的讨论和我自己本身测试也证实了说法。当Arraynode inside of ObjectNode的时候,Comprator是失效的。
最终代码
一开始我还是不放弃,去
github上找相应的issue看有没人处理,没人反馈的话,准备提个Issue的(现在为止提了不少开源的PR。此处装个B),后来发现了这么一个对话,作者认为顺序对于数据来说是至关重要的,所以不打算进行此处的处理。
如下代码,主要就是对ObjectNode做递归处理,接管掉
ArrayNode的比较。进行不关心顺序的比较。其他还是交给本身的equals进行比较。
public class Test {
public static void main(String[] args) {
try {
String jsonPath1 = Test.class.getResource("/json1").getPath();
String jsonPath2 = Test.class.getResource("/json2").getPath();
File file1 = new File(jsonPath1);
File file2 = new File(jsonPath2);
List<String> json1 = Files.readLines(file1, Charset.defaultCharset());
List<String> json2 = Files.readLines(file2, Charset.defaultCharset());
String jsonStr1 = StringUtils.join(json1, "");
String jsonStr2 = StringUtils.join(json2, "");
ObjectMapper objectMapper = getObjectMapper();
JsonNode doc1 = objectMapper.readTree(jsonStr1);
JsonNode doc2 = objectMapper.readTree(jsonStr2);
System.out.println(equalsJsonNode(doc1, doc2));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 比较2个arrayNode是否相等
* 和Jackson本身携带的ArrayNode.equals()方法比,此处不考虑arrayNode里元素的顺序,只要互相包含即可。
*
* @param arrayNode
* @param compareArrayNode
* @return
*/
private static boolean compareArrayNode(ArrayNode arrayNode, ArrayNode compareArrayNode) {
boolean compareFlag = true;
for(int i = 0; i < arrayNode.size(); i++) {
boolean compareNodeFlag = false;
JsonNode o1Node = arrayNode.get(i);
for(int j = 0; j < compareArrayNode.size(); j++) {
JsonNode o2Node = compareArrayNode.get(j);
if (equalsJsonNode(o1Node, o2Node)) {
compareNodeFlag = true;
break;
}
}
if (!compareNodeFlag) {
compareFlag = false;
break;
}
}
return compareFlag;
}
private static boolean equalsJsonNode(JsonNode node, JsonNode compareNode) {
return equalsJsonNode(null, node, compareNode);
}
private static boolean equalsJsonNode(Comparator comparator, JsonNode node, JsonNode compareNode) {
boolean compareFlag = true;
if (!node.getClass().equals(compareNode.getClass())) {
return false;
}
if (node.size() != compareNode.size()) {
return false;
}
if (node instanceof ArrayNode) {
compareFlag = compareArrayNode((ArrayNode) node, (ArrayNode) compareNode);
} else if (node instanceof ValueNode) {
if (comparator != null) {
compareFlag = node.equals(comparator, compareNode);
} else {
compareFlag = node.equals(compareNode);
}
} else if (node instanceof ObjectNode) {
Iterator<Map.Entry<String, JsonNode>> nodeIterator = node.fields();
while (nodeIterator.hasNext() && compareFlag) {
Map.Entry<String, JsonNode> entry = nodeIterator.next();
JsonNode elementNode = entry.getValue();
JsonNode compareElementNode = compareNode.get(entry.getKey());
compareFlag = equalsJsonNode(elementNode, compareElementNode);
}
}
return compareFlag;
}
public static ObjectMapper getObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.enable(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME);
objectMapper.setTimeZone(TimeZone.getDefault());
return objectMapper;
}
}
这种比较就根据各自业务来比较了,看各位看官的比较上是否需要考虑其顺序性相关进行比较了。