基于密钥的Gson多态反序列化



我可以找到很多基于对象内字段的多态反序列化示例:

[
{
"type": "Engine",
"name": "Ford 6.7L",
"cylinders": 8
},
{
"type": "Tires",
"name": "Blizzak LM32",
"season": "winter"
}
]

但我似乎无法轻易地将使用对象键来确定类型的东西组合在一起:

{
"Engine": {
"name": "Ford 6.7L",
"cylinders": 8
},
"Tires": {
"name": "Blizzak LM32",
"season": "winter"
}
}

而无需首先将文件解析为JsonObject,然后遍历该对象,并将每个值重新转换回字符串,然后根据键重新解析为类(并滚动我自己的跟踪每个键类型的方法(。

理想情况下,我想做一些类似的事情:

@JsonKey("Engine")
class Engine implements Equipment {
String name;
Integer cylinders;
}
@JsonKey("Tires")
class Tires implements Equipment {
String name;
String season;
}

并且能够像这样解析文件:

Map<String, Equipment> car = gson.fromJson(fileContents, new TypeToken<Map<String, Equipment>>(){}.getType();

对我来说,这似乎是一个非常明显的用例。我缺少什么?

使用对象名称作为反序列化多态类型的键并没有什么好处。这导致多个具有相同名称的对象成为父对象的一部分(您的情况(。当您尝试反序列化父JSON对象时(将来可能会有包含属性的Engine和Tires的父对象(,您可能会发现多个JSON对象以相同的名称(重复类型名称(表示该属性,从而导致解析器异常。

基于JSON对象内部类型属性的反序列化是一种常见而方便的方法。您可以实现代码,使其按预期工作,但并非在所有情况下都容易出错,因此JSON解析器实现的期望是,在这种情况下,通过嵌套类型属性反序列化多态类型,这是一种容易出错且干净的方法

编辑:您试图实现的也是反对关注点分离(JSON对象键是键本身,同时也是类型键(,而类型属性分离的类型责任是JSON对象的一个属性。这也遵循了KISS原则(保持愚蠢的简单(,而且在多态反序列化的情况下,许多开发人员都用来键入属性。

您所要做的就是实现一个自定义Map<String, ...>反序列化程序,该反序列化程序将为使用了解映射规则的特殊反序列化程序定义的映射触发。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface JsonKey {
@Nonnull
String value();
}
final class JsonKeyMapTypeAdapterFactory<V>
implements TypeAdapterFactory {
private final Class<V> superClass;
private final Map<String, Class<? extends V>> subClasses;
private final Supplier<? extends Map<String, V>> createMap;
private JsonKeyMapTypeAdapterFactory(final Class<V> superClass, final Map<String, Class<? extends V>> subClasses,
final Supplier<? extends Map<String, V>> createMap) {
this.superClass = superClass;
this.subClasses = subClasses;
this.createMap = createMap;
}
static <V> Builder<V> build(final Class<V> superClass) {
return new Builder<>(superClass);
}
@Override
@Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !Map.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
final Type type = typeToken.getType();
if ( !(type instanceof ParameterizedType) ) {
return null;
}
final ParameterizedType parameterizedType = (ParameterizedType) type;
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
final Type valueType = actualTypeArguments[1];
if ( !(valueType instanceof Class) ) {
return null;
}
final Class<?> valueClass = (Class<?>) valueType;
if ( !superClass.isAssignableFrom(valueClass) ) {
return null;
}
final Type keyType = actualTypeArguments[0];
if ( !(keyType instanceof Class) || keyType != String.class ) {
throw new IllegalArgumentException(typeToken + " must represent a string-keyed map");
}
final Function<? super String, ? extends TypeAdapter<? extends V>> resolveTypeAdapter = subClasses.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> gson.getDelegateAdapter(this, TypeToken.get(e.getValue()))))
::get;
@SuppressWarnings("unchecked")
final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) JsonKeyMapTypeAdapter.create(resolveTypeAdapter, createMap);
return castTypeAdapter;
}
static final class Builder<V> {
private final Class<V> superClass;
private final ImmutableMap.Builder<String, Class<? extends V>> subClasses = new ImmutableMap.Builder<>();
private Supplier<? extends Map<String, V>> createMap = LinkedHashMap::new;
private Builder(final Class<V> superClass) {
this.superClass = superClass;
}
Builder<V> register(final Class<? extends V> subClass) {
@Nullable
final JsonKey jsonKey = subClass.getAnnotation(JsonKey.class);
if ( jsonKey == null ) {
throw new IllegalArgumentException(subClass + " must be annotated with " + JsonKey.class);
}
return register(jsonKey.value(), subClass);
}
Builder<V> register(final String key, final Class<? extends V> subClass) {
if ( !superClass.isAssignableFrom(subClass) ) {
throw new IllegalArgumentException(subClass + " must be a subclass of " + superClass);
}
subClasses.put(key, subClass);
return this;
}
Builder<V> createMapWith(final Supplier<? extends Map<String, V>> createMap) {
this.createMap = createMap;
return this;
}
TypeAdapterFactory create() {
return new JsonKeyMapTypeAdapterFactory<>(superClass, subClasses.build(), createMap);
}
}
private static final class JsonKeyMapTypeAdapter<V>
extends TypeAdapter<Map<String, V>> {
private final Function<? super String, ? extends TypeAdapter<? extends V>> resolveTypeAdapter;
private final Supplier<? extends Map<String, V>> createMap;
private JsonKeyMapTypeAdapter(final Function<? super String, ? extends TypeAdapter<? extends V>> resolveTypeAdapter,
final Supplier<? extends Map<String, V>> createMap) {
this.resolveTypeAdapter = resolveTypeAdapter;
this.createMap = createMap;
}
private static <V> TypeAdapter<Map<String, V>> create(final Function<? super String, ? extends TypeAdapter<? extends V>> resolveTypeAdapter,
final Supplier<? extends Map<String, V>> createMap) {
return new JsonKeyMapTypeAdapter<>(resolveTypeAdapter, createMap)
.nullSafe();
}
@Override
public void write(final JsonWriter out, final Map<String, V> value) {
throw new UnsupportedOperationException();
}
@Override
public Map<String, V> read(final JsonReader in)
throws IOException {
in.beginObject();
final Map<String, V> map = createMap.get();
while ( in.hasNext() ) {
final String key = in.nextName();
@Nullable
final TypeAdapter<? extends V> typeAdapter = resolveTypeAdapter.apply(key);
if ( typeAdapter == null ) {
throw new JsonParseException("Unknown key " + key + " at " + in.getPath());
}
final V value = typeAdapter.read(in);
@Nullable
final V replaced = map.put(key, value);
if ( replaced != null ) {
throw new JsonParseException(value + " duplicates " + replaced + " using " + key);
}
}
in.endObject();
return map;
}
}
}
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.registerTypeAdapterFactory(JsonKeyMapTypeAdapterFactory.build(Equipment.class)
.register(Engine.class)
.register(Tires.class)
.create()
)
.create();

上面的Gson对象将把JSON文档反序列化为toString映射,如下所示(假设Lombok用于toString(:

{Engine=Engine(name=Ford 6.7L, cylinders=8), Tires=Tires(name=Blizzak LM32, season=winter)}

最新更新