Osarez
Osarez

Reputation: 366

Deserialize nested object with GSON

I'm trying to deserialize the following structure

{ meta: { keywords:  [a, b, c, d]}  ...  }

other valid structures are

{ meta: { keywords: "a,b,c,d"}  ... }

and

{ meta: {keywords: "a"}  ...}

I have this classes

public class Data {
   @PropertyName("meta")
   MetaData meta;
   ...
}


public class  MetaData {
    List<String> keywords;
    ...
}

and a custom deserializer

public static class CustomDeserilizer implements JsonDeserializer<MetaData>{
    @Override
    public MetaData deserialize(JsonElement json, Type typeOfT,  JsonDeserializationContext context) throws JsonParseException {
        List<String> keywords = null;
        Gson gson = new Gson();
        MetaData metaData = gson.fromJson(json, AppMetaData.class);
        JsonObject jsonObject = json.getAsJsonObject();

        if (jsonObject.has("keywords")) {
            JsonElement elem = jsonObject.get("keywords");
            if (elem != null && !elem.isJsonNull()) {

                if (jsonObject.get("keywords").isJsonArray()) {
                    keywords = gson.fromJson(jsonObject.get("keywords"),   new TypeToken<List<String>>() {
                    }.getType());
                } else {
                    String keywordString = gson.fromJson(jsonObject.get("keywords"), String.class);
                    keywords = new ArrayList<String>();
                    list.addAll(Arrays.asList(keywordString.split(",")));
                }
            }
        }
       metaData.setKeywords(keywords);
}

Then I try to apply the deserilizer:

Gson gson = new GsonBuilder()              
        .registerTypeAdapter(Data.class,new CustomDeserilizer())               
        .create();

But I get a parsing error , because is trying to deserialize Data instead of MetaData, how can I apply this deserializer to make it work right?

Upvotes: 8

Views: 24876

Answers (4)

Ilan Granot
Ilan Granot

Reputation: 21

This suppose to achieve what you want easily. You should define an inner static class. You can keep nesting classes to define keywords as class Keywords, etc. Just remember to have a field in the containing class, i.e. in your inner class have private Keywords keywords;

In your Main class:

Gson gson = new Gson();
Data data = gson.fromJson(SOME_JSON_STRING, Data.class);

In a class called Data:

public class Data {
    private Meta meta;
    
    static class Meta{
        private String[] keywords;
    }
}

Upvotes: 0

Gene Bo
Gene Bo

Reputation: 12063

Here's a concise solution that leverages Java inheritance to represent the nested structure; and therefore does not need to provide any actual instance member fields (mappings, etc) for capturing the nested String data that GSON maps.

Step 1: For readability, create an empty object to represent the nested mapping

public class StateRegionCitiesMap extends HashMap<String, List<String>> {

}

Step 2: Add the one line of actual code to do the mapping; no other serialize/deserialize logic to manage

protected void loadContent(JsonObject stateRegionsJsonObject) {

    HashMap<String, StateRegionCitiesMap> stateRegionCitiesMap =
            mGson.fromJson(
                    stateRegionsJsonObject,
                    new TypeToken<HashMap<String, StateRegionCitiesMap>>() {
                    }.getType()
            );
}

Alternatively, you can skip the wrapper class altogether and just directly put <String, List<String>> in the GSON call. However, I find an explicit object helps to inform/remind whoever is reading the code, what the purpose is.


Example JSON:

The class StateRegionCitiesMap represents a multi-tier map structure for say:

[US State] -> [State-Region Key] -> [Sub-Region Key] -> CitiesArray[]

"CA": {
  "Central CA": {
    "Central Valley": [
      "FRESNO",
      "VISALIA"
    ],
    "Sacramento Area": [
      "SACRAMENTO",
      "EL DORADO HILLS"
    ]
  },

Upvotes: 0

Osarez
Osarez

Reputation: 366

I solved it creating a deserializer for my class Data.

public static class DataDeserilizer implements JsonDeserializer {
    @Override
    public Data deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        Gson gson = new Gson();
        Data data = gson.fromJson(json, Data.class);
        JsonObject jsonObject = json.getAsJsonObject();
        if (jsonObject.has("meta")) {
            JsonElement elem = jsonObject.get("meta");
            if (elem != null && !elem.isJsonNull()) {

                Gson gsonDeserializer = new GsonBuilder()
                        .registerTypeAdapter(MetaData.class, new CustomDeserilizer())
                        .create();
                gsonDeserializer.fromJson(jsonObject.get("meta"), Data.class);
            }
        }

        return data;
    }



}

And

Gson gson = new GsonBuilder()              
    .registerTypeAdapter(Data.class,new DataDeserilizer())               
    .create();

Pretty obvious, but is there a more elegant solution?

Upvotes: 11

Hobbit
Hobbit

Reputation: 611

Firstly, rename your class to meta instead of metadata and make keywords String instead of List.Then use the following to map your JSonString into your object.

Gson gson = new GsonBuilder().create();
Meta meta = gson.from(yourJsonString,Meta.class);

In order to get keywords only, you need this.

JsonObject jsonObject = new JsonObject(yourJSonString);
String data = jsonObject.getJsonObject("meta").getString("keywords");

keywords is a JsonObject not an JsonArray so you can't directly map it onto List. You can split the string to get keywords in an array.

String keywords[] = data.split(",");

Upvotes: 0

Related Questions