如何比较JSON文档并返回与Jackson或Gson的差异?
我正在使用spring-boot来开发后端服务。 有一种方案可以比较2-beans(一个是DB对象,另一个是客户端请求的对象)并返回“new element”,“modified element”,如果没有更改,则返回false。 2豆的格式如下
"sampleList":{ "timeStamp":"Thu, 21 Jun 2018 07:57:00 +0000", "id":"5b19441ac9e77c000189b991", "sampleListTypeId":"type001", "friendlyName":"sample", "contacts":[ { "id":"5b05329cc9e77c000189b950", "priorityOrder":1, "name":"sample1", "relation":"Friend", "sampleInfo":{ "countryCode":"91", "numberType":"MOBILE", "numberRegion":"IN" } }, { "id":"5b05329cc9e77c000189b950", "priorityOrder":1, "name":"sample2", "relation":"Friend", "sampleInfo":{ "countryCode":"91", "numberType":"MOBILE", "numberRegion":"IN" } } ] }
我在java中浏览了关于这种情况的bean比较的网络,但我找不到任何更简单的解决方案,但找到了一些很酷的JSON解决方案。 我可以看到GSON的一些解决方案,但它不会返回客户端对象包含“新元素”和“更改元素”。 有没有办法在JSON或JAVA中返回更新和修改过的元素? 你的帮助应该是值得的。 即使是暗示对我来说也是一个很好的开始。
将JSON文档读取为Map
并进行比较
您可以将两个JSON文档都读为Map
。 请参阅以下Jackson和Gson的示例:
ObjectMapper mapper = new ObjectMapper(); TypeReference> type = new TypeReference>() {}; Map leftMap = mapper.readValue(leftJson, type); Map rightMap = mapper.readValue(rightJson, type);
Gson gson = new Gson(); Type type = new TypeToken
然后使用Guava的Maps.difference(Map
来比较它们。 它返回一个MapDifference
实例:
MapDifference difference = Maps.difference(left, right);
如果您对结果不满意,可以考虑展平地图然后进行比较。 它将提供更好的比较结果,尤其是对于嵌套对象和数组。
创建平面Map
以进行比较
要平整地图,您可以使用:
public final class FlatMapUtil { private FlatMapUtil() { throw new AssertionError("No instances for you!"); } public static Map flatten(Map map) { return map.entrySet().stream() .flatMap(FlatMapUtil::flatten) .collect(LinkedHashMap::new, (m, e) -> m.put("/" + e.getKey(), e.getValue()), LinkedHashMap::putAll); } private static Stream> flatten(Map.Entry entry) { if (entry == null) { return Stream.empty(); } if (entry.getValue() instanceof Map, ?>) { return ((Map, ?>) entry.getValue()).entrySet().stream() .flatMap(e -> flatten(new AbstractMap.SimpleEntry<>(entry.getKey() + "/" + e.getKey(), e.getValue()))); } if (entry.getValue() instanceof List>) { List> list = (List>) entry.getValue(); return IntStream.range(0, list.size()) .mapToObj(i -> new AbstractMap.SimpleEntry(entry.getKey() + "/" + i, list.get(i))) .flatMap(FlatMapUtil::flatten); } return Stream.of(entry); } }
它使用RFC 6901中定义的JSON指针表示法作为键,因此您可以轻松找到值。
例
请考虑以下JSON文档:
{ "name": { "first": "John", "last": "Doe" }, "address": null, "birthday": "1980-01-01", "company": "Acme", "occupation": "Software engineer", "phones": [ { "number": "000000000", "type": "home" }, { "number": "999999999", "type": "mobile" } ] }
{ "name": { "first": "Jane", "last": "Doe", "nickname": "Jenny" }, "birthday": "1990-01-01", "occupation": null, "phones": [ { "number": "111111111", "type": "mobile" } ], "favorite": true, "groups": [ "close-friends", "gym" ] }
以下代码比较它们并显示差异:
Map leftFlatMap = FlatMapUtil.flatten(leftMap); Map rightFlatMap = FlatMapUtil.flatten(rightMap); MapDifference difference = Maps.difference(leftFlatMap, rightFlatMap); System.out.println("Entries only on the left\n--------------------------"); difference.entriesOnlyOnLeft() .forEach((key, value) -> System.out.println(key + ": " + value)); System.out.println("\n\nEntries only on the right\n--------------------------"); difference.entriesOnlyOnRight() .forEach((key, value) -> System.out.println(key + ": " + value)); System.out.println("\n\nEntries differing\n--------------------------"); difference.entriesDiffering() .forEach((key, value) -> System.out.println(key + ": " + value));
它将产生以下输出:
Entries only on the left -------------------------- /address: null /phones/1/number: 999999999 /phones/1/type: mobile /company: Acme Entries only on the right -------------------------- /name/nickname: Jenny /groups/0: close-friends /groups/1: gym /favorite: true Entries differing -------------------------- /birthday: (1980-01-01, 1990-01-01) /occupation: (Software engineer, null) /name/first: (John, Jane) /phones/0/number: (000000000, 111111111) /phones/0/type: (home, mobile)
创建JSON补丁文档
作为另一个答案中描述的方法的替代,您可以使用JSR 374中定义的Java API for JSON Processing (它不在Gson或Jackson上使用)。 需要以下依赖项:
javax.json javax.json-api 1.1.2 org.glassfish javax.json 1.1.2
然后,您可以从JSON文档创建JSON diff。 它将生成RFC 6902中定义的JSON补丁文档:
JsonPatch diff = Json.createDiff(source, target);
应用于源文档时,JSON Patch会生成目标文档。 可以使用以下命令将JSON修补程序应用于源文档:
JsonObject patched = diff.apply(source);
创建JSON Merge修补程序文档
根据您的需要,您可以创建RFC 7396中定义的JSON Merge Patch文档:
JsonMergePatch mergeDiff = Json.createMergeDiff(source, target);
应用于源文档时,JSON Merge Patch会生成目标文档。 要修补源,请使用:
JsonValue patched = mergeDiff.apply(source);
漂亮的打印JSON文档
要打印JSON文档,您可以使用:
System.out.println(format(diff.toJsonArray())); System.out.println(format(mergeDiff.toJsonValue()));
public static String format(JsonValue json) { StringWriter stringWriter = new StringWriter(); prettyPrint(json, stringWriter); return stringWriter.toString(); } public static void prettyPrint(JsonValue json, Writer writer) { Map config = Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true); JsonWriterFactory writerFactory = Json.createWriterFactory(config); try (JsonWriter jsonWriter = writerFactory.createWriter(writer)) { jsonWriter.write(json); } }
例
请考虑以下JSON文档:
{ "name": { "first": "John", "last": "Doe" }, "address": null, "birthday": "1980-01-01", "company": "Acme", "occupation": "Software engineer", "phones": [ { "number": "000000000", "type": "home" }, { "number": "999999999", "type": "mobile" } ] }
{ "name": { "first": "Jane", "last": "Doe", "nickname": "Jenny" }, "birthday": "1990-01-01", "occupation": null, "phones": [ { "number": "111111111", "type": "mobile" } ], "favorite": true, "groups": [ "close-friends", "gym" ] }
以下代码生成JSON补丁:
JsonValue source = Json.createReader(new StringReader(leftJson)).readValue(); JsonValue target = Json.createReader(new StringReader(rightJson)).readValue(); JsonPatch diff = Json.createDiff(source.asJsonObject(), target.asJsonObject()); System.out.println(format(diff.toJsonArray()));
它将产生以下输出:
[ { "op": "replace", "path": "/name/first", "value": "Jane" }, { "op": "add", "path": "/name/nickname", "value": "Jenny" }, { "op": "remove", "path": "/address" }, { "op": "replace", "path": "/birthday", "value": "1990-01-01" }, { "op": "remove", "path": "/company" }, { "op": "replace", "path": "/occupation", "value": null }, { "op": "replace", "path": "/phones/1/number", "value": "111111111" }, { "op": "remove", "path": "/phones/0" }, { "op": "add", "path": "/favorite", "value": true }, { "op": "add", "path": "/groups", "value": [ "close-friends", "gym" ] } ]
现在考虑以下代码来生成JSON Merge Patch:
JsonValue source = Json.createReader(new StringReader(leftJson)).readValue(); JsonValue target = Json.createReader(new StringReader(rightJson)).readValue(); JsonMergePatch mergeDiff = Json.createMergeDiff(source, target); System.out.println(format(mergeDiff.toJsonValue()));
它将产生以下输出:
{ "name": { "first": "Jane", "nickname": "Jenny" }, "address": null, "birthday": "1990-01-01", "company": null, "occupation": null, "phones": [ { "number": "111111111", "type": "mobile" } ], "favorite": true, "groups": [ "close-friends", "gym" ] }
应用补丁时的结果不同
当应用补丁文档时,对于上述方法,结果略有不同。 请考虑以下将JSON修补程序应用于文档的代码:
JsonPatch diff = ... JsonValue patched = diff.apply(source.asJsonObject()); System.out.println(format(patched));
它产生:
{ "name": { "first": "Jane", "last": "Doe", "nickname": "Jenny" }, "birthday": "1990-01-01", "occupation": null, "phones": [ { "number": "111111111", "type": "mobile" } ], "favorite": true, "groups": [ "close-friends", "gym" ] }
现在考虑以下将JSON Merge Patch应用于文档的代码:
JsonMergePatch mergeDiff = ... JsonValue patched = mergeDiff.apply(source); System.out.println(format(patched));
它产生:
{ "name": { "first": "Jane", "last": "Doe", "nickname": "Jenny" }, "birthday": "1990-01-01", "phones": [ { "number": "111111111", "type": "mobile" } ], "favorite": true, "groups": [ "close-friends", "gym" ] }
在第一个示例中, occupation
属性为null
。 在第二个例子中,它被省略了。 这是由于JSON Merge Patch上的null
语义。 来自RFC 7396 :
如果目标确实包含该成员,则替换该值。 合并补丁中的空值具有特殊含义,表示删除目标中的现有值。 […]
此设计意味着合并修补程序文档适用于描述主要使用对象作为其结构且不使用显式空值的JSON文档的修改。 合并修补程序格式不适用于所有JSON语法。