与gson的多态性

我有一个反序列化与JSONstring的问题。 我收到一组命令。 该命令可以是启动,停止,其他types的命令。 自然我有多态性,并从命令inheritance启动/停止命令。

我如何使用gson将其序列化回正确的命令对象?

似乎我只得到基本types,这是声明的types,而不是运行时types。

这有点迟,但今天我必须做同样的事情。 所以,基于我的研究和使用gson-2.0时,你真的不想使用registerTypeHierarchyAdapter方法,而是更普通的registerTypeAdapter 。 而且您当然不需要为派生类执行instanceofs或编写适配器:只需要一个基类或接口的适配器,当然,您对派生类的默认序列化感到满意。 无论如何,这里的代码(包和import删除)(也在github中可用):

基类(在我的例子中的接口):

public interface IAnimal { public String sound(); } 

这两个派生类,Cat:

 public class Cat implements IAnimal { public String name; public Cat(String name) { super(); this.name = name; } @Override public String sound() { return name + " : \"meaow\""; }; } 

和狗:

 public class Dog implements IAnimal { public String name; public int ferocity; public Dog(String name, int ferocity) { super(); this.name = name; this.ferocity = ferocity; } @Override public String sound() { return name + " : \"bark\" (ferocity level:" + ferocity + ")"; } } 

IAnimalAdapter:

 public class IAnimalAdapter implements JsonSerializer<IAnimal>, JsonDeserializer<IAnimal>{ private static final String CLASSNAME = "CLASSNAME"; private static final String INSTANCE = "INSTANCE"; @Override public JsonElement serialize(IAnimal src, Type typeOfSrc, JsonSerializationContext context) { JsonObject retValue = new JsonObject(); String className = src.getClass().getName(); retValue.addProperty(CLASSNAME, className); JsonElement elem = context.serialize(src); retValue.add(INSTANCE, elem); return retValue; } @Override public IAnimal deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME); String className = prim.getAsString(); Class<?> klass = null; try { klass = Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new JsonParseException(e.getMessage()); } return context.deserialize(jsonObject.get(INSTANCE), klass); } } 

和testing类:

 public class Test { public static void main(String[] args) { IAnimal animals[] = new IAnimal[]{new Cat("Kitty"), new Dog("Brutus", 5)}; Gson gsonExt = null; { GsonBuilder builder = new GsonBuilder(); builder.registerTypeAdapter(IAnimal.class, new IAnimalAdapter()); gsonExt = builder.create(); } for (IAnimal animal : animals) { String animalJson = gsonExt.toJson(animal, IAnimal.class); System.out.println("serialized with the custom serializer:" + animalJson); IAnimal animal2 = gsonExt.fromJson(animalJson, IAnimal.class); System.out.println(animal2.sound()); } } } 

当你运行Test :: main你会得到如下输出:

 serialized with the custom serializer: {"CLASSNAME":"com.synelixis.caches.viz.json.playground.plainAdapter.Cat","INSTANCE":{"name":"Kitty"}} Kitty : "meaow" serialized with the custom serializer: {"CLASSNAME":"com.synelixis.caches.viz.json.playground.plainAdapter.Dog","INSTANCE":{"name":"Brutus","ferocity":5}} Brutus : "bark" (ferocity level:5) 

实际上,我已经使用registerTypeHierarchyAdapter方法完成了上述操作,但似乎需要实现自定义的DogAdapter和CatAdapter序列化器/反序列化器类,在任何时候想要向Dog或Cat添加另一个字段时都很痛苦。

Gson目前有一个机制来注册一个types层次适配器 ,据说可以configuration为简单的多态反序列化,但我不明白这是怎么回事,因为types层次适配器似乎只是一个组合的序列化/解串器/实例创build者,将实例创build的细节留给编码器,而不提供任何实际的多态types注册。

看起来Gson很快就会拥有RuntimeTypeAdapter来实现更简单的多态反序列化。 有关详细信息,请参阅http://code.google.com/p/google-gson/issues/detail?id=231

