I have roughly the following structure
class MyDeserialParent<T extends MyChildInterface> {
MyChildInterface mSerialChild;
... //some other fields (not 'type')
}
But it's deserialized from a messy JSON structure the two properties of the child are returned on the parent node like follows.
{
"myDeserialParents" : [
{
... //some parent properties
"type": "value", //used in a TypeAdapter to choose child implementation
"childProp1": "1",
"childProp2": "2",
},
... //more in this list
]
}
Obviously this prevents me from just annotating mSerialChild with SerializedName
and letting a TypeAdapter
work its magic. So what I'm hoping to do is when MyDeserialParent
is deserialised use "type" to find the correct concrete class of MyChildInterface
and make a new one using childProp1
and childProp2
as params for the constructor. I don't know how to go about this.
I can imagine using a TypeAdapter
(JsonDeserializer
) for MyDeserialParent
and in deserialize
get the type field (as well as the two child properties), then instantiate the correct concrete for MyChildInterface
myself.
This then means I have to create my MyDeserialParent
class (with context.deserialize(json, MyDeserialParent.class)
) and call a setter with MyChildInterface
instance. That feels wrong like I am missing something. Is there a better way?
Is there also a way to specify the generics (T
on MyDeserialParent
) if I manually create the parent object also? or does Type Erasure mean there is no way to do this? (This question is less important because I know I can get type safety if I use specific subtypes of MyDeserialParent which already infer T
instead, but I'd like to avoid it)
You obviously need a custom TypeAdapter
. But the tricky parts are:
mSerialChild
is not of type T
, but of type MyChildInterface
Keeping that in mind, I ended up with the following solution.
public class MyParentAdapter implements JsonDeserializer<MyDeserialParent>{
private static Gson gson = new GsonBuilder().create();
// here is the trick: keep a map between "type" and the typetoken of the actual child class
private static final Map<String, Type> CHILDREN_TO_TYPETOKEN;
static{
// initialize the mapping once
CHILDREN_TO_TYPETOKEN = new TreeMap<>();
CHILDREN_TO_TYPETOKEN.put( "value", new TypeToken<MyChild1>(){}.getType() );
}
@Override
public MyDeserialParent deserialize( JsonElement json, Type t, JsonDeserializationContext
jsonDeserializationContext ) throws JsonParseException{
try{
// first, get the parent
MyDeserialParent parent = gson.fromJson( json, MyDeserialParent.class );
// get the child using the type parameter
String type = ((JsonObject)json).get( "type" ).getAsString();
parent.mSerialChild = gson.fromJson( json, CHILDREN_TO_TYPETOKEN.get( type ) );
return parent;
}catch( Exception e ){
e.printStackTrace();
}
return null;
}
}
remarks:
Gson
object in the constructor of MyParentAdapter
, since right now it uses the default one; Complete example
Main:
public class DeserializeExample{
MyDeserialParent[] myDeserialParents;
static String json = "{\n" +
" \"myDeserialParents\" : [\n" +
" {\n" +
" \"otherProp\": \"lala\"," +
" \"type\": \"value\", //used in a TypeAdapter to choose child implementation\n" +
" \"childProp1\": \"1\",\n" +
" \"childProp2\": \"2\"\n" +
" }\n" +
" ]\n" +
"}";
public static void main( String[] args ){
Gson gson = new GsonBuilder().registerTypeAdapter( MyDeserialParent.class, new MyParentAdapter() ).create();
DeserializeExample result = gson.fromJson( json, DeserializeExample.class );
System.out.println( gson.toJson( result ));
// output:
// {"myDeserialParents":[{"mSerialChild":{"childProp1":"1","childProp2":"2"},"otherProp":"lala"}]}
}//end main
}//end class
Parent:
class MyDeserialParent<T extends MyChildInterface>{
MyChildInterface mSerialChild;
//some other fields (not 'type')
String otherProp;
}
child:
public class MyChild1 implements MyChildInterface {
String childProp1;
String childProp2;
}//end class
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With