使用 GSON 反序列化的父对象的参数实例化子对象并使用泛型?
Posted
技术标签:
【中文标题】使用 GSON 反序列化的父对象的参数实例化子对象并使用泛型?【英文标题】:Instantiate child object with params from parent object being deserialized with GSON and use generics? 【发布时间】:2016-02-09 15:06:01 【问题描述】:我大致有以下结构
class MyDeserialParent<T extends MyChildInterface>
MyChildInterface mSerialChild;
... //some other fields (not 'type')
但它是从一个凌乱的 JSON 结构反序列化的,子节点的两个属性在父节点上返回,如下所示。
"myDeserialParents" : [
... //some parent properties
"type": "value", //used in a TypeAdapter to choose child implementation
"childProp1": "1",
"childProp2": "2",
,
... //more in this list
]
显然,这使我无法仅使用 SerializedName
注释 mSerialChild 并让 TypeAdapter
发挥其魔力。所以我希望做的是当MyDeserialParent
被反序列化时,使用“类型”来找到MyChildInterface
的正确具体类,并使用childProp1
和childProp2
作为构造函数的参数创建一个新类。我不知道该怎么做。
我可以想象为MyDeserialParent
使用TypeAdapter
(JsonDeserializer
) 并在deserialize
中获取类型字段(以及两个子属性),然后我自己为MyChildInterface
实例化正确的混凝土。
这意味着我必须创建我的MyDeserialParent
类(使用context.deserialize(json, MyDeserialParent.class)
)并使用MyChildInterface
实例调用setter。感觉不对,好像我错过了什么。有没有更好的办法?
如果我也手动创建父对象,是否还有一种方法可以指定泛型(MyDeserialParent
上的T
)?或者类型擦除是否意味着没有办法做到这一点? (这个问题不太重要,因为我知道如果我使用已经推断出 T
的 MyDeserialParent 的特定子类型,我可以获得类型安全,但我想避免它)
【问题讨论】:
【参考方案1】:您显然需要自定义TypeAdapter
。但棘手的部分是:
mSerialChild
不是T
类型,而是MyChildInterface
类型
我们希望避免手动解析每个子类的 json,并且能够在不修改整个代码的情况下向父类添加属性。
牢记这一点,我最终得到了以下解决方案。
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;
备注:
自定义适配器必须在 gsonBuilder 上注册 如果你需要为你的孩子定制一些gson属性,你可以在MyParentAdapter
的构造函数中传递Gson
对象,因为现在它使用默认的;
子项和父项必须具有不同名称的属性;
每个新类型都必须使用相应的类添加到地图中。
完整示例
主要:
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
家长:
class MyDeserialParent<T extends MyChildInterface>
MyChildInterface mSerialChild;
//some other fields (not 'type')
String otherProp;
孩子:
public class MyChild1 implements MyChildInterface
String childProp1;
String childProp2;
//end class
【讨论】:
这与我自己的解决方案非常接近,不是我正在寻找的不同解决方案,而是一个很好的答案,它将涵盖大多数来这里寻找的人 如果父类需要为其他成员自定义反序列化器,则此答案不起作用,因为您在那里使用默认 Gson。而且您也不能使用 JsonDeserializationContext ,因为这会导致无限循环以上是关于使用 GSON 反序列化的父对象的参数实例化子对象并使用泛型?的主要内容,如果未能解决你的问题,请参考以下文章