如果使用新的RuntimeTypeAdapter是不可能的,并且你必须使用Gson,那么我认为你将不得不推出自己的解决scheme,注册一个自定义的反序列化器作为types层次结构适配器或types适配器。 以下是一个这样的例子。

 // output: // Starting machine1 // Stopping machine2 import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; public class Foo { // [{"machine_name":"machine1","command":"start"},{"machine_name":"machine2","command":"stop"}] static String jsonInput = "[{\"machine_name\":\"machine1\",\"command\":\"start\"},{\"machine_name\":\"machine2\",\"command\":\"stop\"}]"; public static void main(String[] args) { GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); CommandDeserializer deserializer = new CommandDeserializer("command"); deserializer.registerCommand("start", Start.class); deserializer.registerCommand("stop", Stop.class); gsonBuilder.registerTypeAdapter(Command.class, deserializer); Gson gson = gsonBuilder.create(); Command[] commands = gson.fromJson(jsonInput, Command[].class); for (Command command : commands) { command.execute(); } } } class CommandDeserializer implements JsonDeserializer<Command> { String commandElementName; Gson gson; Map<String, Class<? extends Command>> commandRegistry; CommandDeserializer(String commandElementName) { this.commandElementName = commandElementName; GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); gson = gsonBuilder.create(); commandRegistry = new HashMap<String, Class<? extends Command>>(); } void registerCommand(String command, Class<? extends Command> commandInstanceClass) { commandRegistry.put(command, commandInstanceClass); } @Override public Command deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { try { JsonObject commandObject = json.getAsJsonObject(); JsonElement commandTypeElement = commandObject.get(commandElementName); Class<? extends Command> commandInstanceClass = commandRegistry.get(commandTypeElement.getAsString()); Command command = gson.fromJson(json, commandInstanceClass); return command; } catch (Exception e) { throw new RuntimeException(e); } } } abstract class Command { String machineName; Command(String machineName) { this.machineName = machineName; } abstract void execute(); } class Stop extends Command { Stop(String machineName) { super(machineName); } void execute() { System.out.println("Stopping " + machineName); } } class Start extends Command { Start(String machineName) { super(machineName); } void execute() { System.out.println("Starting " + machineName); } } 

GSON有一个非常好的testing用例,展示了如何定义和注册types层次结构适配器。

http://code.google.com/p/google-gson/source/browse/trunk/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java?r=739

要使用这样做:

  gson = new GsonBuilder() .registerTypeAdapter(BaseQuestion.class, new BaseQuestionAdaptor()) .create(); 

适配器的Serialize方法可以是级联的if-else检查它正在序列化的types。

  JsonElement result = new JsonObject(); if (src instanceof SliderQuestion) { result = context.serialize(src, SliderQuestion.class); } else if (src instanceof TextQuestion) { result = context.serialize(src, TextQuestion.class); } else if (src instanceof ChoiceQuestion) { result = context.serialize(src, ChoiceQuestion.class); } return result; 

反序列化有点怪异。 在unit testing的例子中,它检查告诉属性的存在性,以决定要反序列化的类。 如果您可以更改要序列化的对象的源,则可以为每个包含实例类名称的FQN的实例添加一个“classType”属性。 这是非常非面向对象的。

Marcus Junius Brutus有一个很好的答案(谢谢!)。 为了扩展他的例子,可以使他的适配器类通用,以适用于所有types的对象(不仅仅是IAnimal),并进行以下更改:

 class InheritanceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T> { .... public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) .... public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException .... } 

而在testing课上:

 public class Test { public static void main(String[] args) { .... builder.registerTypeAdapter(IAnimal.class, new InheritanceAdapter<IAnimal>()); .... } 

很长一段时间过去了,但我找不到一个很好的解决scheme在线..这是@ MarcusJuniusBrutus的解决scheme,避免了无限recursion的小捻。

保持相同的解串器,但删除串行器 –

 public class IAnimalAdapter implements JsonDeSerializer<IAnimal> { private static final String CLASSNAME = "CLASSNAME"; private static final String INSTANCE = "INSTANCE"; @Override public IAnimal deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME); String className = prim.getAsString(); Class<?> klass = null; try { klass = Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new JsonParseException(e.getMessage()); } return context.deserialize(jsonObject.get(INSTANCE), klass); } } 

然后,在您的原始类中,使用@SerializedName("CLASSNAME")添加一个字段。 诀窍现在是在基类的构造函数初始化它,所以把你的接口变成一个抽象类。

 public abstract class IAnimal { @SerializedName("CLASSNAME") public String className; public IAnimal(...) { ... className = this.getClass().getName(); } } 

这里没有无限recursion的原因是我们将实际的运行时类(即Dog not IAnimal)传递给context.deserialize 。 这不会调用我们的types适配器,只要我们使用registerTypeAdapter而不是registerTypeHierarchyAdapter

如果你想为一个typespipe理一个TypeAdapter,而为他的子typespipe理一个TypeAdapter,你可以像这样使用TypeAdapterFactory:

 public class InheritanceTypeAdapterFactory implements TypeAdapterFactory { private Map<Class<?>, TypeAdapter<?>> adapters = new LinkedHashMap<>(); { adapters.put(Animal.class, new AnimalTypeAdapter()); adapters.put(Dog.class, new DogTypeAdapter()); } @SuppressWarnings("unchecked") @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { TypeAdapter<T> typeAdapter = null; Class<?> currentType = Object.class; for (Class<?> type : adapters.keySet()) { if (type.isAssignableFrom(typeToken.getRawType())) { if (currentType.isAssignableFrom(type)) { currentType = type; typeAdapter = (TypeAdapter<T>)adapters.get(type); } } } return typeAdapter; } } 

这家工厂将发送最准确的TypeAdapter

Google已经发布了自己的RuntimeTypeAdapterFactory来处理多态,但是不幸的是它不是gson核心的一部分。

在这里,我已经发布了一个使用动物,狗和猫模型的例子。

我认为最好是依靠这个适配器,而不是从零开始重新实现它。