Skip to content

Instantly share code, notes, and snippets.

@ariens
Last active October 9, 2015 19:59
Show Gist options
  • Save ariens/f5810795fc50832ce781 to your computer and use it in GitHub Desktop.
Save ariens/f5810795fc50832ce781 to your computer and use it in GitHub Desktop.
/**
* Iterates over json1 which is intended to be a full representation of a complete JSON
* structure. It compares nodes on json1 against nodes on json2 which should contain
* either the same identical structure of json1 or a subset of JSON structure contained
* in json1.
*
* If identically named nodes on json1 and json2 vary in type (ObjectNode vs ArrayNode
* for example) then an exception is thrown since json2 must not contain any additional
* structure than what is found in json1.
*
* Explicit Null Node Handling Regardless of Node type:
*
* This pertains to the value of a node being explicity equal to null. See further below
* for handling of non-existent nodes
*
* If a node is null on json1 and not null on json2 then the node on json1 is set to the
* value of the node on json2.
*
* If a node is not null on json1 and is null on json2 then the node on json1 is made null.
*
* Non-existent Node Handling:
*
* Since json1 is intended to be a full representation of a complete JSON structure
* nodes on json2 that don't exist on json1 are completely ignored. Only if the same
* node exists on both json1 and json2 will the structures be merged.
*
* ArrayNode Handling
*
* If the node being compared is an ArrayNode then the elements on json2 are iterated
* over. If the index on json1 exists on json1 then the two elements are merged. If the
* index doesn't exist on json1 then the element is added to the ArrayNode on json1.
* Note: The existence of the element on json1 is determined by index and when an
* element is added to json1 it's index increases by one. That shouldn't be a problem
* though as for there to ever be more elements in json2, the index pointer will always
* be one larger than the max index of json1.
*
* ArrayNode Handling when json1 contains more elements than json2:
*
* Elements are removed from json1 if they have higher indexes than the size of json2
* minus 1
*
* @param json1
* @param json2
* @return
* @throws com.blackberry.bdp.common.exception.JsonMergeException
*/
public static JsonNode merge(JsonNode json1, JsonNode json2) throws JsonMergeException {
Iterator<String> json1Fields = json1.fieldNames();
LOG.info("Merged called on json1 ({}), json2 ({})", json1.getNodeType(), json2.getNodeType());
while (json1Fields.hasNext()) {
String nodeName = json1Fields.next();
JsonNode json1Node = json1.get(nodeName);
// Check if json2 has the node and run explicit null checks
if (!json2.has(nodeName)) {
LOG.info("Not comparing {} since it doesn't exist on json2", nodeName);
continue;
} else if (json1Node.isNull() && json2.hasNonNull(nodeName)) {
((ObjectNode) json1).replace(nodeName, json2.get(nodeName));
LOG.info("explicit null {} on json1 replaced with non-null from json2", nodeName);
continue;
} else if (json1.hasNonNull(nodeName) && json2.get(nodeName).isNull()) {
((ObjectNode) json1).replace(nodeName, json2.get(nodeName));
LOG.info("non-null {} on json1 replaced with explicitly null on json2", nodeName);
continue;
}
JsonNode json2Node = json2.get(nodeName);
if (json1Node.getNodeType().equals(json2Node.getNodeType()) == false) {
throw new JsonMergeException(String.format(
"json1 (%s) cannot be merged with json2 (%s)",
json1.getNodeType(), json2.getNodeType()));
}
LOG.info("need to compare \"{}\" which is a {}", nodeName, json1Node.getNodeType());
if (json1Node.isObject()) {
LOG.info("Calling merge on object {}", nodeName);
merge(json1Node, json2.get(nodeName));
} else if (json1Node instanceof ObjectNode) {
throw new JsonMergeException("{} is instance of ObjectNode and wasn't isObject()--what gives?!");
} else if (json1Node.isArray()) {
ArrayNode json1Array = (ArrayNode) json1Node;
ArrayNode json2Array = (ArrayNode) json2Node;
LOG.info("ArrayNode {} json1 has {} elements and json2 has {} elements",
nodeName, json1Array.size(), json2Array.size());
int indexNo = 0;
Iterator<JsonNode> json2Iter = json2Array.iterator();
while (json2Iter.hasNext()) {
JsonNode json2Element = json2Iter.next();
if (json1Array.has(indexNo)) {
LOG.info("Need to merge ArrayNode {} element {}", nodeName, indexNo);
merge(json1Node.get(indexNo), json2Element);
} else {
LOG.info("ArrayNode {} element {} not found on json1, adding", nodeName, indexNo);
json1Array.add(json2Element);
}
indexNo++;
}
while (json1Array.size() > json2Array.size()) {
int indexToRemove = json1Array.size() - 1;
json1Array.remove(indexToRemove);
LOG.info("ArrayNode {} index {} on json1 removed since greater than size of json2 ({})",
nodeName, indexToRemove, json2Array.size());
}
} else {
LOG.info("{} ({}) has fallen through known merge types", nodeName, json1Node.getNodeType());
((ObjectNode) json1).replace(nodeName, json2Node);
LOG.info("json1 node {} replaced with json2's node", nodeName);
}
}
return json1;
}
@ariens
Copy link
Author

ariens commented Oct 8, 2015

Outdated.

Disregard, expect an update with corresponding unit tests and Jackson PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment