如何比较JSON文档并将差异与Jackson或Gson返回? 我正在使用Spring-boot开发后端服务。有一个方案可以比较2个蜂窝(一个是DB对象,另一个是客户端请求的对象),然后返回“新元素”,“修改了”

问题描述 投票:0回答:3

将JSON文档阅读为
Map
并进行比较

您可以将两个JSON文档都读为

java json jackson gson
3个回答
98
投票

。请参阅杰克逊和格森的以下示例:
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);

然后使用Guava的
Maps.difference(Map<K, V>, Map<K, V>)

比较它们。它返回一个
MapDifference<K, V>

实例:

MapDifference<String, Object> difference = Maps.difference(leftMap, rightMap);
如果您对结果不满意,则可以考虑flanting them them the Maps,然后比较它们。它将提供更好的比较结果,尤其是对于嵌套对象和数组。
为比较创建扁平
Map

要平整地图,您可以使用:
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);
    }
}

使用keSSON指针符号

RFC6901

中定义的键,以便您可以轻松地找到值。
示例

考虑以下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<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));

它将产生以下输出:

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补丁文档

其他答案

中所描述的方法上,您可以将
javaapi用于jsonProcessing

JSR374

中定义的JSONPROCESTING
(它在Gson或Jackson上不使用)。需要以下依赖项:

52
投票

然后您可以创建与JSON文档的JSON差异。它将产生一个JSON补丁文档,如

RFC6902

的定义: JsonPatch diff = Json.createDiff(source, target); 当应用于源文档时,JSON补丁会产生目标文档。 JSON补丁可以使用以下方式应用于源文档。 JsonObject patched = diff.apply(source);

创建JSON合并补丁文档
取决于您的需求,您可以创建一个JSON合并补丁文档,如

RFC7396

JsonMergePatch mergeDiff = Json.createMergeDiff(source, target);
当应用于源文档时,JSON合并补丁会产生目标文档。要修补源,请使用:

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<String, Object> 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合并补丁:
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" ] }
应用patches
时不同的结果

应用补丁文档时,对于上述方法,结果略有不同。考虑将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合并补丁程序应用于文档的以下代码:
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

语义。来自
RFC7396

如果目标确实包含构件,则更换值。  合并贴片中的空值具有特殊的含义,以指示删除目标中现有值。 [...]

该设计意味着合并补丁文档适合描述主要使用对象进行结构的JSON文档的修改,并且不使用显式零值。 合并补丁格式不适合所有JSON语法。

    
您可以尝试我的图书馆-Yjson -delta
.
。
它基于
Gson,可以使用特定目的进行配置,例如忽略字段或不考虑缺失/意外字段。

示例

expected: { "type": "animal", "info": { "id": 123, "subtype": "Cat", "timestamp": 1684852390 } } actual: { "type": "animal", "info": { "id": 123, "subtype": "Tiger", "timestamp": 1684852399 } }

比较和印刷结果:

1
投票

输出看起来像这样: Status: failed Mismatches: "root.info.subtype": Value mismatch. Expected: "Cat"; Actual: "Tiger"
jSondeltareport

对象具有以下字段:

Success
(布尔值):比较结果(如果JSON相等的话,成功)

mismatches
(列表):所有不匹配列表

您也可以使用
javers

。示例: private void assertOutputIsEquivalentToInput(String inputEvent, ObjectNode expectedJson, JsonNode actualJson) { var javers = JaversBuilder.javers().build(); var description=javers.compare(expectedJson, actualWithoutNulls).prettyPrint(); assertThat(description,actualJson,is(equalTo(expectedJson))); }

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.