2012-03-27 8 views
28

Ist es möglich, zwei JSON-Dokumente mit der Jackson JSON-Bibliothek zusammenzuführen? Ich benutze im Grunde den Jackson Mapper mit einfachen Java Maps.Zusammenführen von zwei JSON-Dokumenten mit Jackson

Ich habe versucht, in Google und Jackson-Dokumentation zu suchen, konnte aber nichts finden.

Antwort

32

Eine Möglichkeit ist ObjectReader wie so zu verwenden:

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

die Daten aus zwei Quellen kombinieren. Dies macht nur eine flache Kopie, d. H. Führt keine rekursive Zusammenführung bei enthaltenen Objekten durch.

Andernfalls müssen Sie möglicherweise nur JSON als Baum (JsonNode) lesen, den Inhalt durchlaufen und manuell zusammenführen. Dies ist oft sinnvoll, da die Regeln der Zusammenführung nicht trivial sind und jeder seine eigenen Vorstellungen davon hat, wie das Zusammenführen funktionieren soll.

EDIT: (03-Apr-2017)

Per @Fernando Correia Kommentar, gibt es tatsächlich eine neues feature in der kommenden Jackson hinzugefügt 2,9 (im April oder Mai 2017 veröffentlicht werden), mit denen tun tiefe Verschmelzung, endlich.

+1

Vielen Dank für Ihre Antwort. Ich brauche eine tiefe Zusammenführung, daher sind Sie Vorschlag, die Zusammenführung manuell zu schreiben echos das Bauchgefühl, das ich hatte, als ich die Frage veröffentlichte. Meine zugrunde liegenden Datenstrukturen sind Maps, daher sollte es sich lohnen, eine generische Deep-Merge-Routine für Java Maps zu schreiben. Es könnte sich in einem zukünftigen Projekt als wiederverwendbar erweisen. – Danish

+0

ist Deep Merge jetzt als Feature verfügbar? – yathirigan

+0

Nein, nicht wie von Jackson 2.6. – StaxMan

47

Inspiriert von StaxMans Antwort habe ich diese Methode implementiert.

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; 
} 

Hoffe, das hilft jemandem.

+0

Sieht ganz gut aus @Arn! Es wird hilfreich sein, wenn ich dies in einem zukünftigen Projekt tun muss. Als ich diese Frage gestellt habe, habe ich mit elasticsearch gearbeitet und ein Teilupdate-Plugin dafür gefunden. Das hat mir damals geholfen. – Danish

+3

Ausgezeichnet, Arne, genau das habe ich gebraucht, du hast mir Stunden der Nachforschungen erspart, danke. Um dies ein wenig mehr als 'Dank 'Kommentar, wie von Jackson 2.4' Put() 'ist veraltet und sollte ersetzt werden mit' ersetzen() '.Auch für JDK8-Benutzer könnte der Code durch triviales Aufrufen von 'forEachRemaining()' direkt auf dem Iterator prägnanter gemacht werden und der etwas kürzere Lambda-Ausdruck übergeben werden. – quantum

7

Inspiriert von Arns Antwort. Bearbeiten Sie es, um den Fall hinzuzufügen, in dem ein Knoten ein Array von Knoten enthalten kann.

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

Die Umwandlung von 'valueToBeUpdated' in einen' ArrayNode' ist nicht sicher. Sie können beispielsweise einen 'TextNode' anstelle eines' ArrayNode' in dieser Variable erhalten. – PNS

+0

Danke PNS. Ich habe meine Antwort bearbeitet und die Bedingung hinzugefügt, um zu überprüfen, ob valueToBeUpdated ein arrayNode ist –

4

Unten ist eine Implementierung in Scala. Der Quellen- und der Zielknoten sind meistens kommutativ, außer wenn ein Zweig sowohl in der Quelle als auch im Ziel existiert.

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

Wenn jemand will einfach zwei oder mehr JsonNode Objekt in einen JsonNode hinzuzufügen, kann dies ein Ansatz sein:

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

Hier ist die vollständige Umsetzung der Zusammenführung von zwei JSON Baum in eine. Hoffe, es wäre hilfreich :)

/** 
* 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()); 
    } 
}