具有 gson 的多态性
我在使用Gson反序列化json字符串时遇到问题。我收到一组命令。该命令可以是启动、停止、某种其他类型的命令。当然,我有多态性,启动/停止命令继承自命令。
如何使用gson将其序列化回正确的命令对象?
似乎我只得到基类型,即声明的类型,而不是运行时类型。
我在使用Gson反序列化json字符串时遇到问题。我收到一组命令。该命令可以是启动、停止、某种其他类型的命令。当然,我有多态性,启动/停止命令继承自命令。
如何使用gson将其序列化回正确的命令对象?
似乎我只得到基类型,即声明的类型,而不是运行时类型。
这有点晚了,但我今天不得不做同样的事情。因此,根据我的研究,当使用gson-2.0时,你真的不想使用registerTypeHierarchyAdapter方法,而是更平凡的registerTypeAdapter。当然,您不需要为派生类执行 instanceof 或编写适配器:只需为基类或接口编写一个适配器,当然前提是您对派生类的默认序列化感到满意。无论如何,这是代码(删除了包和导入)(在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 + ")";
}
}
The 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);
}
}
和测试类:
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 目前有一种机制来注册一个类型层次结构适配器,据报道,该适配器可以配置为简单的多态反序列化,但我不明白情况如何,因为类型层次结构适配器似乎只是一个组合的序列化程序/反序列化程序/实例创建者,将实例创建的详细信息留给编码人员,而不提供任何实际的多态类型注册。
看起来Gson很快就会有更简单的多态反序列化。有关详细信息,请参阅 http://code.google.com/p/google-gson/issues/detail?id=231。RuntimeTypeAdapter
如果无法使用新的,并且您必须使用Gson,那么我认为您必须推出自己的解决方案,将自定义反序列化程序注册为类型层次结构适配器或类型适配器。下面是一个这样的例子。RuntimeTypeAdapter
// 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);
}
}