1. Overview

In this tutorial, weā€™ll look at different ways to read JSON documents as Maps and compare them. Weā€™ll also look at ways to find the differences between the two Maps. 2. Converting to Map

First, weā€™ll look at different ways to convert JSON documents to Maps. Letā€™s look at the JSON objects weā€™ll use for our test. Letā€™s create a file namedĀ first.jsonĀ with the following content: { ā€œnameā€: ā€œJohnā€, ā€œageā€: 30, ā€œcarsā€: [ ā€œFordā€, ā€œBMWā€ ], ā€œaddressā€: { ā€œstreetā€: ā€œSecond Streetā€, ā€œcityā€: ā€œNew Yorkā€ }, ā€œchildrenā€: [ { ā€œnameā€: ā€œSaraā€, ā€œageā€: 5 }, { ā€œnameā€: ā€œAlexā€, ā€œageā€: 3 } ] }

Similarly, letā€™s create another file named second.jsonĀ with the following content: { ā€œnameā€: ā€œJohnā€, ā€œageā€: 30, ā€œcarsā€: [ ā€œFordā€, ā€œAudiā€ ], ā€œaddressā€: { ā€œstreetā€: ā€œMain Streetā€, ā€œcityā€: ā€œNew Yorkā€ }, ā€œchildrenā€: [ { ā€œnameā€: ā€œPeterā€, ā€œageā€: 5 }, { ā€œnameā€: ā€œCathyā€, ā€œageā€: 10 } ] }

As we can see,Ā there are two differences between the above JSON documents: The value of theĀ carsĀ array is different The value of theĀ streetĀ key in theĀ addressĀ object is different TheĀ childrenĀ arrays have multiple differences 2.1. Using Jackson

Jackson is a popular library used for JSON operations. We can use Jackson to convert a JSON to aĀ Map. Letā€™s start by adding the Jackson dependency:

com.fasterxml.jackson.core
jackson-databind
2.15.2

Now we can convert a JSON document to Map using Jackson: class JsonUtils { public static Map jsonFileToMap(String path) throws IOException { ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(new File(path), new TypeReference>() {}); } }

Here,Ā weā€™re using theĀ readValue()Ā method from theĀ ObjectMapperĀ class to convert the JSON document to a Map.Ā It takes the JSON document as aĀ FileĀ object and aĀ TypeReferenceĀ object as parameters. 2.2. Using Gson

Similarly,Ā we can also use Gson to convert the JSON document to a Map.Ā We need to include the dependency for this:

com.google.code.gson
gson
2.10.1

Now letā€™s look at the code to convert the JSON: public static Map jsonFileToMapGson(String path) throws IOException { Gson gson = new Gson(); return gson.fromJson(new FileReader(path), new TypeToken>() {}.getType()); }

Here,Ā weā€™re using theĀ fromJson()Ā method from theĀ GsonĀ class to convert the JSON document to a Map.Ā It takes the JSON document as aĀ FileReaderĀ object and aĀ TypeTokenĀ object as parameters. 3. Comparing Maps

Now that weā€™ve converted the JSON documents to Maps,Ā letā€™s look at different ways to compare them. 3.1. Using Guavaā€™s Map.difference()

Guava provides aĀ Maps.difference()Ā method that can be used to compare two Maps.Ā To utilize this,Ā letā€™s add theĀ GuavaĀ dependency to our project:

com.google.guava
guava
32.1.2-jre

Now,Ā letā€™s look at the code to compare the Maps: @Test void givenTwoJsonFiles_whenCompared_thenTheyAreDifferent() throws IOException { Map firstMap = JsonUtils.jsonFileToMap(ā€œsrc/test/resources/first.jsonā€); Map secondMap = JsonUtils.jsonFileToMap(ā€œsrc/test/resources/second.jsonā€); MapDifference difference = Maps.difference(firstFlatMap, secondFlatMap); difference.entriesDiffering().forEach((key, value) -> { System.out.println(key + ": " + value.leftValue() + " - " + value.rightValue()); }); assertThat(difference.areEqual()).isFalse(); }

