Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to decode JSON with unknown field using Gson?

I have JSON similar to this :

{
  "unknown_field": {
    "field1": "str",
    "field2": "str",
    "field3": "str",
    "field4": "str",
    "field5": "str"
  }, ......
}

I created classes to map this json

public class MyModel implements Serializable {
  private int id;
  private HashMap<String, Model1> models;

  // getters and setter for id and models here
}

and class Model1 is a simple class only with String fields.

But it doesn't work.

Edit: the JSON format looks like this:

{
    "1145": {
        "cities_id": "1145",
        "city": "Nawanshahr",
        "city_path": "nawanshahr",
        "region_id": "53",
        "region_district_id": "381",
        "country_id": "0",
        "million": "0",
        "population": null,
        "region_name": "Punjab"
    },
    "1148": {
        "cities_id": "1148",
        "city": "Nimbahera",
        "city_path": "nimbahera",
        "region_id": "54",
        "region_district_id": "528",
        "country_id": "0",
        "million": "0",
        "population": null,
        "region_name": "Rajasthan"
    }, 
    ...
}
like image 978
NazarK Avatar asked Dec 07 '13 14:12

NazarK


1 Answers

(After OP commented that in fact the JSON looks like this, I completely updated the answer.)

Solution for Gson 2.0+

I just learned that with newer Gson versions this is extremely simple:

GsonBuilder builder = new GsonBuilder();
Object o = builder.create().fromJson(json, Object.class);

The created object is a Map (com.google.gson.internal.LinkedTreeMap), and if you print it, it looks like this:

{1145={cities_id=1145, city=Nawanshahr, city_path=nawanshahr, region_id=53, region_district_id=381, country_id=0, million=0, population=null, region_name=Punjab}, 
 1148={cities_id=1148, city=Nimbahera, city_path=nimbahera, region_id=54, region_district_id=528, country_id=0, million=0, population=null, region_name=Rajasthan}
...

Solution using a custom deserialiser

(NB: Turns out you don't really a custom deserialiser unless you're stuck with pre-2.0 versions of Gson. But still it is useful to know how to do custom deserialisation (and serialisation) in Gson, and it may often be the best approach, depending on how you want to use the parsed data.)

So we're indeed dealing with random / varying field names. (Of course, this JSON format is not very good; this kind of data should be inside a JSON array, in which case it could be very easily read into a List. Oh well, we can still parse this.)

First, this is how I would model the JSON data in Java objects:

// info for individual city
public class City    {
    String citiesId;
    String city;
    String regionName;
    // and so on
}

// top-level object, containing info for lots of cities
public class CityList  {
    List<City> cities;

    public CityList(List<City> cities) {
        this.cities = cities;
    }
}

Then, the parsing. One way to deal with this kind of JSON is to create a custom deserialiser for the top level object (CityList).

Something like this:

public class CityListDeserializer implements JsonDeserializer<CityList> {

    @Override
    public CityList deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException {
        JsonObject jsonObject = element.getAsJsonObject();
        List<City> cities = new ArrayList<City>();
        for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
            // For individual City objects, we can use default deserialisation:
            City city = context.deserialize(entry.getValue(), City.class); 
            cities.add(city);
        }
        return new CityList(cities);
    }

}

A key point to notice is the call to jsonObject.entrySet() which retuns all the top-level fields (with names like "1145", "1148", etc). This Stack Overflow answer helped me solve this.

Complete parsing code below. Note that you need to use registerTypeAdapter() to register the custom serialiser.

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(CityList.class, new CityListDeserializer());
Gson gson = builder.setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES).create();
CityList list = gson.fromJson(json, CityList.class);

(Here's a full, executable example that I used for testing. Besides Gson, it uses Guava library.)

like image 57
Jonik Avatar answered Oct 24 '22 10:10

Jonik