Laranjeiro
Laranjeiro

Reputation: 2622

Gson Parse Json with array with different object types

How can I parse this JSON using Gson? I have an array with multiple object types and I don't know what kind of object I need to create to save this structure. I cannot change the json message (I don't control the server).

The only class that function (sort of) was this

public class Response {
    private List<Object> tr;
    private int results;

    (...)

}

JSON Message (Note the array with multiple object types.)

{
   "tr":
   [
       {
           "a":
           {
               "userId": "112"
           }
       },
       {
           "b":
           {
               "userId": "123",
               "address":"street dummy" 
           }
       },
       {
           "a":
           {
               "userId": "154"
           }
       }
   ],
"results":3
}

Upvotes: 17

Views: 31625

Answers (5)

Laranjeiro
Laranjeiro

Reputation: 2622

I pick something from each answer and did it this way:

Response Object

public class Response {
    private List<Users> tr;
    private int results;

    (...)
}

Generic User

public class User {
    public static final int TYPE_USER_A =0;
    public static final int TYPE_USER_B =1;
    private String userId;
    private int type;
    (...)
}

A

public class a extends User {
    private String location;
    (...) 
}

B

public class b extends User { 
    private String adress;   
    (...)
}

Parsing Method

private Response buildResponseObject(String response) {
        Response tls = new Response();
        List<Users> users = new ArrayList<users>();
        User u;

        try {
            JSONObject object = new JSONObject(response);
            tls.setResults(object.getInt("results"));
            JSONArray array = object.getJSONArray("tr");

            for (int i = 0; i < array.length(); i++) {
                JSONObject trs = array.getJSONObject(i);

                if (trs.has("a")) {
                    String json = trns.getString("a");
                    A a = new Gson().fromJson(json,A.class);
                    a.setType(User.TYPE_USER_A);
                    users.add(a);

                } else if (trs.has("b")) {
                    String json = trs.getString("b");
                    B b= new Gson().fromJson(json,B.class);
                    B.setType(User.TYPE_USER_B);
                    users.add(b);
                }
            }

            tls.setUsers(users);

        } catch (JSONException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return tls;
    }

This is not as elegant as I wanted and mix native JsonObjects with Gson methods but works for me.

Upvotes: 5

blue01
blue01

Reputation: 2095

You can create corresponding java classes for the json objects. The integer, string values can be mapped as is. Json can be parsed like this-

Gson gson = new GsonBuilder().create();
Response r = gson.fromJson(jsonString, Response.class);

Here is an example- http://rowsandcolumns.blogspot.com/2013/02/url-encode-http-get-solr-request-and.html

Upvotes: -2

Aerilys
Aerilys

Reputation: 1639

I think this link might help you: https://sites.google.com/site/gson/gson-user-guide#TOC-Collections-Examples

Basically, create a class for your "object" (kind of user I guess), and then use the deserialization code of Gson, like this:

Type collectionType = new TypeToken<Collection<User>>(){}.getType();
Collection<User> users= gson.fromJson(json, collectionType);

Upvotes: 0

Brian Roach
Brian Roach

Reputation: 76908

The Gson User's Guide explicitly covers this:

https://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Collection-with-Objects-of-Arbitrary-Types

You have an object with a field tr that is an array containing arbitrary types.

The users guide explains that you can't directly deserialize such a structure, and recomends:

Use Gson's parser API (low-level streaming parser or the DOM parser JsonParser) to parse the array elements and then use Gson.fromJson() on each of the array elements. This is the preferred approach.

In your case ... it would really depend on what objects were possible in that array. If they are all going to have that same inner object you'd want to do something like...

List<MyUserPojo> list = new ArrayList<MyUserPojo>();
JsonArray array = parser.parse(json).getAsJsonObject().getAsJsonArray("tr");
for (JsonElement je : array)
{
    Set<Map.Entry<String,JsonElement>> set = je.getAsObject().entrySet();
    JsonElement je2 = set.iterator().next().getValue();

    MyUserPojo mup = new Gson().fromJson(je2, MyUserPojo.class);
    list.add(mup);
}

And of course, this would need to be inside a custom deserializer for your actual object that would have the tr and results fields.

class MyPojo
{
    List<MyUserPojo> userList;
    int results; 
}

class MyUserPojo
{
    String userId;
    String address;
}

class MyDeserializer implements JsonDeserializer<MyPojo>
{
    @Override
    public MyPojo deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
                              throws JsonParseException
    {
        List<MyUserPojo> list = new ArrayList<MyUserPojo>();
        JsonArray array = je.getAsJsonObject().getAsJsonArray("tr");
        for (JsonElement je2 : array)
        {
            Set<Map.Entry<String,JsonElement>> set = je2.getAsObject().entrySet();
            JsonElement je3 = set.iterator().next().getValue();                 

            MyUserPojo mup = new Gson().fromJson(je3, MyUserPojo.class);
            list.add(mup);
        }

        MyPojo mp = new MyPojo();
        mp.tr = list;
        mp.results = je.getAsObject().getAsJsonPrimitive("results").getAsInt();

        return mp;
    }
}

Now you're all set - you can use that deserializer and create your object:

Gson gson = new GsonBuilder()
                .registerTypeAdapter(MyPojo.class, new MyDeserializer())
                .build();

MyPojo mp = gson.fromJson(json, MyPojo.class);

If the a, b etc are important ... well, you'll have to figure that out. But the above should get you well on your way to understanding what's going to be needed to deal with your JSON structure.

For completeness sake, the only "hacky" way around this is if there is a fairly limited number of those types and the inner object also is fairly limited in terms of its fields. You could create a POJO that encompasses all the possibilities:

class MyPojo 
{
    MySecondPojo a;
    MySecondPojo b;
    ...
    MySecondPojo f;
}

class MySecondPojo
{
    String userId;
    String address;
    ...
    String someOtherField;
}

When Gson deserializes JSON it will set any missing fields in your POJO(s) to null. You could now have tr be a List or array of these in your POJO. Again and to emphasize, this is really quite hacky and the wrong way to do it, but I thought I'd explain what would be required to directly parse that array.

Upvotes: 22

rekire
rekire

Reputation: 47945

Try this code here:

public class Address {
    public String userId;
    public String address;
    // ...
}

public class Response {
    private HashMap<String, Address> tr;
    private int results;
    // ...
}

Usage:

String json = "{\n  \"tr\":\n  {\n    \"a\": {\n       \"userId\": \"112\"\n    },\n    \"b\": {\n       \"userId\": \"123\",\n       \"address\":\"street dummy\"\n    },\n    \"c\": {\n       \"userId\": \"154\"\n    }\n  },\n  \"results\":3\n}";

Response users = new Gson().fromJson(json, Response.class);

As you may see I needed to modify the structure:

{
  "tr":
  {
    "a": {
       "userId": "112"
    },
    "b": {
       "userId": "123",
       "address":"street dummy"
    },
    "c": {
       "userId": "154"
    }
  },
  "results":3
}

But unfortunately I don't get it managed to allow multiple keys. Right now I have no idea how to fix this.

Upvotes: 2

Related Questions