Is it possible to merge two JSON documents with the Jackson JSON library? I am basically using the Jackson mapper with simple Java Maps.
I've tried to search in Google and Jackson's documentation but couldn't find anything.
Is it possible to merge two JSON documents with the Jackson JSON library? I am basically using the Jackson mapper with simple Java Maps.
I've tried to search in Google and Jackson's documentation but couldn't find anything.
One way is to use ObjectReader
like so:
MyBean defaults = objectMapper.readValue(defaultJson, MyBean.class);
ObjectReader updater = objectMapper.readerForUpdating(defaults);
MyBean merged = updater.readValue(overridesJson);
which will combine data from two sources. This only makes a shallow copy, i.e. does not do recursive merge on contained objects.
Otherwise you may need to just read JSON as a tree (JsonNode
), loop over contents and merge manually. This often makes sense anyway since rules of merging are not trivial, and everyone has their own ideas of how merging should work.
EDIT: (03-Apr-2017)
As per @Fernando Correia's comment, there is actually a new feature added in upcoming Jackson 2.9 (to be released in April or May 2017) that does allow deep merging, finally.
Inspired by StaxMans answer I implemented this merging method.
public static JsonNode merge(JsonNode mainNode, JsonNode updateNode) {
Iterator<String> fieldNames = updateNode.fieldNames();
while (fieldNames.hasNext()) {
String fieldName = fieldNames.next();
JsonNode jsonNode = mainNode.get(fieldName);
// if field exists and is an embedded object
if (jsonNode != null && jsonNode.isObject()) {
merge(jsonNode, updateNode.get(fieldName));
}
else {
if (mainNode instanceof ObjectNode) {
// Overwrite field
JsonNode value = updateNode.get(fieldName);
((ObjectNode) mainNode).put(fieldName, value);
}
}
}
return mainNode;
}
Hope this helps someone.
thank you
comment, as of Jackson 2.4 put()
is deprecated and should be substituted with replace()
. Also, for JDK8 users, the code could be made more concise with trivial invoking of forEachRemaining()
directly on the iterator and passing in the somewhat shorter lambda-expression. –
Exerciser One way is to use ObjectReader
like so:
MyBean defaults = objectMapper.readValue(defaultJson, MyBean.class);
ObjectReader updater = objectMapper.readerForUpdating(defaults);
MyBean merged = updater.readValue(overridesJson);
which will combine data from two sources. This only makes a shallow copy, i.e. does not do recursive merge on contained objects.
Otherwise you may need to just read JSON as a tree (JsonNode
), loop over contents and merge manually. This often makes sense anyway since rules of merging are not trivial, and everyone has their own ideas of how merging should work.
EDIT: (03-Apr-2017)
As per @Fernando Correia's comment, there is actually a new feature added in upcoming Jackson 2.9 (to be released in April or May 2017) that does allow deep merging, finally.
Inspired by Arn's answer. Editing it to add the case where a node may have a array of nodes in it.
public static JsonNode merge(JsonNode mainNode, JsonNode updateNode) {
Iterator<String> fieldNames = updateNode.fieldNames();
while (fieldNames.hasNext()) {
String updatedFieldName = fieldNames.next();
JsonNode valueToBeUpdated = mainNode.get(updatedFieldName);
JsonNode updatedValue = updateNode.get(updatedFieldName);
// If the node is an @ArrayNode
if (valueToBeUpdated != null && valueToBeUpdated.isArray() &&
updatedValue.isArray()) {
// running a loop for all elements of the updated ArrayNode
for (int i = 0; i < updatedValue.size(); i++) {
JsonNode updatedChildNode = updatedValue.get(i);
// Create a new Node in the node that should be updated, if there was no corresponding node in it
// Use-case - where the updateNode will have a new element in its Array
if (valueToBeUpdated.size() <= i) {
((ArrayNode) valueToBeUpdated).add(updatedChildNode);
}
// getting reference for the node to be updated
JsonNode childNodeToBeUpdated = valueToBeUpdated.get(i);
merge(childNodeToBeUpdated, updatedChildNode);
}
// if the Node is an @ObjectNode
} else if (valueToBeUpdated != null && valueToBeUpdated.isObject()) {
merge(valueToBeUpdated, updatedValue);
} else {
if (mainNode instanceof ObjectNode) {
((ObjectNode) mainNode).replace(updatedFieldName, updatedValue);
}
}
}
return mainNode;
}
valueToBeUpdated
to an ArrayNode
is not safe. You can get a TextNode
, for instance, instead of an ArrayNode
in that variable. –
Sruti Below is an implementation in Scala. The source and target node are mostly commutative except when a branch exists in both source and target.
def mergeYamlObjects(source: ObjectNode, target: ObjectNode, overwrite: Boolean = true): ObjectNode = {
if (target == null)
source
else if (source == null)
target
else {
val result = source.deepCopy
val fieldlist = source.fieldNames.asScala.toList ++ target.fieldNames.asScala.toList
for (item <- fieldlist) {
if (!(source has item)) {
result put(item, target get item)
} else {
if ((source get item).isValueNode) {
if (target has item)
if (overwrite)
result.put(item, target get item)
} else {
result.put(item, mergeYamlObjects(source.get(item).asInstanceOf[ObjectNode],
target.get(item).asInstanceOf[ObjectNode], overwrite = overwrite))
}
}
}
result
}
}
If someone simply wants to add two or more JsonNode object into one JsonNode, this can be one approach:
ArrayNode arrayNode = objectMapper.createArrayNode();
arrayNode.add(firstJsonNode);
arrayNode.add(secondJsonNode);
arrayNode.add(thirdJsonNode);
JsonNode root = JsonNodeFactory.instance.objectNode();
((ObjectNode) root).put("", arrayNode);
System.out.println("merged array node #: " + root);
Here,is full implementation of merging two JSON tree into one. Hope it would be helpful :)
/**
* Merge two JSON tree into one i.e mergedInTo.
*
* @param toBeMerged
* @param mergedInTo
*/
public static void merge(JsonNode toBeMerged, JsonNode mergedInTo) {
Iterator<Map.Entry<String, JsonNode>> incomingFieldsIterator = toBeMerged.fields();
Iterator<Map.Entry<String, JsonNode>> mergedIterator = mergedInTo.fields();
while (incomingFieldsIterator.hasNext()) {
Map.Entry<String, JsonNode> incomingEntry = incomingFieldsIterator.next();
JsonNode subNode = incomingEntry.getValue();
if (subNode.getNodeType().equals(JsonNodeType.OBJECT)) {
boolean isNewBlock = true;
mergedIterator = mergedInTo.fields();
while (mergedIterator.hasNext()) {
Map.Entry<String, JsonNode> entry = mergedIterator.next();
if (entry.getKey().equals(incomingEntry.getKey())) {
merge(incomingEntry.getValue(), entry.getValue());
isNewBlock = false;
}
}
if (isNewBlock) {
((ObjectNode) mergedInTo).replace(incomingEntry.getKey(), incomingEntry.getValue());
}
} else if (subNode.getNodeType().equals(JsonNodeType.ARRAY)) {
boolean newEntry = true;
mergedIterator = mergedInTo.fields();
while (mergedIterator.hasNext()) {
Map.Entry<String, JsonNode> entry = mergedIterator.next();
if (entry.getKey().equals(incomingEntry.getKey())) {
updateArray(incomingEntry.getValue(), entry);
newEntry = false;
}
}
if (newEntry) {
((ObjectNode) mergedInTo).replace(incomingEntry.getKey(), incomingEntry.getValue());
}
}
ValueNode valueNode = null;
JsonNode incomingValueNode = incomingEntry.getValue();
switch (subNode.getNodeType()) {
case STRING:
valueNode = new TextNode(incomingValueNode.textValue());
break;
case NUMBER:
valueNode = new IntNode(incomingValueNode.intValue());
break;
case BOOLEAN:
valueNode = BooleanNode.valueOf(incomingValueNode.booleanValue());
}
if (valueNode != null) {
updateObject(mergedInTo, valueNode, incomingEntry);
}
}
}
private static void updateArray(JsonNode valueToBePlaced, Map.Entry<String, JsonNode> toBeMerged) {
toBeMerged.setValue(valueToBePlaced);
}
private static void updateObject(JsonNode mergeInTo, ValueNode valueToBePlaced,
Map.Entry<String, JsonNode> toBeMerged) {
boolean newEntry = true;
Iterator<Map.Entry<String, JsonNode>> mergedIterator = mergeInTo.fields();
while (mergedIterator.hasNext()) {
Map.Entry<String, JsonNode> entry = mergedIterator.next();
if (entry.getKey().equals(toBeMerged.getKey())) {
newEntry = false;
entry.setValue(valueToBePlaced);
}
}
if (newEntry) {
((ObjectNode) mergeInTo).replace(toBeMerged.getKey(), toBeMerged.getValue());
}
}
If your goal is to concatenate two JSONs, the simplest way I found is the following (considering you have two ObjecNode ready):
ObjectMapper mapper = new ObjectMapper();
// Dummy objects to concatenate
Map<String, Object> map = new HashMap();
map.put("k", "v");
Map<String, Object> secondMap = new HashMap();
secondMap.put("secondK", "secondV");
//Transforming Objects into ObjectNode
ObjectNode firstObjectNode = mapper.convertValue(map, ObjectNode.class);
ObjectNode secondObjectNode = mapper.convertValue(secondMap, ObjectNode.class);
//Concatenating secondObjectNode into firstObjectNode
firstObjectNode.setAll(secondObjectNode);
//Output will be: {"k":"v","secondK":"secondV"}
System.out.println(firstObjectNode);
Hope it helps
Merge json -> json with ObjectMapper:
@SneakyThrows
public static String mergeToPretty(String baseJson, String overrideJson) {
ObjectMapper objectMapper = new ObjectMapper();
Object base = objectMapper.readValue(baseJson, Object.class);
Object merged = objectMapper.readerForUpdating(base).readValue(overrideJson);
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(merged);
}
Notes:
@SneakyThrows
could be replaced with exception handling if not using Lombok..writerWithDefaultPrettyPrinter()
from last line.© 2022 - 2024 — McMap. All rights reserved.