2012-03-27 33 views
28

¿Es posible fusionar dos documentos JSON con la biblioteca Jackson JSON? Básicamente estoy usando el mapeador Jackson con mapas Java simples.Fusionando dos documentos JSON con Jackson

He intentado buscar en la documentación de Google y Jackson pero no he podido encontrar nada.

Respuesta

32

Una forma es utilizar ObjectReader así:

MyBean defaults = objectMapper.readValue(defaultJson, MyBean.class); 
ObjectReader updater = objectMapper.readerForUpdating(defaults); 
MyBean merged = updater.readValue(overridesJson); 

que combinará los datos de dos fuentes. Esto solo hace una copia superficial, es decir, no hace una fusión recursiva en objetos contenidos.

De lo contrario, es posible que deba leer JSON como á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)

De acuerdo con el comentario de @Fernando Correia, en realidad hay una nueva feature añadido en la próxima Jackson 2.9 (para ser lanzado en abril o mayo del 2017) que sí permite fusión profunda, finalmente.

+1

Gracias por su respuesta. Necesito una fusión profunda, por lo tanto, su sugerencia de escribir la fusión de forma manual muestra la sensación que tenía cuando publiqué la pregunta. Mis estructuras de datos subyacentes son Mapas, por lo tanto, vale la pena escribir una rutina genérica de fusión profunda para Java Maps. Puede ser reutilizable en algún proyecto futuro. – Danish

+0

¿está disponible la fusión profunda como una función ahora? – yathirigan

+0

No, no como de Jackson 2.6. – StaxMan

47

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.

+0

Se ve muy bien @Arn! Será útil si necesito hacer esto en un proyecto futuro. Cuando hice esta pregunta, estaba trabajando con elasticsearch y encontré un complemento de actualización parcial para él. Eso me ayudó en ese momento. – Danish

+3

Excelente, Arne, eso es exactamente lo que he necesitado, me has ahorrado horas de investigación, gracias. Para hacer esto un poco más que un comentario de 'gracias', a partir de Jackson 2.4 'put()' está en desuso y debería sustituirse por 'replace()'.Además, para los usuarios de JDK8, el código podría ser más conciso con invocación trivial de 'forEachRemaining()' directamente en el iterador y pasando la expresión lambda algo más corta. – quantum

7

Inspirado en 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; 
} 
+0

La conversión de 'valueToBeUpdated' a' ArrayNode' no es segura. Puede obtener un 'TextNode', por ejemplo, en lugar de un' ArrayNode' en esa variable. – PNS

+0

Gracias PNS. He editado mi respuesta y he agregado la condición para verificar si valueToBeUpdated es un arrayNode –

4

A continuación se detalla 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 
    } 
    } 
1

Si alguien simplemente quiere añadir dos o más objetos en una JsonNode JsonNode, esto puede ser uno de los enfoques:

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); 
0

Aquí, es la plena aplicación de la fusión de dos árboles JSON en una sola. 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()); 
    } 
}