Java 如何比较 JSON 文档并返回与 Jackson 或 Gson 的差异?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/50967015/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 23:54:33  来源:igfitidea点击:

How to compare JSON documents and return the differences with Hymanson or Gson?

javajsonHymansongson

提问by VelNaga

I am using spring-boot to develop backend services. There is a scenario to compare 2-beans(one is the DB object and another one is the client requested object) and return the "new element","modified element" and if there is no change then return false. The 2-beans are in a below format

我正在使用 spring-boot 开发后端服务。有一个场景来比较 2 个 bean(一个是 DB 对象,另一个是客户端请求的对象)并返回“新元素”、“修改后的元素”,如果没有变化,则返回 false。2-beans 采用以下格式

"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"
           }
        }
     ]
  }

I have browsed internet about bean comparison for this scenario in java but I couldn't find any simpler solution but found some cool solution for JSON. I can see some solution for GSON but it will not return the client object contains "new element" and the "changes element". Is there any way to return the newer and modified element in JSON or JAVA? Your help should be appreciable. Even a hint will be a great start for me.

我已经在互联网上浏览了有关 Java 中此场景的 bean 比较的信息,但我找不到任何更简单的解决方案,但找到了一些很酷的 JSON 解决方案。我可以看到 GSON 的一些解决方案,但它不会返回包含“新元素”和“更改元素”的客户端对象。有什么方法可以在 JSON 或 JAVA 中返回较新和修改过的元素?你的帮助应该是可观的。即使是一个提示对我来说也是一个很好的开始。

采纳答案by cassiomolin

Reading the JSON documents as Maps and comparing them

将 JSON 文档作为Maps读取并进行比较

You could read both JSON documents as Map<K, V>. See the below examples for Hymanson and Gson:

您可以将两个 JSON 文档阅读为Map<K, V>. 请参阅以下 Hymanson 和 Gson 示例:

ObjectMapper mapper = new ObjectMapper();
TypeReference<HashMap<String, Object>> type = 
    new TypeReference<HashMap<String, Object>>() {};

Map<String, Object> leftMap = mapper.readValue(leftJson, type);
Map<String, Object> rightMap = mapper.readValue(rightJson, type);
Gson gson = new Gson();
Type type = new TypeToken<Map<String, Object>>(){}.getType();

Map<String, Object> leftMap = gson.fromJson(leftJson, type);
Map<String, Object> rightMap = gson.fromJson(rightJson, type);

Then use Guava's Maps.difference(Map<K, V>, Map<K, V>)to compare them. It returns a MapDifference<K, V>instance:

然后使用番石榴的Maps.difference(Map<K, V>, Map<K, V>)来比较它们。它返回一个MapDifference<K, V>实例:

MapDifference<String, Object> difference = Maps.difference(leftMap, rightMap);

If you are not happy with the result, you can consider flatteningthe maps and then compare them. It will provide better comparison results especially for nested objects and arrays.

如果您对结果不满意,可以考虑将地图展,然后进行比较。它将提供更好的比较结果,尤其是对于嵌套对象和数组。

Creating flat Maps for the comparison

Map为比较创建平面

To flat the map, you can use:

要平整地图,您可以使用:

public final class FlatMapUtil {

    private FlatMapUtil() {
        throw new AssertionError("No instances for you!");
    }

    public static Map<String, Object> flatten(Map<String, Object> map) {
        return map.entrySet().stream()
                .flatMap(FlatMapUtil::flatten)
                .collect(LinkedHashMap::new, (m, e) -> m.put("/" + e.getKey(), e.getValue()), LinkedHashMap::putAll);
    }

    private static Stream<Map.Entry<String, Object>> flatten(Map.Entry<String, Object> 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<String, Object>(entry.getKey() + "/" + i, list.get(i)))
                    .flatMap(FlatMapUtil::flatten);
        }

        return Stream.of(entry);
    }
}

It uses the JSON Pointer notationdefined in the RFC 6901for the keys, so you can easily locate the values.

它使用RFC 6901 中定义的JSON 指针表示法作为键,因此您可以轻松找到这些值。

Example

例子

Consider the following JSON documents:

考虑以下 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"
  ]
}

And the following code to compare them and show the differences:

以及以下代码来比较它们并显示差异:

Map<String, Object> leftFlatMap = FlatMapUtil.flatten(leftMap);
Map<String, Object> rightFlatMap = FlatMapUtil.flatten(rightMap);

MapDifference<String, Object> 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));

It will produce the following output:

它将产生以下输出:

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)

回答by cassiomolin

Creating a JSON Patch document

创建 JSON 补丁文档

