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