使用 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 的正确具体类,并使用childProp1childProp2 作为构造函数的参数创建一个新类。我不知道该怎么做。

我可以想象为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 反序列化的父对象的参数实例化子对象并使用泛型?的主要内容,如果未能解决你的问题,请参考以下文章

使用 gson 反序列化反序列化对象的内部对象

在Gson反序列化期间使用Dagger进行依赖注入

完全理解Gson:Gson反序列化

Gson 在反序列化对象时忽略 null

使用 GSON 将 JSON 反序列化为 Java 对象时遇到问题

Gson关于抽象类的序列化与反序列化