与gson的多态性

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添加另一个字段都很困难.

  • 错误.此解决方案不起作用.如果以任何方式调用context.serialize,您将最终得到无限递归.我想知道为什么人们在没有实际测试代码的情 我试过2.2.1.请参阅http://stackoverflow.com/questions/13244769/gson-polymorphic-serialization中描述的错误 (6认同)
  • 请注意,使用Class.forName对类名进行序列化和反序列化(来自用户输入)会在某些情况下出现安全隐患,因此Gson开发团队不鼓励这样做.https://code.google.com/p/google-gson/issues/detail?id=340#c2 (5认同)
  • 你是如何管理不在序列化中获得无限循环的,你是在调用context.serialize(src); 这将再次调用您的适配器.这就是我的类似代码中发生的事情. (4认同)
  • @MarcusJuniusBrutus我运行了你的代码,它似乎只适用于这种特殊情况 - 因为你已经定义了一个超接口IAnimal,而IAnimalAdapter使用它.如果您只使用'Cat',那么您将获得无限递归问题.因此,只有当您能够定义公共接口时,此解决方案仍然无法在一般情况下工作.在我的情况下,没有接口,所以我不得不使用TypeAdapterFactory的不同方法. (4认同)
  • 用户src.getClass().getName()而不是src.getClass().getCanonicalName().这说明代码也适用于内部/嵌套类. (2认同)

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)


k.c*_*ham 6

GSON在这里有一个非常好的测试用例,展示了如何定义和注册类型层次结构适配器.

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();
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"属性.这是非常非面向对象的.


db8*_*b80 6

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)

在这里,我使用动物、狗和猫模型发布了一个完整的工作示例。

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