Python で JSON をマージする方法と、Java(Jackson使用)でマージする方法

Python
PythonJSON をマージするのに誰でも思いつく簡単な方法は、
JSON を辞書(dict)として読みこんで、dict(dict1, **dict2) の方法でマージする方法である。
簡単なサンプル

import json

j1 = '''
{
    "a": 1,
    "ary": [ 10, 11 ],
    "b":{
        "b1": 20
    }
}
'''
j2 = '''
{
    "a": 2,
    "ary": [ 12, 13 ],
    "b":{
        "b2": 21
    }
}
'''
j3 = json.dumps(dict(json.loads(j1), **json.loads(j2)))
print(j3)

結果は、

{"a": 2, "ary": [12, 13], "b": {"b2": 21}}

これでは、ただの上書きだ!期待するのは、少なくとも配列は追加してほしい

{'a': 2, 'ary': [10, 11, 12, 13], 'b': {'b1': 20, 'b2': 21}}

dictknife · PyPI deepmerge というのを使えば、期待する結果を得ることができる。

from dictknife import deepmerge

j3 = deepmerge(json.loads(j1), json.loads(j2))

Java
Python と違って Java では、配列維持のマージだけでなく、
同じキーがマージ先に存在する場合、上書きではなく配列として追加するマージをすることも可能である。

Jackson を使用するので、JsonNode でマージを行う。。。

配列維持のマージ

public static JsonNode updateArrayMerge(JsonNode baseNode, JsonNode upNode) {
   ObjectNode node = JsonNodeFactory.instance.objectNode();
   Map<String, Integer> map = new HashMap<>();
   for(Iterator<String> it = baseNode.fieldNames(); it.hasNext();){
      map.put(it.next(), 1);
   }
   for(Iterator<String> it = upNode.fieldNames(); it.hasNext();){
      map.put(it.next(), 2);
   }
   map.entrySet().stream().forEach(e->{
      String key = e.getKey();
      if (e.getValue()==1){
         node.set(e.getKey(), baseNode.get(key));
      }else{
         JsonNode n = baseNode.get(key);
         if (n==null){
            node.set(key, upNode.get(key));
         }else{
            JsonNode n1 = baseNode.get(key);
            JsonNode n2 = upNode.get(key);
            if (n1.isArray() && n2.isArray()){
               ArrayNode array = JsonNodeFactory.instance.arrayNode();
               for(Iterator<JsonNode> it = n1.elements(); it.hasNext();){
                  array.add(it.next());
               }
               for(Iterator<JsonNode> it = n2.elements(); it.hasNext();){
                  array.add(it.next());
               }
               node.set(key, array);
            }else if(n1.isObject() && n2.isObject()){
               node.set(key, updateArrayMerge(n1, n2));
            }else{
               node.set(key, n2);
            }
         }
      }
   });
   return node;
}

Python と同じJSONでこのマージを実行すると

{"a":2,"b":{"b2":21,"b1":20},"ary":[10,11,12,13]}

同じキーがマージ先に存在する場合、上書きではなく配列として追加するマージ

public static JsonNode merge(JsonNode baseNode, JsonNode upNode) {
   ObjectNode node = JsonNodeFactory.instance.objectNode();
   Map<String, Boolean> map = new HashMap<>();
   for(Iterator<String> it = baseNode.fieldNames(); it.hasNext();){
      map.put(it.next(), false);
   }
   for(Iterator<String> it = upNode.fieldNames(); it.hasNext();){
      String s = it.next();
      map.put(s, map.get(s)==null ? false : true);
   }
   map.entrySet().stream().forEach(e->{
      String key = e.getKey();
      if (e.getValue()){
         JsonNode n = baseNode.get(key);
         if (n==null){
            node.set(key, upNode.get(key));
         }else{
            JsonNode n1 = baseNode.get(key);
            JsonNode n2 = upNode.get(key);
            ArrayNode array = JsonNodeFactory.instance.arrayNode();
            if (n1.isArray()) {
               for(Iterator<JsonNode> it = n1.elements(); it.hasNext();){
                  array.add(it.next());
               }
            }else{
               array.add(n1);
            }
            if (n2.isArray()) {
               for(Iterator<JsonNode> it = n2.elements(); it.hasNext();){
                  array.add(it.next());
               }
            }else{
               array.add(n2);
            }
            node.set(key, array);
         }
      }else{
         node.set(key, upNode.get(key));
      }
   });
   return node;
}

Python と同じJSONでこのマージを実行すると

{"a":[1,2],"b":[{"b1":20},{"b2":21}],"ary":[10,11,12,13]}

同じキー'a' に対しての上書きにならずに、配列としてマージすることができる。

今回のメソッドは、Git-Hub で公開している。
yipuran-jack/JsonClip.java at master · yipuran/yipuran-jack · GitHub