Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gson - deserialization to specific object type based on field value

I want to deserialize json objects to specific types of objects (using Gson library) based on type field value, eg.:

[
    {
          "type": "type1",
          "id": "131481204101",
          "url": "http://something.com",
          "name": "BLAH BLAH",
          "icon": "SOME_STRING",
          "price": "FREE",
          "backgroundUrl": "SOME_STRING"
    },
    {
        ....
    }
]

So type field will have different (but known) values. Based on that value I need to deserialize that json object to appropriate model object, eg.: Type1Model, Type2Model etc. I know I can easily do that before deserialization by converting it to JSONArray, iterate through it and resolve which type it should be deserialized to. But I think it's ugly approach and I'm looking for better way. Any suggestions?

like image 831
lukaleli Avatar asked Feb 13 '14 23:02

lukaleli


4 Answers

You may implement a JsonDeserializer and use it while parsing your Json value to a Java instance. I'll try to show it with a code which is going to give you the idea:

1) Define your custom JsonDeserializer class which creates different instance of classes by incoming json value's id property:

class MyTypeModelDeserializer implements JsonDeserializer<MyBaseTypeModel> {      @Override     public MyBaseTypeModel deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)             throws JsonParseException {          JsonObject jsonObject = json.getAsJsonObject();          JsonElement jsonType = jsonObject.get("type");         String type = jsonType.getAsString();          MyBaseTypeModel typeModel = null;               if("type1".equals(type)) {             typeModel = new Type1Model();         } else if("type2".equals(type)) {             typeModel = new Type2Model();         }         // TODO : set properties of type model          return typeModel;     } } 

2) Define a base class for your different instance of java objects:

class  MyBaseTypeModel {     private String type;     // TODO : add other shared fields here } 

3) Define your different instance of java objects' classes which extend your base class:

class Type1Model extends MyBaseTypeModel {     // TODO: add specific fields for this class }  class Type2Model extends MyBaseTypeModel {     // TODO: add specific fields for this class } 

4) Use these classes while parsing your json value to a bean:

GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(MyBaseTypeModel.class, new MyTypeModelDeserializer()); Gson gson = gsonBuilder.create();  MyBaseTypeModel myTypeModel = gson.fromJson(myJsonString, MyBaseTypeModel.class); 

I can not test it right now but I hope you get the idea. Also this link would be very helpful.

like image 87
Devrim Avatar answered Sep 19 '22 12:09

Devrim


@stephane-k 's answer works, but it is a bit confusing and could be improved upon (see comments to his answer)

Copy https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java into your project. (It's ok; these classes are designed to be copy/pasted https://github.com/google/gson/issues/845#issuecomment-217231315)

Setup model inheritance:

// abstract is optional abstract class BaseClass { }  class Type1Model extends BaseClass { }  class Type2Model extends BaseClass { } 

Setup GSON or update existing GSON:

RuntimeTypeAdapterFactory<BaseClass> typeAdapterFactory = RuntimeTypeAdapterFactory         .of(BaseClass.class, "type")         .registerSubtype(Type1Model.class, "type1")         .registerSubtype(Type2Model.class, "type2");  Gson gson = new GsonBuilder().registerTypeAdapterFactory(typeAdapterFactory)                 .create(); 

Deserialize your JSON into base class:

String jsonString = ... BaseClass baseInstance = gson.fromJson(jsonString, BaseClass.class); 

baseInstance will be instanceof either Type1Model or Type2Model.

From here you can either code to an interface or check instanceof and cast.

like image 29
tir38 Avatar answered Sep 19 '22 12:09

tir38


use https://github.com/google/gson/blob/master/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java

then configure it with

public static final class JsonAdapterFactory extends 
    RuntimeTypeAdapterFactory<MediumSummaryInfo> {
        public JsonAdapterFactory() {
            super(MyBaseType.class, "type");
            registerSubtype(MySubtype1.class, "type1");
            registerSubtype(MySubtype2.class, "type2");
        }
}

and add the annotation:

@JsonAdapter(MyBaseType.JsonAdapterFactory.class)

to MyBaseType

Much better.

like image 34
stephane k. Avatar answered Sep 18 '22 12:09

stephane k.


If you have a lot of sub types and you do not want to or cannot maintain a list of them, you can also use an annotation based approach.

Here is the required code and also some usage examples: https://gist.github.com/LostMekka/d90ade1fe051732d6b4ac60deea4f9c2 (it is Kotlin, but can easily be ported to Java)

For me, this approach is especially appealing, since I write a small library that does not know all possible sub types at compile time.

like image 33
LostMekkaSoft Avatar answered Sep 20 '22 12:09

LostMekkaSoft