Reading the JSON documents as Maps and comparing them

You could read both JSON documents as Map<K, V>. See the below examples for Jackson and 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:

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

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

Creating flat Maps for the comparison

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 notation defined in the RFC 6901 for the keys, so you can easily locate the values.

Example

Consider the following JSON documents:

{
  "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)
Answer from cassiomolin on Stack Overflow
🌐
GitHub
github.com › deblockt › json-diff
GitHub - deblockt/json-diff: A library to generate a json diff on java · GitHub
A library to generate a json diff on java. Contribute to deblockt/json-diff development by creating an account on GitHub.
Starred by 54 users
Forked by 10 users
Languages   Java
🌐
JSON Diff
jsondiff.com
JSON Diff - The semantic JSON compare tool
Validate, format, and compare two JSON documents. See the differences between the objects instead of just the new lines and mixed up properties.
Top answer
1 of 4
102

Reading the JSON documents as Maps and comparing them

You could read both JSON documents as Map<K, V>. See the below examples for Jackson and 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:

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

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

Creating flat Maps for the comparison

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 notation defined in the RFC 6901 for the keys, so you can easily locate the values.

Example

Consider the following JSON documents:

{
  "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)
2 of 4
54

Creating a JSON Patch document

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

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

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:

JsonObject patched = diff.apply(source);

Creating a JSON Merge Patch document

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

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:

JsonValue patched = mergeDiff.apply(source);

Pretty printing JSON documents

To pretty print the JSON documents, you can use:

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:

{
  "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:

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:

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:

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:

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 occupation property is null. In the second example, it's omitted. It's due to the null semantics on JSON Merge Patch. From the 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.

🌐
GitHub
github.com › fslev › json-compare
GitHub - fslev/json-compare: A Java library for comparing JSONs · GitHub
A Java library for matching JSONs, with some tweaks ! Compare any JSON convertible Java objects and check the differences between them when matching fails.
Starred by 75 users
Forked by 14 users
Languages   Java
🌐
Baeldung
baeldung.com › home › json › jackson › compare two json objects with jackson
Compare Two JSON Objects with Jackson | Baeldung
January 8, 2024 - JsonNode.equals works quite well in most cases. Jackson also provides JsonNode.equals(comparator, JsonNode) to configure a custom Java Comparator object.
🌐
Stanislav Myachenkov
smyachenkov.com › posts › how to compare json documents in java
How To Compare JSON Documents In Java | Stanislav Myachenkov
June 9, 2020 - List<Difference> compare(Object from, Object to, String path) The value of the key can be primitive value, array, or an object, so we need to handle all those situations. Let’s create functions to check if both object belong to the same category. Set<Class<?>> JSON_PRIMITIVES = Set.of( Integer.class, Long.class, Double.class, String.class ); boolean oneIsPrimitive(Class<?> from, Class<?> to) { return JSON_PRIMITIVES.contains(to) || JSON_PRIMITIVES.contains(from); } boolean bothAreObjects(Object from, Object to) { return from instanceof Map && to instanceof Map; } boolean bothAreArrays(Class<?> from, Class<?> to) { return from == ArrayList.class && to == ArrayList.class; }
🌐
GitHub
github.com › java-json-tools › json-patch › blob › master › src › main › java › com › github › fge › jsonpatch › diff › JsonDiff.java
json-patch/src/main/java/com/github/fge/jsonpatch/diff/JsonDiff.java at master · java-json-tools/json-patch
import java.util.*; · /** * JSON "diff" implementation · * * <p>This class generates a JSON Patch (as in, an RFC 6902 JSON Patch) given · * two JSON values as inputs. The patch can be obtained directly as a {@link · * JsonPatch} or as a {@link JsonNode}.</p> * * <p>Note: there is <b>no guarantee</b> about the usability of the generated ·
Author   java-json-tools
Find elsewhere
🌐
Cassiomolin
cassiomolin.com › programming › comparing-json-documents-in-java-with-jsonp
Comparing JSON documents in Java with JSON‑P | Cássio Mazzochi Molin
August 8, 2019 - And this document with the differences, when applied to the source document, yields the target document. To create JSON Patch document with the differences between a source and target documents, we can use the createDiff() method:
🌐
Baeldung
baeldung.com › home › json › compare two json objects with gson
Compare Two JSON Objects with Gson | Baeldung
January 8, 2024 - And though it’s possible to compare strings containing JSON, string comparison is sensitive to differences in representation, rather than content. To overcome this and compare JSON data semantically, we need to load the data into a structure in memory that’s not affected by things like whitespace or by the order of an object’s keys. In this short tutorial, we’ll solve this using Gson, a JSON serialization\deserialization library that can do a deep comparison between JSON objects.
🌐
GitHub
github.com › dann41 › jsoncomparator
GitHub - dann41/jsoncomparator: Java library to compare JSON objects
ComparatorConfig config = ComparatorConfig.aComparatorConfig() .withRecursive(true) .withArrayIncluded(false) .withConcatenateFields(true) .withFieldSeparator(".") .build(); JsonComparator comparator = new RecursiveJsonComparator(config); ComparisonResult result = comparator.compare("{}", "{}"); The comparison returns a ComparisonResult object containing all the differences found according to the provided (or default) ComparisonConfig.
Author   dann41
🌐
DEV Community
dev.to › keploy › json-diff-comparing-and-identifying-changes-in-json-data-2m3m
JSON Diff: Comparing and Identifying Changes in JSON Data - DEV Community
January 14, 2025 - This script uses the deepdiff library to identify changes, such as the updated age value and the difference in the skills array. ... Testing API Responses: Ensuring API outputs match the expected format and data. Validating Configuration Files: Comparing configuration files to detect unintended changes. Tracking Changes: Monitoring updates in JSON-based database exports or logs.
🌐
Cassiomolin
cassiomolin.com › programming › comparing-json-documents-in-java
Comparing JSON documents in Java — Cássio Mazzochi Molin
July 23, 2018 - MapDifference<String, Object> difference = Maps.difference(left, right); Everything was good until I had to compare complex JSON documents, with nested objects and arrays.
🌐
Quora
quora.com › How-do-I-compare-two-JSON-which-has-different-structures-using-Java
How to compare two JSON which has different structures using Java - Quora
Answer (1 of 2): You may try implementing this logic - 1. Consider each JSON object as a set. 2. And each key and value of the object defines a member of the set. Offcourse the value of a member of this set can be a key and another set. 3. Apply set difference on both of the sets. And two member...
🌐
Coderanch
coderanch.com › t › 533531 › java › compare-json-object-java-side
how to compare two json object at java side (Beginning Java forum at Coderanch)
So, Please give idea. How can i write json object comparision? And, Please provide sample code base Thanks in advance ... I recommend using this Java library for comparing JSONs: https://github.com/fslev/json-compare It has all kinds of features.
🌐
javaspring
javaspring.net › blog › getting-a-diff-of-two-json-strings-using-java-code
How to Get a Diff of Two JSON Strings in Java: Top Libraries & Code Examples — javaspring.net
Overview: zjsonpatch is a lightweight library that implements RFC 6902 (JSON Patch), a standard format for describing changes to JSON documents. It generates a "JSON Patch"—an array of operations (e.g., add, remove, replace)—that can be ...
🌐
GroupDocs
products.groupdocs.com › comparison › java › json
Java JSON Comparison API - Check JSON Files for Differences
December 19, 2024 - // Check files from your hard drive for differences or similarities // Create a Comparer object by specifying the initial file try (Comparer comparer = new Comparer("source.json") { // Include additional files to comparing comparer.add("target1.json"); comparer.add("target2.json"); // Get the report with the specified name as the result final Path resultPath = comparer.compare("result.json"); System.out.println("\nDocuments compared successfully."); }