简体   繁体   English

与gson的多态性

[英]Polymorphism with gson

I have a problem deserializing a json string with Gson. 我在使用Gson反序列化json字符串时遇到问题。 I receive an array of commands. 我收到一系列命令。 The command can be start, stop , some other type of command. 该命令可以是start,stop,其他一些命令。 Naturally I have polymorphism, and start/stop command inherit from command. 当然我有多态,并且start / stop命令继承自命令。

How can I serialize it back to the correct command object using gson? 如何使用gson将其序列化回正确的命令对象?

Seems that I get only the base type, that is the declared type and never the runtime type. 似乎我只得到基类型,即声明的类型,而不是运行时类型。

This is a bit late but I had to do exactly the same thing today. 这有点晚了,但今天我必须做同样的事情。 So, based on my research and when using gson-2.0 you really don't want to use the registerTypeHierarchyAdapter method, but rather the more mundane registerTypeAdapter . 因此,基于我的研究和使用gson-2.0时,你真的不想使用registerTypeHierarchyAdapter方法,而是更普通的registerTypeAdapter And you certainly don't need to do instanceofs or write adapters for the derived classes: just one adapter for the base class or interface, provided of course that you are happy with the default serialization of the derived classes. 并且您当然不需要为派生类执行instanceofs或编写适配器:只需要一个适用于基类或接口的适配器,当然,前提是您对派生类的默认序列化感到满意。 Anyway, here's the code (package and imports removed) (also available in github ): 无论如何,这里是代码(删除了包和导入)(也可以在github中使用 ):

The base class (interface in my case): 基类(在我的例子中是接口):

public interface IAnimal { public String sound(); }

The two derived classes, Cat: 两个派生类,Cat:

public class Cat implements IAnimal {

    public String name;

    public Cat(String name) {
        super();
        this.name = name;
    }

    @Override
    public String sound() {
        return name + " : \"meaow\"";
    };
}

And Dog: 和狗:

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: 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);
    }
}

And the Test class: 和测试类:

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());
        }
    }
}

When you run the Test::main you get the following output: 当您运行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)

I've actually done the above using the registerTypeHierarchyAdapter method too, but that seemed to require implementing custom DogAdapter and CatAdapter serializer/deserializer classes which are a pain to maintain any time you want to add another field to Dog or to Cat. 我实际上也使用了registerTypeHierarchyAdapter方法完成了上述操作,但这似乎需要实现自定义DogAdapter和CatAdapter序列化器/反序列化器类,这对于维护任何时候想要向Dog或Cat添加另一个字段都很困难。

Gson currently has a mechanism to register a Type Hierarchy Adapter that reportedly can be configured for simple polymorphic deserialization, but I don't see how that's the case, as a Type Hierarchy Adapter appears to just be a combined serializer/deserializer/instance creator, leaving the details of instance creation up to the coder, without providing any actual polymorphic type registration. Gson目前有一种注册类型层次结构适配器的机制,据报道它可以配置为简单的多态反序列化,但我不知道是怎么回事,因为类型层次结构适配器似乎只是一个组合的序列化器/反序列化器/实例创建器,将实例创建的细节留给编码器,而不提供任何实际的多态类型注册。

It looks like Gson will soon have the RuntimeTypeAdapter for simpler polymorphic deserialization. 看起来Gson很快将拥有RuntimeTypeAdapter以实现更简单的多态反序列化。 See http://code.google.com/p/google-gson/issues/detail?id=231 for more info. 有关详细信息,请参阅http://code.google.com/p/google-gson/issues/detail?id=231

If use of the new RuntimeTypeAdapter isn't possible, and you gotta use Gson, then I think you'll have to roll your own solution, registering a custom deserializer either as a Type Hierarchy Adapter or as Type Adapter. 如果无法使用新的RuntimeTypeAdapter ,并且你必须使用Gson,那么我认为你必须推出自己的解决方案,将自定义反序列化器注册为类型层次结构适配器或类型适配器。 Following is one such example. 以下是一个这样的例子。

// 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);
  }
}

Marcus Junius Brutus had a great answer (thanks!). Marcus Junius Brutus有一个很好的答案(谢谢!)。 To extend his example, you can make his adapter class generic to work for all types of objects (Not just IAnimal) with the following changes: 要扩展他的示例,您可以使其适配器类通用,以适用于所有类型的对象(不仅仅是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
....
}

And in the Test Class: 在测试类中:

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

GSON has a pretty good test case here showing how to define and register a type hierarchy adapter. GSON在这里有一个非常好的测试用例,展示了如何定义和注册类型层次结构适配器。

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

To use that do this: 要使用它,请执行此操作:

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

Serialize method of the adapter can be a cascading if-else check of what type it is serializing. 适配器的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;