Guava can only compare one level of Maps. This doesnā€™t work for our case as we haveĀ a nested Map. Letā€™s look at how we compare our nested Maps above.Ā Weā€™re using the entriesDiffering()Ā method to get the differences between the Maps. This returns a Map of differences where the key is the path to the value, and the value is a MapDifference.ValueDifferenceĀ object.Ā This object contains the values from both the Maps.Ā If we run the test,Ā weā€™ll see the keys that are different between the Maps and their values: cars: [Ford, BMW] - [Ford, Audi] address: {street=Second Street, city=New York} - {street=Main Street, city=New York} children: [{name=Sara, age=5}, {name=Alex, age=3}] - [{name=Peter, age=5}, {name=Cathy, age=10}] As we can see, this shows that theĀ cars, address, and children fields are different, and the differences are listed. However, this doesnā€™t show which nested fields are leading to these differences. For example, it doesnā€™t point out that the street field in theĀ address objects is different. 3.2. Flattening Maps

To precisely point out differences between nested Maps, weā€™ll flatten the Maps so each key is a path to the value. For example, theĀ streetĀ key in theĀ addressĀ object will be flattened toĀ address.streetĀ and so on. Letā€™s look at the code for this: class FlattenUtils public static Map flatten(Map map) { return flatten(map, null); } private static Map flatten(Map map, String prefix) { Map flatMap = new HashMap<>(); map.forEach((key, value) -> { String newKey = prefix != null ? prefix + ā€œ.ā€ + key : key; if (value instanceof Map) { flatMap.putAll(flatten((Map) value, newKey)); } else if (value instanceof List) { // check for list of primitives Object element = ((List) value).get(0); if (element instanceof String | element instanceof Number || element instanceof Boolean) { flatMap.put(newKey, value); else { // check for list of objects List> list = (List>) value; for (int i = 0; i < list.size(); i++) { flatMap.putAll(flatten(list.get(i), newKey + ā€œ[ā€ + i + ā€œ]ā€)); } } } else { flatMap.put(newKey, value); } }); return flatMap; } }

Here,Ā weā€™re using recursion to flatten the Map.Ā For any field,Ā one of the following conditions will be true: The value could be a Map (nested JSON object). In this case, weā€™ll recursively call the flatten()Ā method with the value as the parameter. For example, theĀ addressĀ object will be flattened toĀ address.streetĀ andĀ address.city. Next, we can check if the value is aĀ ListĀ (JSON array). If the list contains primitive values, weā€™ll add the key and value to the flattened Map. If the list contains objects, weā€™ll recursively call theĀ flatten()Ā method with each object as the parameter. For example, theĀ childrenĀ array will be flattened toĀ children[0].name,Ā children[0].age,Ā children[1].name, andĀ children[1].age. If the value is neither aĀ MapĀ nor aĀ List, weā€™ll add the key and value to the flattened Map. This will be recursive until we reach the last level of the Map.Ā At this point,Ā weā€™ll have a flattened Map with each key as a path to the value. 3.3. Testing

Now that weā€™ve flattened the Maps,Ā letā€™s look at how we can compare them usingĀ Maps.difference(): @Test void givenTwoJsonFiles_whenCompared_thenTheyAreDifferent() throws IOException { Map firstFlatMap = FlattenUtils.flatten(JsonUtils.jsonFileToMap(ā€œsrc/test/resources/first.jsonā€)); Map secondFlatMap = FlattenUtils.flatten(JsonUtils.jsonFileToMap(ā€œsrc/test/resources/second.jsonā€)); MapDifference difference = Maps.difference(firstFlatMap, secondFlatMap); difference.entriesDiffering().forEach((key, value) -> { System.out.println(key + ": " + value.leftValue() + " - " + value.rightValue()); }); assertThat(difference.areEqual()).isFalse(); }

Again, weā€™ll print the keys and values that are different. This leads to the output below: cars: [Ford, BMW] - [Ford, Audi] children[1].age: 3 - 10 children[1].name: Alex - Cathy address.street: Second Street - Main Street children[0].name: Sara - Peter

  1. Conclusion

In this article, we looked at comparing two JSON documents in Java. We looked at different ways to convert the JSON documents to Maps and then compared them using Guavaā€™sĀ Maps.difference()Ā method.Ā We also looked at how we can flatten the Maps so that we can compare nested Maps. As always,Ā the code for this article is availableĀ over on GitHub.