0

Given this JSON:

{
  "contact_data": [
    "address/all",
    "telephone/us"
  ],
  "financial_data": [
    "bank_account_number/all",
    "bank_account_number/uk",
    "credit_card_numbers"
  ]
}

and this JSON:

{
  "financial_data": [
    "credit_card_numbers",
    "bank_account_number/ca",
    "bank_account_number/all"
  ],
  "government_id": [
    "driving_license/americas"
  ],
  "sensitive_data": [
    "racial_ethnic_origin"
  ]
}

I want to merge these to look like this:

{
  "contact_data": [
    "address/all",
    "telephone/us"
  ],
  "financial_data": [
    "credit_card_numbers",
    "bank_account_number/ca",
    "bank_account_number/uk",
    "bank_account_number/all"
  ],
  "government_id": [
    "driving_license/americas"
  ],
  "sensitive_data": [
    "racial_ethnic_origin"
  ]
}

I have the following, which almost works:

import org.json.JSONObject;
...
final List<String> jsonStrings = ...; // A list of the above sample JSONs
final List<JSONObject> jsonObjects = jsonStrings
      .stream()
      .map(JSONObject::new)
      // JSONObject.getNames() (called later on) will return null if JSONObject is empty, so filter out empty objects.
      .filter(jsonObject -> !jsonObject.isEmpty()) 
      .collect(Collectors.toList());
if (jsonObjects..size() > 1) {
    // Merge multiple JSONObjects: https://stackoverflow.com/a/2403453/12177456
    final JSONObject firstJsonObject = jsonObjects.get(0);
    final JSONObject merged = new JSONObject(firstJsonObject, JSONObject.getNames(firstJsonObject));
    final List<JSONObject> remainingJsonObjects = jsonObjects.subList(1, jsonObjects.size());
    for (final JSONObject nextJsonObject : remainingJsonObjects) {
        for (final String nextJsonObjectFieldName : JSONObject.getNames(nextJsonObject)) {
            merged.put(nextJsonObjectFieldName, nextJsonObject.get(nextJsonObjectFieldName));
        }
    }
    return merged;
} 

however, where I would expect to see 4 entries in financial_data:

...
"financial_data": [
    "bank_account_number/all",
    "bank_account_number/uk",
    "bank_account_number/ca",
    "credit_card_numbers"
  ]
...

instead, I see just 3, with bank_account_number/uk not in the merged result:

...
"financial_data": [
    "bank_account_number/all",
    "bank_account_number/ca",
    "credit_card_numbers"
  ]
...

I'm not stuck on using org.json, if it's simplier using gson, jackson, plain Java maps, I'm ok with that.

rmf
  • 625
  • 2
  • 9
  • 39

2 Answers2

1

Actually, it won't work. The problem is here:

merged.put(nextJsonObjectFieldName, nextJsonObject.get(nextJsonObjectFieldName))

This replaces the entry with key nextJsonObjectFieldName with the later one. That is why you're getting in the financial_data from the second object.

{
    "financial_data": [
        "credit_card_numbers",
        "bank_account_number/ca",
        "bank_account_number/all"
    ]
}

You are seeing other keys okay because other keys hase exact same value in both JSON's. If you change the value of other keys too in the second json, you'll see it the merged JSON will have the values with the same key from second json. That is, IT WON'T WORK.

What you can do is, you can check if the key has already value in the map or not. If there's no existing JSONobject for this key, just put it into the merged as you're doing. Otherwise, we have some more job to do:

JSONObject jsonObject = merged.get(nextJsonObjectFieldName);
if (jsonObject != null) {
    final JSONObject finalObj = mergeJsonObjectCheckingFieldValues(jsonObject, nextJsonObject.get(nextJsonObjectFieldName));
    merged.put(nextJsonObjectFieldName, finalObj);
} else {
    merged.put(nextJsonObjectFieldName, nextJsonObject.get(nextJsonObjectFieldName));
}

The mergeJsonObjectCheckingFieldValues method checks each element between the given two JSONObject and compares whether they are same. As per your example and for simply answering this question, I've assumed that each of the JSONObject is nothing but a list of String. For this, we'll be needing objectMapper. So make sure you have the com.fasterxml.jackson.databind.ObjectMapper in your project. So, the checking will be:

ObjectMapper mapper = new ObjectMapper();

public JSONObject mergeJsonObjectCheckingFieldValues(JSONObject jsonObject, JSONObject nextJsonObject)) {
    List<String> existingList = Arrays.asList(mapper
                    .readValue(jsonObject.toString(), String[].class));

    List<String> newList = Arrays.asList(mapper
                    .readValue(nextJsonObject.toString(), String[].class));
    
    List<String> toBeAdded = newList
                                .stream()
                                .filter(x -> !existingList.contains(x))
                                .collect(Collectors.toList());

    if(toBeAdded.size() > 0) {
        existingList.addAll(toBeAdded);
    }
    return new JSONObject(JSONArray.toJSONString(existingList));
}

This is the probable solution to your problem. I haven't tested it, but the code should be pretty much this.

devReddit
  • 2,696
  • 1
  • 5
  • 20
  • Thanks, I don't see this method though: `org.json.JSONArray.toJSONString`? – rmf Jul 21 '21 at 17:53
  • 1
    it is under `org.json.simple` package – devReddit Jul 21 '21 at 17:59
  • I'm using org.json.JSONObject - is your example using org.json.simple.JSON? e.g. I'm calling methods like JSONObject.getNames(firstJsonObject) - do I need to rewrite these to use org.json.simple.JSON? – rmf Jul 21 '21 at 18:11
  • No, that org.json.simple.JSONArray.toJSONString() return String, and org.json.JSONObject can accept that. So no other changes here. – devReddit Jul 21 '21 at 18:19
  • Seems you're using `org.json.simple` here etc: `JSONObject jsonObject = merged.get(nextJsonObjectFieldName);` – rmf Jul 21 '21 at 18:48
0

I went with the accepted answer here using gson:

https://stackoverflow.com/a/34092374/12177456

  • Handles recursively merging JsonObjects
  • Handles conflicts when the key exists in both maps
  • Has special handling for non-primitive values such as Arrays
rmf
  • 625
  • 2
  • 9
  • 39