Deserializing is a bit hacky. 反序列化有点hacky。 In the unit test example, it checks for existence of tell-tale attributes to decide which class to deserialized to. 在单元测试示例中,它检查是否存在tell-tale属性以决定要反序列化的类。 If you can change the source of the object you're serializing, you can add a 'classType' attribute to each instance which holds the FQN of the instance class's name. 如果您可以更改要序列化的对象的源,则可以为每个保存实例类名称的FQN的实例添加“classType”属性。 This is so very un-object-oriented though. 这是非常非面向对象的。

Google has released its own RuntimeTypeAdapterFactory to handle the polymorphism but unfortunately it's not part of the gson core (you must copy and paste the class inside your project). 谷歌已经发布了自己的RuntimeTypeAdapterFactory来处理多态,但不幸的是它不是gson核心的一部分(你必须在你的项目中复制和粘贴这个类)。

Example: 例:

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
    .create();

Here I have posted a full working example of it using the models Animal, Dog and Cat. 在这里,我使用Animal,Dog和Cat模型发布了一个完整的工作示例。

I think it's better to rely on this adapter rather than reimplement it from scratch. 我认为最好依靠这个适配器而不是从头开始重新实现它。

Long time has passed, but I couldn't find a really good solution online.. Here is small twist on @MarcusJuniusBrutus's solution, that avoids the infinite recursion. 很长一段时间过去了,但我找不到一个非常好的在线解决方案。这是@ MarcusJuniusBrutus解决方案的小转折,避免了无限递归。

Keep the same deserializer, but remove the serializer - 保持相同的解串器,但删除序列化器 -

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);
  }
}

Then, in your original class, add a field with @SerializedName("CLASSNAME") . 然后,在原始类中,添加一个带@SerializedName("CLASSNAME")的字段。 The trick is now to initialize this in the constructor of the base class , so make your interface into a an abstract class. 现在的技巧是在基类的构造函数初始化它,所以将你的接口变成一个抽象类。

public abstract class IAnimal {
  @SerializedName("CLASSNAME")
  public String className;

  public IAnimal(...) {
    ...
    className = this.getClass().getName();
  }
}

The reason there is no infinite recursion here is that we pass the actual runtime class (ie Dog not IAnimal) to context.deserialize . 这里没有无限递归的原因是我们将实际的运行时类(即Dog not IAnimal)传递给context.deserialize This will not call our type adapter, as long as we use registerTypeAdapter and not registerTypeHierarchyAdapter 只要我们使用registerTypeAdapter而不是registerTypeHierarchyAdapter ,这就不会调用我们的类型适配器

If you want manage a TypeAdapter for a type and an other for his sub type, you can use a TypeAdapterFactory like this : 如果要为类型管理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;
    }
}

This factory will send the most accurate TypeAdapter 该工厂将发送最准确的TypeAdapter

Updated Answer - Best parts of all other answers 更新的答案 - 所有其他答案的最佳部分

I am describing solutions for various use cases and would be addressing the infinite recursion problem as well 我正在描述各种用例的解决方案,并且也将解决无限递归问题

  • Case 1: You are in control of the classes , ie, you get to write your own Cat , Dog classes as well as the IAnimal interface. 案例1: 您可以控制类 ,即您可以编写自己的CatDog类以及IAnimal接口。 You can simply follow the solution provided by @marcus-junius-brutus(the top rated answer) 你可以简单地按照@ marcus-junius-brutus提供的解决方案(评价最高的答案)

    There won't be any infinite recursion if there is a common base interface as IAnimal 如果有一个公共基接口作为IAnimal则不会有任何无限递归

    But, what if I don't want to implement the IAnimal or any such interface? 但是,如果我不想实现IAnimal或任何此类接口,该怎么办?

    Then, @marcus-junius-brutus(the top rated answer) will produce an infinite recursion error. 然后,@ marcus-junius-brutus(评价最高的答案)将产生无限递归错误。 In this case, we can do something like below. 在这种情况下,我们可以做类似下面的事情。

    We would have to create a copy constructor inside the base class and a wrapper subclass as follows: 我们必须在基类和包装器子类中创建一个复制构造函数 ,如下所示:

.

// Base class(modified)
public class Cat implements IAnimal {

    public String name;

    public Cat(String name) {
        super();
        this.name = name;
    }
    // COPY CONSTRUCTOR
    public Cat(Cat cat) {
        this.name = cat.name;
    }

    @Override
    public String sound() {
        return name + " : \"meaow\"";
    };
}



    // The wrapper subclass for serialization
public class CatWrapper extends Cat{


    public CatWrapper(String name) {
        super(name);
    }

    public CatWrapper(Cat cat) {
        super(cat);
    }
}

And the serializer for the type Cat : Cat型的序列化器:

public class CatSerializer implements JsonSerializer<Cat> {

