How to decode JSON with unknown field using Gson?

41,546

(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.)

Share:
41,546
NazarK
Author by

NazarK

Updated on April 26, 2020

Comments

  • NazarK
    NazarK about 4 years

    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"
        }, 
        ...
    }
    
  • Jonik
    Jonik over 10 years
    @Nazik: Right, then it's a bit trickier, but solvable using a custom deserialiser. See my edited answer.
  • Jonik
    Jonik over 10 years
    I updated the answer again, as it turned out this is very simple with newer Gson versions. Nice to learn something new about Gson.
  • NazarK
    NazarK over 10 years
    Thank you for your very helpful response
  • CoolMind
    CoolMind over 7 years
    Thanks for a LinkedTreeMap. Manual parsing of String values within a JSON object is difficult, but at least it helps.