Nithya Subramanian
Nithya Subramanian

Reputation: 11

Compare a heavily nested Json and print out the difference in structure and value

I have a heavily nested json which has to be compared. Comparison referring to structural(fields) being same and values are equal. I also want to print out the differences in the value and structure. Is there a java library that can help this out of the box?

Upvotes: 1

Views: 3305

Answers (3)

Vardhan Shah
Vardhan Shah

Reputation: 31

I was also dealing with the same issue, in my case, the JsonObject was deeply nested, was having nested object having arrays having nested objects, and so on. I tried to find several libraries, but none of the libraries offer to compare nested json arrays, as the internal order might be differing & the element of json array can also json object or json array & so on. So following is the generic routine that dealt with my problem, and, this is pretty accurate & have tested it on a huge volume of data. Hope you find it helpful:

public static boolean compare(@NotNull JsonElement jsonElement1, @NotNull JsonElement jsonElement2) {
    boolean toBeReturned;
    if (jsonElement1.isJsonArray() && jsonElement2.isJsonArray()) {
        JsonArray elements1 = jsonElement1.getAsJsonArray();
        JsonArray elements2 = jsonElement2.getAsJsonArray();

        if (elements1.size() != elements2.size()) {
            toBeReturned = false;
        } else {
            JsonArray removedElements = new JsonArray();
            toBeReturned = true;
            for (int i = 0; i < elements1.size(); i++) {
                JsonElement jsonElement = elements1.get(i);
                boolean found = false;
                for (int j = 0; j < elements2.size(); j++) {
                    if (compare(jsonElement, elements2.get(j))) {
                        found = true;
                        removedElements.add(elements2.remove(j));
                        break;
                    }
                }
                if (!found) {
                    toBeReturned = false;
                    break;
                }
            }
            for (int i = 0; i < removedElements.size(); i++) {
                elements2.add(removedElements.get(i));
            }
        }
    } else if (jsonElement1.isJsonNull() && jsonElement2.isJsonNull()) {
        toBeReturned = true;
    } else if (jsonElement1.isJsonPrimitive() && jsonElement2.isJsonPrimitive()) {
        toBeReturned = jsonElement1.equals(jsonElement2);
    } else if (jsonElement1.isJsonObject() && jsonElement2.isJsonObject()) {
        Set<Map.Entry<String, JsonElement>> map = jsonElement1.getAsJsonObject().entrySet();
        JsonObject json2 = jsonElement2.getAsJsonObject();
        toBeReturned = true;
        for (Map.Entry<String, JsonElement> entry : map) {
            if (!json2.has(entry.getKey()) || !compare(entry.getValue(), json2.get(entry.getKey()))) {
                toBeReturned = false;
                break;
            }
        }
    } else {
        toBeReturned = false;
    }
    return toBeReturned;
}

library is com.google.gson.

to covert a String to JsonElement, you can use following:

JsonParser.parseString(jsonString)

Upvotes: 1

Sean Van Gorder
Sean Van Gorder

Reputation: 3453

Implementation for Gson library:

public static void compareJson(JsonElement json, JsonElement other) {
    compareJson(json, other, "");
}

private static void compareJson(JsonElement json, JsonElement other, String path) {
    if (json.equals(other)) return;

    if (json.isJsonArray() && other.isJsonArray()) {
        JsonArray arrJ = (JsonArray) json;
        JsonArray arrO = (JsonArray) other;

        int size = Math.min(arrJ.size(), arrO.size());
        for (int i = 0; i < size; i++) {
            compareJson(arrJ.get(i), arrO.get(i), path + "/" + i);
        }
        if (arrJ.size() > arrO.size()) {
            for (int i = arrO.size(); i < arrJ.size(); i++) {
                System.out.println(path + "/ deleted array value: " + arrJ.get(i));
            }
        } else if (arrJ.size() < arrO.size()) {
            for (int i = arrJ.size(); i < arrO.size(); i++) {
                System.out.println(path + "/ added array value: " + arrO.get(i));
            }
        }
    } else if (json.isJsonObject() && other.isJsonObject()) {
        JsonObject objJ = (JsonObject) json;
        JsonObject objO = (JsonObject) other;

        for (Entry<String, JsonElement> entry : objJ.entrySet()) {
            String key = entry.getKey();
            JsonElement value = entry.getValue();
            if (objO.has(key)) {
                compareJson(value, objO.get(key), path + "/" + key);
            } else {
                System.out.println(path + "/ deleted object entry: \"" + key + "\": " + value);
            }
        }
        for (Entry<String, JsonElement> entry : objO.entrySet()) {
            String key = entry.getKey();
            JsonElement value = entry.getValue();
            if (!objJ.has(key)) {
                System.out.println(path + "/ added object entry: \"" + key + "\": " + value);
            }
        }
    } else if (json.isJsonPrimitive() && other.isJsonPrimitive()) {
        System.out.println(path + "/ value changed: " + json + " -> " + other);
    } else {
        System.out.println(path + "/ element changed: " + json + " -> " + other);
    }
}

Use:

JsonParser parser = new JsonParser();
compareJson(parser.parse(json1), parser.parse(json2));

Example output:

/glossary/title/ value changed: "example glossary" -> "Example Glossary"
/glossary/GlossDiv/GlossList/GlossEntry/SortAs/ element changed: "SGML" -> null
/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/GlossSeeAlso/0/ value changed: "GML" -> "XML"
/glossary/GlossDiv/GlossList/GlossEntry/GlossDef/GlossSeeAlso/ deleted array value: "XML"
/glossary/GlossDiv/GlossList/GlossEntry/ deleted object entry: "GlossSee": "markup"
/glossary/GlossDiv/GlossList/GlossEntry/ added object entry: "GlossType": "markup"

Upvotes: 2

satnam
satnam

Reputation: 11474

I wrote something similar to this for a video game. Here's the code I used. It leverages the Json library inside of Ox

  private Json computeDiff(Json a, Json b) {
    Json ret = Json.object();
    for (String key : b) {
      if (a.hasKey(key)) {
        Object aVal = a.getObject(key);
        Object bVal = b.getObject(key);

        if (aVal instanceof Json && ((Json) aVal).isObject()) {
          Json innerJson = computeDiff((Json) aVal, (Json) bVal);
          if (innerJson.size() > 0) {
            ret.with(key, innerJson);
          }
        } else {
          if (aVal.equals(bVal)) {
            continue;
          }

          ret.with(key, b.getObject(key));
        }
      } else {
        ret.with(key, b.getObject(key));
      }
    }
    for (String key : a) {
      if (!b.hasKey(key)) {
        // this element got deleted
        ret.with(key, "<DELETED>");
      }
    }
    return ret;
  }

Upvotes: 0

Related Questions