Alternatively to the approach described in the other answer, you could use the Java API for JSON Processingdefined in the JSR 374(it doesn't use on Gson or Hymanson). The following dependencies are required:

作为另一个答案中描述的方法的替代方法,您可以使用JSR 374 中定义的用于 JSON 处理Java API(它不在 Gson 或 Hymanson 上使用)。需要以下依赖项:

<!-- Java API for JSON Processing (API) -->
<dependency>
    <groupId>javax.json</groupId>
    <artifactId>javax.json-api</artifactId>
    <version>1.1.2</version>
</dependency>

<!-- Java API for JSON Processing (implementation) -->
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.json</artifactId>
    <version>1.1.2</version>
</dependency>

Then you can create a JSON diff from the JSON documents. It will produce a JSON Patch document as defined in the RFC 6902:

然后您可以从 JSON 文档创建一个 JSON 差异。它将生成RFC 6902 中定义的 JSON Patch 文档:

JsonPatch diff = Json.createDiff(source, target);

When applied to the source document, the JSON Patch yields the target document. The JSON Patch can be applied to the source document using:

当应用于源文档时,JSON 补丁会生成目标文档。可以使用以下方法将 JSON 补丁应用于源文档:

JsonObject patched = diff.apply(source);

Creating a JSON Merge Patch document

创建 JSON 合并补丁文档

Depending on your needs, you could create a JSON Merge Patch document as defined in the RFC 7396:

根据您的需要,您可以创建RFC 7396 中定义的 JSON Merge Patch 文档:

JsonMergePatch mergeDiff = Json.createMergeDiff(source, target);

When applied to the source document, the JSON Merge Patch yields the target document. To patch the source, use:

当应用于源文档时,JSON Merge Patch 会生成目标文档。要修补源,请使用:

JsonValue patched = mergeDiff.apply(source);

Pretty printing JSON documents

漂亮的打印 JSON 文档

To pretty print the JSON documents, you can use:

要漂亮地打印 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<String, Object> config =
            Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true);
    JsonWriterFactory writerFactory = Json.createWriterFactory(config);
    try (JsonWriter jsonWriter = writerFactory.createWriter(writer)) {
        jsonWriter.write(json);
    }
}

Example

例子

Consider the following JSON documents:

考虑以下 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"
  ]
}

And the following code to produce a JSON Patch:

以及以下用于生成 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()));

It will produce the following output:

它将产生以下输出:

[
    {
        "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"
        ]
    }
]

Now consider the following code to produce a JSON Merge Patch:

现在考虑以下代码来生成 JSON 合并补丁:

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()));

It will produce the following output:

它将产生以下输出:

{
    "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"
    ]
}

Different results when applying the patches

应用补丁时的不同结果

When the patch document is applied, the results are slightly different for the approaches described above. Consider the following code that applies JSON Patch to a document:

当应用补丁文档时,上述方法的结果略有不同。考虑以下将 JSON Patch 应用于文档的代码:

JsonPatch diff = ...
JsonValue patched = diff.apply(source.asJsonObject());
System.out.println(format(patched));

It produces:

它产生:

{
    "name": {
        "first": "Jane",
        "last": "Doe",
        "nickname": "Jenny"
    },
    "birthday": "1990-01-01",
    "occupation": null,
    "phones": [
        {
            "number": "111111111",
            "type": "mobile"
        }
    ],
    "favorite": true,
    "groups": [
        "close-friends",
        "gym"
    ]
}

Now consider the following code that applies JSON Merge Patch to a document:

现在考虑将 JSON Merge Patch 应用于文档的以下代码:

JsonMergePatch mergeDiff = ...
JsonValue patched = mergeDiff.apply(source);
System.out.println(format(patched));

It produces:

它产生:

{
    "name": {
        "first": "Jane",
        "last": "Doe",
        "nickname": "Jenny"
    },
    "birthday": "1990-01-01",
    "phones": [
        {
            "number": "111111111",
            "type": "mobile"
        }
    ],
    "favorite": true,
    "groups": [
        "close-friends",
        "gym"
    ]
}

In the first example, the occupationproperty is null. In the second example, it's omitted. It's due to the nullsemantics on JSON Merge Patch. From the RFC 7396:

在第一个示例中,occupation属性是null。在第二个例子中,它被省略了。这是由于nullJSON Merge Patch 上的语义。来自RFC 7396

If the target does contain the member, the value is replaced. Null values in the merge patch are given special meaning to indicate the removal of existing values in the target. [...]

This design means that merge patch documents are suitable for describing modifications to JSON documents that primarily use objects for their structure and do not make use of explicit null values. The merge patch format is not appropriate for all JSON syntaxes.

如果目标确实包含该成员,则替换该值。合并补丁中的空值被赋予特殊含义以指示删除目标中的现有值。[...]

这种设计意味着合并补丁文档适用于描述对 JSON 文档的修改,这些 JSON 文档主要使用对象作为其结构并且不使用显式空值。合并补丁格式不适用于所有 JSON 语法。