What I learned while comparing two JSONs

Mujtabauddin Furqan
3 min readFeb 23, 2019

--

  • JSON is a set of key-value pairs wherein the order of the keys is never guaranteed
    But the order of any array in a JSON is guaranteed
  • There are many ways to compare two JSONs, a lot of them involve converting them two Flat Maps or HashMaps
    The first and the most convenient one we tried was Guava’s Flat Map method
  • Guava is a common Utility library by Google made open source(Fucking awesome people)
  • . If you deserialize the objects as a Map<String, Object>, you can compare two JSONs , you can use Maps.difference to compare the two resulting maps.
    Note that if you care about the order of the elements, Json doesn't preserve order on the fields of Objects, so this method won't show those comparisons.
    A Simple example of how it’s done
public static void main(String[] args) {
String json1 = "{\"name\":\"ABC\", \"city\":\"XYZ\", \"state\":\"CA\"}";
String json2 = "{\"city\":\"XYZ\", \"street\":\"123 anyplace\", \"name\":\"ABC\"}";

Gson g = new Gson();
Type mapType = new TypeToken<Map<String, Object>>(){}.getType();
Map<String, Object> firstMap = g.fromJson(json1, mapType);
Map<String, Object> secondMap = g.fromJson(json2, mapType);
System.out.println(Maps.difference(firstMap, secondMap));
  • It has three methods that can be called on Maps.difference()
    a. entriesDiffering() — this produces the keys that are present in both the maps but have different values
    b. entriesOnLeft() — this produces keys that are present in the old JSON but not in the new One, thus the removed Fields
    c. entriesOnRight() — this produces keys that are present in the new JSON but not in the old, thus the added Fields
  • The problem with this approach is if we have a JSON array that is really big in size (say containing 100 elements) then the way it produces the differences is very inefficient and space consuming
    The best example of its inefficiency came when I added one element to the top of the array
    It did a simple index to index comparison and produces a change if the value of any sub-element changed. In the example I am talking about, if the Array previously had 10 element and I added one at index 0, shifting all the elements one step down and each element had say 3 sub-elements then the API would produce 33 changes of the form
    “RootKey.ArrayName.0.Sub-Element”:newValue
  • Now to solve this problem we needed an API that would do the comparison a little more smartly and recognise when there was only a shuffling in the array and when there was an addition or deletion from it. For this we started experimenting with ZJSONPATCH.
  • ZJON Patch is a Java implementaion of RFC 6902 Json Patch which is described in detail here : https://tools.ietf.org/html/rfc6902
    JSON Patch defines a JSON document structure for representing changes to a JSON document.
    It can be used to avoid sending a whole document when only a part has changed, thus reducing network bandwidth requirements if data (in JSON format) is required to send across multiple systems over network or in case of multi DC transfer.

Obtaining JSON Diff as patch

JsonNode patch = JsonDiff.asJson(JsonNode source, JsonNode target)

Computes and returns a JSON patch from source to target, Both source and target must be either valid JSON objects or arrays or values. Further, if resultant patch is applied to source, it will yield target.

The algorithm which computes this JsonPatch currently generates following operations as per RFC 6902 -

  • add
  • remove
  • replace
  • move
  • copy

Apply Json Patch

JsonNode target = JsonPatch.apply(JsonNode patch, JsonNode source);

Given a patch, it apply it to source JSON and return a target JSON which can be ( JSON object or array or value ). This operation performed on a clone of source JSON ( thus, the source JSON is unmodified and can be used further).

To turn off MOVE & COPY Operations

EnumSet<DiffFlags> flags = DiffFlags.dontNormalizeOpIntoMoveAndCopy().clone()
JsonNode patch = JsonDiff.asJson(JsonNode source, JsonNode target, flags)

Example

First Json

{"a": 0,"b": [1,2]}

Second json ( the json to obtain )

{"b": [1,2,0]}

Following patch will be returned:

[{"op":"move","from":"/a","path":"/b/2"}]

here "op" specifies the operation ("move"), "from" specifies the path from where the value should be moved, and"path" specifies where value should be moved. The value that is moved is taken as the content at the "from" path.

Apply Json Patch In-Place

JsonPatch.applyInPlace(JsonNode patch, JsonNode source);

Given a patch, it will apply it to the source JSON mutating the instance, opposed to JsonPatch.apply which returns a new instance with the patch applied, leaving the source unchanged.

--

--