6

I have a JSON string that I get from a database which contains repeated keys. I want to remove the repeated keys by combining their values into an array.

For example

Input

{
"a":"b",
"c":"d",
"c":"e",
"f":"g"
}

Output

{
"a":"b",
"c":["d","e"],
"f":"g"
}

The actual data is a large file that may be nested. I will not know ahead of time what or how many pairs there are.

I need to use Java for this. org.json throws an exception because of the repeated keys, gson can parse the string but each repeated key overwrites the last one. I need to keep all the data.

If possible, I'd like to do this without editing any library code

adamF
  • 961
  • 2
  • 9
  • 23
  • 1
    JSON Objects cannot contain 2 items with the same key. In your example json, the first appearance of c will be overridden. You can see this by validating your json at http://jsonlint.com/ – Mike Elofson Jun 25 '14 at 19:33
  • Technically, the stuff you're getting from the database isn't "JSON" then. You'll need to either reevaluate how that data is getting written in, or more likely, write some kind of parser for your data yourself. I can't think of a library that would be expecting to transform the data that way. – Tejs Jun 25 '14 at 19:33
  • According to this question, it is valid, although the parsers don't allow it for obvious reasons. I dont really care if its not valid JSON as long as I can convert the string format with repeated values into a string format without repeated values http://stackoverflow.com/questions/21832701/does-json-syntax-allow-duplicate-keys-in-an-object – adamF Jun 25 '14 at 19:38

4 Answers4

8

As of today the org.json library version 20170516 provides accumulate() method that stores the duplicate key entries into JSONArray

JSONObject jsonObject = new JSONObject();
jsonObject.accumulate("a", "b");
jsonObject.accumulate("c", "d");
jsonObject.accumulate("c", "e");
jsonObject.accumulate("f", "g");
System.out.println(jsonObject);

Output:

{  
    "a":"b",  
    "c":["d","e"],  
    "f":"g"  
}
jannis
  • 4,843
  • 1
  • 23
  • 53
Kainix
  • 1,186
  • 3
  • 21
  • 33
2

I want to remove the repeated keys by combining their values into an array.

Think other than JSON parsing library. It's very simple Java Program using String.split() method that convert Json String into Map<String, List<String>> without using any library.

Sample code:

String jsonString = ...
// remove enclosing braces and double quotes
jsonString = jsonString.substring(2, jsonString.length() - 2);

Map<String, List<String>> map = new HashMap<String, List<String>>();
for (String values : jsonString.split("\",\"")) {
    String[] keyValue = values.split("\":\"");
    String key = keyValue[0];
    String value = keyValue[1];

    if (!map.containsKey(key)) {
        map.put(key, new ArrayList<String>());
    }
    map.get(key).add(value);
}

output:

{
  "f": ["g"],
  "c": ["d","e"],
  "a": ["b"]
}
Braj
  • 46,415
  • 5
  • 60
  • 76
1

In order to accomplish what you want, you need to create some sort of custom class since JSON cannot technically have 2 values at one key. Below is an example:

public class SomeClass {

Map<String, List<Object>> values = new HashMap<String, List<Object>>();

public void add(String key, Object o) {
    List<Object> value = new ArrayList<Object>();
    if (values.containsKey(key)) {
        value = values.get(key);
    }
    value.add(o);
    values.put(key, value);
}

public JSONObject toJson() throws JSONException {
    JSONObject json = new JSONObject();
    JSONArray tempArray = null;

    for (Entry<String, List<Object>> en : values.entrySet()) {
        tempArray = new JSONArray();
        for (Object o : en.getValue()) {
            tempArray.add(o);
        }
        json.put(en.getKey(), tempArray);
    }

    return json;
}
}

You can then retrieve the values from the database, call the .add(String key, Object o) function with the column name from the database, and the value (as the Object param). Then call .toJson() when you are finished.

Mike Elofson
  • 2,017
  • 1
  • 10
  • 16
  • This is much too hard coded to work. The example I gave was just to illustrate the problem. My actual data is a large nested file and I don't know ahead of time what or how many fields there are. Even if I abstracted some of it away, I'd still need a way to parse through the json string reliably. – adamF Jun 25 '14 at 19:48
  • Make it generic. There can be any type of values in JSON. – Braj Jun 25 '14 at 19:50
  • Make it generic using a map. Read in the key-value pairs one at a time, check if the key exists in a map, if it does, add the value to a list at that key, if it doesn't, create a new list with that value and add it at that key. Then, in toJSON, iterate through each `Entry` – Mike Elofson Jun 25 '14 at 19:54
  • I have updated my answer showing how to make it generic using a map. – Mike Elofson Jun 25 '14 at 19:58
1

Thanks to Mike Elofson and Braj for helping me in the right direction. I only wanted to have the keys with multiple values become arrays so I had to modify the code a bit. Eventually I want it to work for nested JSON as well, as it currently assumes it is flat. However, the following code works for what I need it for at the moment.

public static String repeatedKeysToArrays(String jsonIn) throws JSONException 
{
    //This assumes that the json is flat
    String jsonString = jsonIn.substring(2, jsonIn.length() - 2);

    JSONObject obj = new JSONObject();
    for (String values : jsonString.split("\",\"")) {
        String[] keyValue = values.split("\":\"");
        String key = keyValue[0];
        String value = ""; 
        if (keyValue.length>1) value = keyValue[1];

        if (!obj.has(key)) {
            obj.put(key, value);
        } else {
            Object Oold = obj.get(key);
            ArrayList<String> newlist = new ArrayList<String>();

            //Try to cast as JSONArray. Otherwise, assume it is a String
            if (Oold.getClass().equals(JSONArray.class)) {
                JSONArray old = (JSONArray)Oold;
                //Build replacement value
                for (int i=0; i<old.length(); i++) {
                    newlist.add( old.getString(i) );
                }
            }
            else if (Oold.getClass().equals(String.class)) newlist = new ArrayList<String>(Arrays.asList(new String[] {(String)Oold})); 
            newlist.add(value);

            JSONArray newarr = new JSONArray( newlist );
            obj.put(key,newarr);                
        }
    }
    return obj.toString();
}
adamF
  • 961
  • 2
  • 9
  • 23