sclass JsonDiff { srecord DiffResult(O a, O b) {} sclass Removed > DiffResult { *(O *a, O *b) {} } // b missing sclass Added > DiffResult { *(O *a, O *b) {} } // a missing sclass DifferentType > DiffResult { *(O *a, O *b) {} } sclass Different > DiffResult { *(O *a, O *b) {} } // same type, generally different // returns null if identical sO diff(S json1, S json2) { O o1 = jsonDecode(json1), o2 = jsonDecode(json2); ret diff(json1, json2); } sO diff(O a, O b) { if (eq(a, b)) null; if (a == null) ret new Removed(a, b); if (b == null) ret new Added(a, b); if (a.getClass() != b.getClass()) ret new DifferentType(a, b); // from here on, we know that a and b have the same type if (a cast Map) ret mapKeyAndFunction_lhm(withoutNullValues(concatAsOrderedSet(keys(a), keys(b)), key -> diff(a.get(key), b/Map.get(key))); // TODO: lists ret new Different(a, b); } }