    @Override
    public JsonElement serialize(Cat src, Type typeOfSrc, JsonSerializationContext context) {

        // Essentially the same as the type Cat
        JsonElement catWrapped = context.serialize(new CatWrapper(src));

        // Here, we can customize the generated JSON from the wrapper as we want.
        // We can add a field, remove a field, etc.


        return modifyJSON(catWrapped);
    }

    private JsonElement modifyJSON(JsonElement base){
        // TODO: Modify something
        return base;
    }
}

So, why a copy constructor? 那么,为什么要复制构造函数呢?

Well, once you define the copy constructor, no matter how much the base class changes, your wrapper will continue with the same role. 好吧,一旦定义了复制构造函数,无论基类改变多少,您的包装器都将继续使用相同的角色。 Secondly, if we don't define a copy constructor and simply subclass the base class then we would have to "talk" in terms of the extended class, ie, CatWrapper . 其次,如果我们不定义复制构造函数并简单地子类化基类,那么我们就必须根据扩展类(即CatWrapper进行“交谈”。 It is quite possible that your components talk in terms of the base class and not the wrapper type. 您的组件很可能会根据基类而不是包装类型进行讨论。

Is there an easy alternative? 有一个简单的替代方案吗?

Sure, it has now been introduced by Google - this is the RuntimeTypeAdapterFactory implementation: 当然,谷歌已经引入了它 - 这是RuntimeTypeAdapterFactory实现:

RuntimeTypeAdapterFactory<Animal> runtimeTypeAdapterFactory = RuntimeTypeAdapterFactory
.of(Animal.class, "type")
.registerSubtype(Dog.class, "dog")
.registerSubtype(Cat.class, "cat");

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(runtimeTypeAdapterFactory)
    .create();

Here, you would need to introduce a field called "type" in Animal and the value of the same inside Dog to be "dog", Cat to be "cat" 在这里,您需要在Animal引入一个名为“type”的字段,并将Dog内部的值设为“dog”, Cat为“cat”

Complete example: https://static.javadoc.io/org.danilopianini/gson-extras/0.2.1/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.html 完整示例: https//static.javadoc.io/org.danilopianini/gson-extras/0.2.1/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.html

  • Case 2: You are not in control of the classes . 案例2: 你无法掌控这些课程 You join a company or use a library where the classes are already defined and your manager doesn't want you to change them in any way - You can subclass your classes and have them implement a common marker interface(which doesn't have any methods) such as AnimalInterface . 您加入公司或使用已定义类的库,并且您的经理不希望您以任何方式更改它们 - 您可以对类进行子类化并让它们实现公共标记接口(没有任何方法) )如AnimalInterface

    Ex: 例如:

.

// The class we are NOT allowed to modify

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 marker interface

public interface AnimalInterface {
}

// The subclass for serialization

public class DogWrapper  extends Dog implements AnimalInterface{

    public DogWrapper(String name, int ferocity) {
        super(name, ferocity);
    }

}

// The subclass for serialization

public class CatWrapper extends Cat implements AnimalInterface{


    public CatWrapper(String name) {
        super(name);
    }
}

So, we would be using CatWrapper instead of Cat , DogWrapper instead of Dog and AlternativeAnimalAdapter instead of IAnimalAdapter 因此,我们将使用CatWrapper而不是CatDogWrapper而不是DogAlternativeAnimalAdapter而不是IAnimalAdapter

// The only difference between `IAnimalAdapter` and `AlternativeAnimalAdapter` is that of the interface, i.e, `AnimalInterface` instead of `IAnimal`

public class AlternativeAnimalAdapter implements JsonSerializer<AnimalInterface>, JsonDeserializer<AnimalInterface> {

    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE  = "INSTANCE";

    @Override
    public JsonElement serialize(AnimalInterface 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 AnimalInterface 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);
    }
}

We perform a test: 我们进行测试:

public class Test {

    public static void main(String[] args) {

        // Note that we are using the extended classes instead of the base ones
        IAnimal animals[] = new IAnimal[]{new CatWrapper("Kitty"), new DogWrapper("Brutus", 5)};
        Gson gsonExt = null;
        {
            GsonBuilder builder = new GsonBuilder();
            builder.registerTypeAdapter(AnimalInterface.class, new AlternativeAnimalAdapter());
            gsonExt = builder.create();
        }
        for (IAnimal animal : animals) {
            String animalJson = gsonExt.toJson(animal, AnimalInterface.class);
            System.out.println("serialized with the custom serializer:" + animalJson);
            AnimalInterface animal2 = gsonExt.fromJson(animalJson, AnimalInterface.class);
        }
    }
}

Output: 输出:

serialized with the custom serializer:{"CLASSNAME":"com.examples_so.CatWrapper","INSTANCE":{"name":"Kitty"}}
serialized with the custom serializer:{"CLASSNAME":"com.examples_so.DogWrapper","INSTANCE":{"name":"Brutus","ferocity":5}}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM