我在当前项目中遇到了GSON多态对象的奇怪情况。情况是这样的:我有两个不同的抽象基类,有两个不同的用例:
设计类的简化版本(为了简洁省略了构造函数和访问器;定义了鉴别器字段,但为了说明而注释掉了):
public abstract class ClassNumeric {
//private String numericType;
}
public class ClassOne extends ClassNumeric {
private String hex;
}
public class ClassTwo extends ClassNumeric {
private Integer whole;
private Integer fraction;
}
public abstract class ClassAlphabetic {
//private String alphabeticType;
}
public class ClassAlpha extends ClassAlphabetic {
private String name;
}
public class ClassBravo extends ClassAlphabetic {
private Integer age;
private Integer numberOfMarbles;
}
public class Container {
private String group;
private List<ClassAlphabetic> alphabetics;
}
适配器工厂及其在GSON上的注册:
public RuntimeTypeAdapterFactory<ClassNumeric> numericTypeFactory = RuntimeTypeAdapterFactory
.of(ClassNumeric.class, "numericType")
.registerSubtype(ClassOne.class)
.registerSubtype(ClassTwo.class);
public RuntimeTypeAdapterFactory<ClassAlphabetic> alphabeticTypeFactory = RuntimeTypeAdapterFactory
.of(ClassAlphabetic.class, "alphabeticType")
.registerSubtype(ClassAlpha.class)
.registerSubtype(ClassBravo.class);
public final Gson gson = new GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.registerTypeAdapterFactory(numericTypeFactory)
.registerTypeAdapterFactory(alphabeticTypeFactory)
.create();
基于我到目前为止所阅读的内容,我不必(并且实际上不应该)在基类中声明鉴别器字段,因为GSON在内部处理这些作为JSON被序列化和反序列化。
以下是如何使用这些内容的示例:
ClassOne c1 = ClassOne.builder().hex("EF8A").build();
ClassTwo c2 = ClassTwo.builder().whole(1).fraction(3).build();
List<ClassNumeric> numerics = Arrays.asList(c1, c2); // List of child objects
log.debug("Numerics: " + gson.toJson(numerics));
ClassAlpha ca = ClassAlpha.builder().name("Fred").build();
ClassBravo cb = ClassBravo.builder().age(5).numberOfMarbles(42).build();
List<ClassAlphabetic> alphas = Arrays.asList(ca, cb);
Container container = Container.builder().group("Test Group 1").alphabetics(alpha).build(); // List of objects field on larger object
log.debug("Alphas (container): " + gson.toJson(container));
我遇到的问题是ClassAlphabetic
对象工作正常(鉴别字段存在于JSON中),而ClassNumeric
对象则不存在(鉴别字段缺失)。样本输出:
09:12:17.910 [main] DEBUG c.s.s.s.s.GSONPolymorphismTest - Numerics: [
{
"hex": "EF8A"
},
{
"whole": 1,
"fraction": 3
}
]
09:12:17.926 [main] DEBUG c.s.s.s.s.GSONPolymorphismTest - Alphas (container): {
"group": "Test Group 1",
"alphabetics": [
{
"alphabeticType": "ClassAlpha",
"name": "Fred"
},
{
"alphabeticType": "ClassBravo",
"age": 5,
"numberOfMarbles": 42
}
]
}
我在这里缺少什么?这些基本上是以相同的方式定义和设置GSON,但是一个用例在另一个用例不起作用的情况下工作。
答案 0 :(得分:1)
这是因为泛型在Java中的运作方式。简而言之,特定的泛型类实例没有任何类型参数化信息作为实例的一部分。但是,类型参数可以存储在字段类型,方法返回类型,方法参数,继承的超类(例如,extends ArrayList<Integer>
),自定义参数化类型信息实例等中。局部变量,如numerics
是,在编译期间保留类型参数并存在于您和编译器的头脑中 - 由于type erasure。所以,它就像运行时的原始List
。与numerics
类似,alphas
没有任何运行时参数化,但与局部变量不同,Container.alphabetics
字段具有在运行时保留的类型信息 - 字段可以提供完整的类型信息。 Gson使用它来确定应用哪种(de)序列化策略。同样,当没有提供其他类型参数信息时,Gson使用默认策略(如局部变量)。如上所述,您还可以创建自定义参数化类型ParameterizedType
以提供原始类型及其类型参数信息。它有什么用?如果你仔细看看Gson toJson
重载,你可以看到它的一个重载accepts是一个额外的参数(我选择了最简单的一个)。这可以被视为一种暗示Gson告诉它传递的实例的确切类型(不一定匹配,但在大多数情况下应该)。所以,只是为了让它成功,告诉Gson你的numerics
List
类型参数化:
// "Canonical" way: TypeToken analyzes its superclass parameterization and returns its via the getType method
private static final Type classNumericListType = new TypeToken<List<ClassNumeric>>() {
}.getType()));
System.out.println("Numerics: " + gson.toJson(numerics, classNumericListType);
或者,如果你可以使用Gson 2.8.0 +,
private static final Type classNumericListType = TypeToken.getParameterized(List.class, ClassNumeric.class);
System.out.println("Numerics: " + gson.toJson(numerics, classNumericListType);
或者只是创建自己的ParameterizedType
实施。在编译时知道类型时我会使用类型标记,如果仅在运行时知道类型,我会使用TypeToken.getParameterized
。有了这个,传递类型实例会触发RuntimeTypeAdapterFactory
机制(现在它是ClassNumeric
,而不是原始Object
),所以输出如下:
Numerics: [
{
"numericType": "N1",
"hex": "EF8A"
},
{
"numericType": "N2",
"whole": 1,
"fraction": 3
}
]