72

I am consuming some JSON from two different sources, I end up with two JSONObjects and I'd like to combine them into one.

Data:

"Object1": {
    "Stringkey":"StringVal",
    "ArrayKey": [Data0, Data1]
}

"Object2": {
    "Stringkey":"StringVal",
    "Stringkey":"StringVal",
    "Stringkey":"StringVal",
}

Code, using http://json.org/java/ library:

// jso1 and jso2 are some JSONObjects already instantiated
JSONObject Obj1 = (JSONObject) jso.get("Object1");
JSONObject Obj2 = (JSONObject) jso.get("Object2");

So in this situation I'd like to combine Obj1 and Obj2, either to make a totally new JSONObject or concat one to the other. Any ideas besides pulling them all apart and individually adding in by puts?

Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277
DanInDC
  • 5,019
  • 8
  • 31
  • 25
  • Related: [Merging YAML](https://github.com/OndraZizka/yaml-merge/blob/master/src/main/java/org/cobbzilla/util/yml/YmlMerger.java#L101) – Ondra Žižka Dec 17 '18 at 16:53
  • 1
    Maybe there are examples here that would work for this? https://www.baeldung.com/java-merge-maps I mention because NONE of the below answers give a solution using Java stream. – djangofan Oct 07 '19 at 19:38

17 Answers17

64

If you want a new object with two keys, Object1 and Object2, you can do:

JSONObject Obj1 = (JSONObject) jso1.get("Object1");
JSONObject Obj2 = (JSONObject) jso2.get("Object2");
JSONObject combined = new JSONObject();
combined.put("Object1", Obj1);
combined.put("Object2", Obj2);

If you want to merge them, so e.g. a top level object has 5 keys (Stringkey1, ArrayKey, StringKey2, StringKey3, StringKey4), I think you have to do that manually:

JSONObject merged = new JSONObject(Obj1, JSONObject.getNames(Obj1));
for(String key : JSONObject.getNames(Obj2))
{
  merged.put(key, Obj2.get(key));
}

This would be a lot easier if JSONObject implemented Map, and supported putAll.

Matthew Flaschen
  • 278,309
  • 50
  • 514
  • 539
  • 2
    I am trying to use your second code snippet in Android, but I don't see a static getNames function on JSONObject. Was this added in a newer version of the org.json library? – Austyn Mahoney Sep 26 '12 at 18:31
  • 2
    @AustynMahoney, not sure about the history, but for Android you can use the instance method [`JSONObject.names`](http://developer.android.com/reference/org/json/JSONObject.html#names%28%29). – Matthew Flaschen Oct 06 '12 at 16:29
  • @AustynMahoney It was not there in android json library i tried it already. It was not mentioned in android documentation also. It was only mentioned here http://www.json.org/javadoc/org/json/JSONObject.html – kishore Jan 07 '14 at 08:41
  • 2
    JSONObject.getNames() method will return null if jsonobject is empty. You need to check for this condition as well. – Ehsan Feb 26 '18 at 17:52
31

In some cases you need a deep merge, i.e., merge the contents of fields with identical names (just like when copying folders in Windows). This function may be helpful:

/**
 * Merge "source" into "target". If fields have equal name, merge them recursively.
 * @return the merged object (target).
 */
public static JSONObject deepMerge(JSONObject source, JSONObject target) throws JSONException {
    for (String key: JSONObject.getNames(source)) {
            Object value = source.get(key);
            if (!target.has(key)) {
                // new value for "key":
                target.put(key, value);
            } else {
                // existing value for "key" - recursively deep merge:
                if (value instanceof JSONObject) {
                    JSONObject valueJson = (JSONObject)value;
                    deepMerge(valueJson, target.getJSONObject(key));
                } else {
                    target.put(key, value);
                }
            }
    }
    return target;
}



/**
 * demo program
 */
public static void main(String[] args) throws JSONException {
    JSONObject a = new JSONObject("{offer: {issue1: value1}, accept: true}");
    JSONObject b = new JSONObject("{offer: {issue2: value2}, reject: false}");
    System.out.println(a+ " + " + b+" = "+JsonUtils.deepMerge(a,b));
    // prints:
    // {"accept":true,"offer":{"issue1":"value1"}} + {"reject":false,"offer":{"issue2":"value2"}} = {"reject":false,"accept":true,"offer":{"issue1":"value1","issue2":"value2"}}
}
Erel Segal-Halevi
  • 33,955
  • 36
  • 114
  • 183
  • 2
    What if a value within 'source' is of type JSONArray?. In this case, we'd need to merge this array with the equivalent JSONArray of the target object. – pgoldweic Aug 30 '17 at 18:22
  • One thing that I had to add for this code to work with nested empty objects - if(source.isEmpty() || source.length()==0){ return target.toString(); } – adeveloper Feb 17 '22 at 21:48
24

You can create a new JSONObject like this:

JSONObject merged = new JSONObject();
JSONObject[] objs = new JSONObject[] { Obj1, Obj2 };
for (JSONObject obj : objs) {
    Iterator it = obj.keys();
    while (it.hasNext()) {
        String key = (String)it.next();
        merged.put(key, obj.get(key));
    }
}

With this code, if you have any repeated keys between Obj1 and Obj2 the value in Obj2 will remain. If you want the values in Obj1 to be kept you should invert the order of the array in line 2.

SBerg413
  • 14,515
  • 6
  • 62
  • 88
Santi P.
  • 473
  • 2
  • 5
  • I didn't know about the static method JSONObject.getNames, it makes the code much simpler, see Matthew's answer below which reduces the code using it. – Santi P. Mar 08 '10 at 20:38
  • In the first line add `new` before `JSONObject()`, edit isn't allowed for below 6 characters. – Anas Azeem Jul 04 '14 at 07:01
6

This wrapper method will help :

private static JSONObject merge(JSONObject... jsonObjects) throws JSONException {

    JSONObject jsonObject = new JSONObject();

    for(JSONObject temp : jsonObjects){
        Iterator<String> keys = temp.keys();
        while(keys.hasNext()){
            String key = keys.next();
            jsonObject.put(key, temp.get(key));
        }

    }
    return jsonObject;
}
Dave Ranjan
  • 2,966
  • 24
  • 55
4

Thanks to Erel. Here is a Gson version.

/**
 * Merge "source" into "target". If fields have equal name, merge them recursively.
 * Null values in source will remove the field from the target.
 * Override target values with source values
 * Keys not supplied in source will remain unchanged in target
 * 
 * @return the merged object (target).
 */
public static JsonObject deepMerge(JsonObject source, JsonObject target) throws Exception {

    for (Map.Entry<String,JsonElement> sourceEntry : source.entrySet()) {
        String key = sourceEntry.getKey();
        JsonElement value = sourceEntry.getValue();
        if (!target.has(key)) {
            //target does not have the same key, so perhaps it should be added to target
            if (!value.isJsonNull()) //well, only add if the source value is not null
            target.add(key, value);
        } else {
            if (!value.isJsonNull()) {
                if (value.isJsonObject()) {
                    //source value is json object, start deep merge
                    deepMerge(value.getAsJsonObject(), target.get(key).getAsJsonObject());
                } else {
                    target.add(key,value);
                }
            } else {
                target.remove(key);
            }
        }
    }
    return target;
}



/**
 * simple test
 */
public static void main(String[] args) throws Exception {
    JsonParser parser = new JsonParser();
    JsonObject a = null;
    JsonObject b = null;
    a = parser.parse("{offer: {issue1: null, issue2: null}, accept: true, reject: null}").getAsJsonObject();
    b = parser.parse("{offer: {issue2: value2}, reject: false}").getAsJsonObject();
    System.out.println(deepMerge(a,b));
    // prints:
    // {"offer":{},"accept":true}
    a = parser.parse("{offer: {issue1: value1}, accept: true, reject: null}").getAsJsonObject();
    b = parser.parse("{offer: {issue2: value2}, reject: false}").getAsJsonObject();
    System.out.println(deepMerge(a,b));
    // prints:
    // {"offer":{"issue2":"value2","issue1":"value1"},"accept":true}

}
3

This is what I do

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

/**
 * This class has all static functions to merge 2 objects into one
 */
public class MergeHelper {
    private static ObjectMapper objectMapper = new ObjectMapper();

    /**
     * return a merge JsonNode, merge newJson into oldJson; override or insert
     * fields from newJson into oldJson
     * 
     * @param oldJson
     * @param newJson
     * @return
     */
    public static JsonNode mergeJsonObject(JsonNode oldJson, JsonNode newJson) {
        ObjectNode merged = objectMapper.createObjectNode();
        merged.setAll((ObjectNode) oldJson);
        merged.setAll((ObjectNode) newJson);
        return merged;
    }
}
Aaron Lam
  • 31
  • 1
  • Code only answers are discouraged. Please add some explanation as to how this solves the problem, or how this differs from the existing answers. [From Review](https://stackoverflow.com/review/late-answers/24531782) – Nick Nov 09 '19 at 00:55
2

A ready method to merge any number of JSONObjects:

/**
 * Merges given JSONObjects. Values for identical key names are merged 
 * if they are objects, otherwise replaced by the latest occurence.
 *
 * @param jsons JSONObjects to merge.
 *
 * @return Merged JSONObject.
 */
public static JSONObject merge(
  JSONObject[] jsons) {

  JSONObject merged = new JSONObject();
  Object parameter;

  for (JSONObject added : jsons) {

    for (String key : toStringArrayList(added.names())) {
      try {

        parameter = added.get(key);

        if (merged.has(key)) {
          // Duplicate key found:
          if (added.get(key) instanceof JSONObject) {
            // Object - allowed to merge:
            parameter =
              merge(
                new JSONObject[]{
                  (JSONObject) merged.get(key),
                  (JSONObject) added.get(key)});
          }
        }

        // Add or update value on duplicate key:
        merged.put(
          key,
          parameter);

      } catch (JSONException e) {
        e.printStackTrace();
      }
    }

  }

  return merged;
}

/**
 * Convert JSONArray to ArrayList<String>.
 *
 * @param jsonArray Source JSONArray.
 *
 * @return Target ArrayList<String>.
 */
public static ArrayList<String> toStringArrayList(JSONArray jsonArray) {

  ArrayList<String> stringArray = new ArrayList<String>();
  int arrayIndex;

  for (
    arrayIndex = 0;
    arrayIndex < jsonArray.length();
    arrayIndex++) {

    try {
      stringArray.add(
        jsonArray.getString(arrayIndex));
    } catch (JSONException e) {
      e.printStackTrace();
    }
  }

  return stringArray;
}
Zon
  • 18,610
  • 7
  • 91
  • 99
  • for JSONArray else if (added.get(key) instanceof JSONArray && merged.get(key) instanceof JSONArray) { JSONArray mkeyArray = merged.getJSONArray(key); JSONArray akeyArray = added.getJSONArray(key); for (int i=0;i – HiteshGs Sep 26 '19 at 13:25
1

For me that function worked:

private static JSONObject concatJSONS(JSONObject json, JSONObject obj) {
    JSONObject result = new JSONObject();

    for(Object key: json.keySet()) {
        System.out.println("adding " + key + " to result json");
        result.put(key, json.get(key));
    }

    for(Object key: obj.keySet()) {
        System.out.println("adding " + key + " to result json");
        result.put(key, obj.get(key));
    }

    return result;
}

(notice) - this implementation of concataion of json is for import org.json.simple.JSONObject;

noam aghai
  • 1,364
  • 3
  • 18
  • 30
1

Somebody already mentioned above. I'll just post a short version.

To merge two JSONObject json1 & json2 You could simply deal it with String like this:

String merged = json1.toString().substring(0, json1.length() - 1) + "," +
json2.toString().substring(1);
JSONObject mergedJson = new JSONObject(merged);

Of course, do not forget deal with JSONException. :) Hope this could help you.

Kael luo
  • 79
  • 1
  • 5
1

It's a while from the question but now JSONObject implements "toMap" method so you can try this way:

Map<String, Object> map = Obj1.toMap();      //making an HashMap from obj1
map.putAll(Obj2.toMap());                    //moving all the stuff from obj2 to map
JSONObject combined = new JSONObject( map ); //new json from map
lunix15
  • 84
  • 1
  • 6
0

In addition to @erel's answer, I had to make this edit (I'm using org.json.simple) to the outer else for dealing with JSONArray's:

            // existing value for "key" - recursively deep merge:
            if (value instanceof JSONObject) {
                JSONObject valueJson = (JSONObject)value;
                deepMerge(valueJson, (JSONObject) target.get(key));
            } 

            // insert each JSONArray's JSONObject in place
            if (value instanceof JSONArray) {
                ((JSONArray) value).forEach(
                    jsonobj ->
                    ((JSONArray) target.get(key)).add(jsonobj));
            }
            else {
                target.put(key, value);
            }
jonayreyes
  • 538
  • 1
  • 10
  • 27
0

I used string to concatenate new object to an existing object.


private static void concatJSON() throws IOException, InterruptedException {

    JSONParser parser = new JSONParser();
    Object obj = parser.parse(new FileReader(new File(Main.class.getResource("/file/user.json").toURI())));


    JSONObject jsonObj = (JSONObject) obj; //usernameJsonObj

    String [] values = {"0.9" , Date.from(Calendar.getInstance().toInstant()).toLocaleString()},
            innermost = {"Accomplished", "LatestDate"}, 
            inner = {"Lesson1", "Lesson2", "Lesson3", "Lesson4"};
    String in = "Jayvee Villa";

    JSONObject jo1 = new JSONObject();
    for (int i = 0; i < innermost.length; i++)
        jo1.put(innermost[i], values[i]);

    JSONObject jo2 = new JSONObject();
    for (int i = 0; i < inner.length; i++)
        jo2.put(inner[i], jo1);

    JSONObject jo3 = new JSONObject();
    jo3.put(in, jo2);

    String merger = jsonObj.toString().substring(0, jsonObj.toString().length()-1) + "," +jo3.toString().substring(1);

    System.out.println(merger);
    FileWriter pr = new FileWriter(file);
    pr.write(merger);
    pr.flush();
    pr.close();
}
Manos Nikolaidis
  • 21,608
  • 12
  • 74
  • 82
0

Merging typed data structure trees is not trivial, you need to define the precedence, handle incompatible types, define how they will be casted and merged...

So in my opinion, you won't avoid

... pulling them all apart and individually adding in by puts`.

If your question is: Has someone done it for me yet? Then I think you can have a look at this YAML merging library/tool I revived. (YAML is a superset of JSON), and the principles are applicable to both.
(However, this particular code returns YAML objects, not JSON. Feel free to extend the project and send a PR.)

Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277
0

Today, I was also struggling to merge JSON objects and came with following solution (uses Gson library).

private JsonObject mergeJsons(List<JsonObject> jsonObjs) {
    JsonObject mergedJson = new JsonObject();
    jsonObjs.forEach((JsonObject jsonObj) -> {
        Set<Map.Entry<String, JsonElement>> entrySet = jsonObj.entrySet();
        entrySet.forEach((next) -> {
            mergedJson.add(next.getKey(), next.getValue());
        });
    });
    return mergedJson;
}
Ismet
  • 11
  • 1
  • 2
0
An improved version of merge on Gson's JsonObjects - can go any level of nested structure
 /**
 * Merge "source" into "target". 
 * 
 * <pre>
 *     An improved version of merge on Gson's JsonObjects - can go any level of nested structure:
 *             1. merge root & nested attributes.
 *             2. replace list of strings. For. eg.
 *              source -> "listOfStrings": ["A!"]
 *              dest -> "listOfStrings": ["A", "B"]
 *              merged -> "listOfStrings": ["A!", "B"]
 *             3. can merge nested objects inside list. For. eg.
 *              source -> "listOfObjects": [{"key2": "B"}]
 *              dest -> "listOfObjects": [{"key1": "A"}]
 *              merged -> "listOfObjects": [{"key1": "A"}, {"key2": "B"}]
 * </pre>
 * @return the merged object (target).
 */
public static JsonObject deepMerge(JsonObject source, JsonObject target) {
    for (String key: source.keySet()) {
        JsonElement srcValue = source.get(key);
        if (!target.has(key)) {
            target.add(key, srcValue);
        } else {
            if (srcValue instanceof JsonArray) {
                JsonArray srcArray = (JsonArray)srcValue;
                JsonArray destArray = target.getAsJsonArray(key);
                if (destArray == null || destArray.size() == 0) {
                    target.add(key, srcArray);
                    continue;
                } else {
                    IntStream.range(0, srcArray.size()).forEach(index -> {
                        JsonElement srcElem = srcArray.get(index);
                        JsonElement destElem = null;
                        if (index < destArray.size()) {
                            destElem = destArray.get(index);
                        }
                        if (srcElem instanceof JsonObject) {
                            if (destElem == null) {
                                destElem = new JsonObject();
                            }
                            deepMerge((JsonObject) srcElem, (JsonObject) destElem);
                        } else {
                            destArray.set(index, srcElem);
                        }
                    });
                }
            } else if (srcValue instanceof JsonObject) {
                JsonObject valueJson = (JsonObject)srcValue;
                deepMerge(valueJson, target.getAsJsonObject(key));
            } else {
                target.add(key, srcValue);
            }
        }
    }
    return target;
}
Maqbool Ahmed
  • 316
  • 2
  • 7
  • I am trying to use the above method. Trying to determine what changes I need to make when either the source or the target is an array and the other - target or source - is not. If you have any idea or thoughts, please let me know. Thanks – adbdkb Mar 18 '21 at 23:44
  • The method is checking whether srcValue is an array [Line 6], inside you can check whether dest is an object or an array and act accordingly. – Maqbool Ahmed Mar 22 '21 at 07:33
0

Here is a version of https://stackoverflow.com/a/15070484/9897317 including JSONArrays :

/**
 * Merge "source" into "target". If fields have equal name, merge them recursively.
 * @return the merged object (target).
 */
public static JSONObject deepMerge(JSONObject source, JSONObject target) throws JSONException {
    for (String key: JSONObject.getNames(source)) {
        Object value = source.get(key);
        if (value != null) {
            if (!target.has(key)) {
                // new value for "key":
                target.put(key, value);
            } else {
                // existing value for "key" - recursively deep merge:
                if (value instanceof JSONObject) {
                    JSONObject valueJson = (JSONObject)value;
                    deepMerge(valueJson, target.getJSONObject(key));
                } else if (value instanceof JSONArray) {
                    JSONArray arr = (JSONArray) value;
                    JSONArray newArr = new JSONArray();
                    for (int i = 0; i < arr.length(); i++) {
                        newArr.put(deepMerge(arr.getJSONObject(i), target.getJSONArray(key).getJSONObject(i)));
                    }
                    target.put(key, newArr);
                } else {
                    target.put(key, value);
                }
            }
        }
    }
    return target;
}
0

After I found myself coming back to this question more often than I wanted to, I decided to write my own util function to solve this. The function merges two JSONObjects and works even if one of them is null.

private JSONObject mergeJson(JSONObject oldJson, JSONObject newJson) {
        if (Objects.isNull(newJson)) {
            return oldJson;
        }
        if(Objects.isNull(oldJson)){
            return newJson;
        }
        for (String key : JSONObject.getNames(newJson)) {
            oldJson.put(key, newJson.get(key));
        }
        return oldJson;
    }
Anomitra
  • 1,111
  • 15
  • 31