Sop*_*hie 95 java polymorphism json gson deserialization
我在使用Gson反序列化json字符串时遇到问题.我收到一系列命令.该命令可以是start,stop,其他一些命令.当然我有多态,并且start/stop命令继承自命令.
如何使用gson将其序列化回正确的命令对象?
似乎我只得到基类型,即声明的类型,而不是运行时类型.
Mar*_*tus 116
这有点晚了,但今天我必须做同样的事情.所以,基于我的研究和使用gson-2.0时,你真的不想使用registerTypeHierarchyAdapter方法,而是更普通的registerTypeAdapter.并且您当然不需要为派生类执行instanceofs或编写适配器:只需要一个适用于基类或接口的适配器,当然,前提是您对派生类的默认序列化感到满意.无论如何,这里是代码(删除了包和导入)(也可以在github中使用):
基类(在我的例子中是接口):
public interface IAnimal { public String sound(); }
Run Code Online (Sandbox Code Playgroud)
两个派生类,Cat:
public class Cat implements IAnimal {
public String name;
public Cat(String name) {
super();
this.name = name;
}
@Override
public String sound() {
return name + " : \"meaow\"";
};
}
Run Code Online (Sandbox Code Playgroud)
和狗:
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 + ")";
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
和测试类:
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());
}
}
}
Run Code Online (Sandbox Code Playgroud)
当您运行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)
Run Code Online (Sandbox Code Playgroud)
我实际上也使用了registerTypeHierarchyAdapter方法完成了上述操作,但这似乎需要实现自定义DogAdapter和CatAdapter序列化器/反序列化器类,这对于维护任何时候想要向Dog或Cat添加另一个字段都很困难.
Pro*_*uce 12
Gson目前有一种注册类型层次结构适配器的机制,据报道它可以配置为简单的多态反序列化,但我不知道是怎么回事,因为类型层次结构适配器似乎只是一个组合的序列化器/反序列化器/实例创建器,将实例创建的细节留给编码器,而不提供任何实际的多态类型注册.
看起来Gson很快就会有RuntimeTypeAdapter更简单的多态反序列化.有关详细信息,请参阅http://code.google.com/p/google-gson/issues/detail?id=231.
如果RuntimeTypeAdapter无法使用new ,并且你必须使用Gson,那么我认为你必须推出自己的解决方案,将自定义反序列化器注册为类型层次结构适配器或类型适配器.以下是一个这样的例子.
// 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);
}
}
Run Code Online (Sandbox Code Playgroud)
小智 8
Marcus Junius Brutus有一个很好的答案(谢谢!).要扩展他的示例,您可以使其适配器类通用,以适用于所有类型的对象(不仅仅是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
....
}
Run Code Online (Sandbox Code Playgroud)
在测试类中:
public class Test {
public static void main(String[] args) {
....
builder.registerTypeAdapter(IAnimal.class, new InheritanceAdapter<IAnimal>());
....
}
Run Code Online (Sandbox Code Playgroud)
GSON在这里有一个非常好的测试用例,展示了如何定义和注册类型层次结构适配器.
要使用它,请执行此操作:
gson = new GsonBuilder()
.registerTypeAdapter(BaseQuestion.class, new BaseQuestionAdaptor())
.create();
Run Code Online (Sandbox Code Playgroud)
适配器的Serialize方法可以是级联if-else检查它序列化的类型.
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;
Run Code Online (Sandbox Code Playgroud)
反序列化有点hacky.在单元测试示例中,它检查是否存在tell-tale属性以决定要反序列化的类.如果您可以更改要序列化的对象的源,则可以为每个保存实例类名称的FQN的实例添加"classType"属性.这是非常非面向对象的.
Google 已经发布了自己的RuntimeTypeAdapterFactory来处理多态性,但不幸的是它不是 gson 核心的一部分(您必须将该类复制并粘贴到您的项目中)。
例子:
RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(runtimeTypeAdapterFactory)
.create();
Run Code Online (Sandbox Code Playgroud)
在这里,我使用动物、狗和猫模型发布了一个完整的工作示例。
我认为最好依靠这个适配器而不是从头开始重新实现它。