web-dev-qa-db-ja.com

JSONドキュメントを比較し、JacksonまたはGsonとの違いを返す方法は?

Spring-bootを使用してバックエンドサービスを開発しています。 2つのBean(1つはDBオブジェクト、もう1つはクライアント要求オブジェクト)を比較し、「新しい要素」、「変更された要素」を返し、変更がない場合は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"
           }
        }
     ]
  }

JavaでこのシナリオのBean比較についてインターネットを閲覧しましたが、簡単なソリューションは見つかりませんでしたが、JSONのクールなソリューションが見つかりました。クライアントオブジェクトに「新しい要素」と「変更要素」が含まれています。JSONまたはJAVAで新しい要素と変更された要素を返す方法はありますか?ヘルプはかなり役立つはずです。

8
VelNaga

JSONドキュメントをMapsとして読み取り、比較する

両方のJSONドキュメントを _Map<K, V>_ として読み取ることができます。ジャクソンと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);
_

次に、グアバの Maps.difference(Map<K, V>, Map<K, V>) を使用してそれらを比較します。 _MapDifference<K, V>_ インスタンスを返します:

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

結果に満足できない場合は、マップを平坦化してから比較することができます。特にネストされたオブジェクトと配列に対して、より良い比較結果を提供します。

比較用のフラットMapsの作成

マップをフラット化するには、次を使用できます。

_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);
    }
}
_

キーに RFC 6901 で定義されているJSON Pointer notationを使用するため、値を簡単に見つけることができます。

次の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)
_
10
cassiomolin

JSONパッチドキュメントの作成

その他の回答 で説明されているアプローチの代わりに、 JSR 374 で定義されている JSON処理用のJava API を使用できますGsonまたはJacksonで使用)。次の依存関係が必要です。

<!-- 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>

次に、JSONドキュメントからJSON diffを作成できます。 RFC 6902 で定義されているJSONパッチドキュメントを生成します。

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

ソースドキュメントに適用すると、JSONパッチはターゲットドキュメントを生成します。 JSONパッチは、次を使用してソースドキュメントに適用できます。

JsonObject patched = diff.apply(source);

JSONマージパッチドキュメントの作成

ニーズに応じて、 RFC 7396 で定義されているJSONマージパッチドキュメントを作成できます。

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

パッチを適用したときの異なる結果

パッチドキュメントを適用すると、上記のアプローチでは結果がわずかに異なります。ドキュメントに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です。 2番目の例では、省略されています。これは、JSONマージパッチのnullセマンティクスによるものです。 RFC 7396 から:

ターゲットにメンバーが含まれている場合、値は置き換えられます。マージパッチのヌル値には、ターゲットの既存の値が削除されたことを示す特別な意味が与えられます。 [...]

この設計は、マージパッチドキュメントが、構造にオブジェクトを主に使用し、明示的なnull値を使用しないJSONドキュメントへの変更を記述するのに適していることを意味します。マージパッチ形式は、すべてのJSON構文に適しているわけではありません。

10
cassiomolin