java - example - Fusionando dos documentos JSON usando Jackson
objectmapper java (6)
¿Es posible fusionar dos documentos JSON con la biblioteca Jackson JSON? Básicamente estoy usando el mapeador Jackson con mapas Java simples.
Intenté buscar en Google y en la documentación de Jackson pero no pude encontrar nada.
A continuación se muestra una implementación en Scala. El nodo fuente y el destino son principalmente conmutativos, excepto cuando existe una rama tanto en origen como en destino.
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
}
}
Aquí, se implementa completamente la fusión de dos árboles JSON en uno. Espero que sea útil :)
/**
* 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());
}
}
Inspirado por la respuesta de Arn. Ediándolo para agregar el caso donde un nodo puede tener una matriz de nodos.
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;
}
Inspirado por la respuesta de StaxMans, implementé este método de fusión.
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;
}
Espero que esto ayude a alguien.
Si alguien simplemente quiere agregar dos o más objetos JsonNode en un JsonNode, este puede ser un enfoque:
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);
Una forma es usar ObjectReader
así:
MyBean defaults = objectMapper.readValue(defaultJson, MyBean.class);
ObjectReader updater = objectMapper.readerForUpdating(defaults);
MyBean merged = updater.readValue(overridesJson);
que combinará datos de dos fuentes. Esto solo hace una copia superficial, es decir, no hace una fusión recursiva en objetos contenidos.
De lo contrario, puede que necesite leer JSON como un árbol ( JsonNode
), recorrer el contenido y fusionar manualmente. Esto a menudo tiene sentido de todos modos, ya que las reglas de fusión no son triviales, y todos tienen sus propias ideas sobre cómo debería funcionar la fusión.
EDITAR : (03-abr-2017)
Según el comentario de @Fernando Correia, en realidad hay una nueva feature añadida en el próximo Jackson 2.9 (que se lanzará en abril o mayo de 2017) que permite una fusión profunda, finalmente.