有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

基于gson的java多态性

我在用Gson反序列化json字符串时遇到问题。 我收到一系列命令。该命令可以是启动、停止或其他类型的命令。当然我有多态性,启动/停止命令继承自命令

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

似乎我只得到基类型,即声明的类型,而不是运行时类型


共 (6) 个答案

  1. # 1 楼答案

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

    适配器的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;
    

    反序列化有点老套。在单元测试示例中,它检查是否存在指示灯属性,以决定反序列化到哪个类。如果可以更改要序列化的对象的源,则可以为每个实例添加一个“classType”属性,该属性包含实例类名称的FQN。但这是非常不面向对象的

  2. # 2 楼答案

    这有点晚了,但我今天不得不做同样的事情。因此,根据我的研究,在使用gson-2.0时,你真的不想使用registerTypeHierarchyAdapter方法,而是更普通的registerTypeAdapter。当然,您不需要为派生类执行instanceofs或编写适配器:只需为基类或接口编写一个适配器,当然前提是您对派生类的默认序列化感到满意。无论如何,下面是代码(已删除包和导入)(也可在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);
        }
    }
    

    还有测试课:

    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添加另一个字段时都很难维护的

  3. # 3 楼答案

    谷歌已经发布了自己的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();
    

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

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

  4. # 4 楼答案

    马库斯·朱尼乌斯·布鲁特斯回答得很好(谢谢!)。为了扩展他的示例,您可以通过以下更改使他的适配器类通用,以适用于所有类型的对象(不仅仅是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
    ....
    }
    

    在测试课上:

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

    很长时间过去了,但我在网上找不到真正好的解决方案。。 这里是@MarcusJuniusBrutus解决方案的一个小转折,它避免了无限递归

    保留相同的反序列化程序,但删除序列化程序-

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

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

  6. # 6 楼答案

    Gson目前有一个register a Type Hierarchy Adapter机制,据报道可以配置为简单的多态反序列化,但我不知道是怎么回事,因为类型层次结构适配器似乎只是一个组合的序列化器/反序列化器/实例创建者,实例创建的细节由编码器决定,不提供任何实际的多态类型注册

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

    如果无法使用新的RuntimeTypeAdapter,而您必须使用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);
      }
    }