JSON(Gson)反序列化为超类对象然后强制转换为子类对象的最佳方法是什么

时间:2013-11-25 05:49:09

标签: android json serialization deserialization gson

我目前正在学习在Android上使用Gson for Json,我刚刚遇到了这个问题。假设我们有如下课程:

    class Command {
        public int id = COMMAND_ID_UNSPECIFIED;
    }

    class CommandSpecific1 extends Command{
        public String specialStr;
        public CommandSpecific1 () {
            id = COMMAND_ID_SPECIAL1;
            specialStr= "special";
        }
    }
    class CommandSpecific2 extends Command{
        public int specialInt;
        public CommandSpecific2 () {
            id = COMMAND_ID_SPECIAL2;
            specialInt = 3.1415926;
        }
    }

我使用以下代码创建Json字符串

CommandSpecific specialCmd = new CommandSpecific();
Gson gson = new Gson();
String json = gson.toJson(specialCmd);

现在我想做这样的事情(错误的代码)

Command genericCmd = gson.fromJson(json, Command.class)
if(genericCmd.id == COMMAND_ID_SPECIAL1) {
    CommandSpecific1 cmd1 = (CommandSpecific1)genericCmd;
    //do sth with cmd1.specialStr
} else if(genericCmd.id == COMMAND_ID_SPECIAL2) {
    CommandSpecific2 cmd2 = (CommandSpecific2)genericCmd;
    //do sth with cmd2.specialInt
}

代码不起作用,因为gson.fromJson(json,Command.class)只为超类创建对象。 我知道我可以通过指定真正的类类型来调用fromJson,但有没有更好的方法呢? 我应该使用自定义的反序列化方法来解决这个问题吗?怎么样?

3 个答案:

答案 0 :(得分:2)

我会像这个准备好的例子一样尝试反序列化。作为注释,您不希望使用id字段在命令之间切换,因此您必须信任字段结构并假设每个子类都存在唯一标识子类的字段组合。

package stackoverflow.questions.q20185625;

import java.lang.reflect.Type;

import com.google.gson.*;

public class Q20185625 {

   public static class Command {
      public int id = -1;
   }

   public static class CommandSpecific1 extends Command {
      public String specialStr;

      public CommandSpecific1() {
         id = 1;
         specialStr = "special";
      }
   }

   public static class CommandSpecific2 extends Command {
      public int specialInt;

      public CommandSpecific2() {
         id = 2;
         specialInt = 42;
      }
   }

   public static class CustomDeserializer implements JsonDeserializer<Command> {

      public Command deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

         if (json == null)
            return null;
         else {
            JsonElement e = json.getAsJsonObject().get("specialStr");
            if (e != null && e.isJsonPrimitive() && e.getAsString() instanceof String) {
               CommandSpecific1 c = new CommandSpecific1();
               c.specialStr = e.getAsString(); // do you need this?
               return c;
            }

            e = json.getAsJsonObject().get("specialInt");
            if (e != null && e.isJsonPrimitive() && e.getAsNumber() instanceof Number) {
               CommandSpecific2 c = new CommandSpecific2();
               c.specialInt = e.getAsInt(); // do you need this?
               return c;
            }
            return null; // or throw an IllegalArgumentException

         }

      }

   }

   public static void main(String[] args) {
      GsonBuilder gb = new GsonBuilder();
      gb.registerTypeAdapter(Command.class, new CustomDeserializer());
      Gson customGson = gb.create();

      String jsonTest1 = "{\"specialStr\":\"AA\"}";
      String jsonTest2 = "{\"specialInt\":13}";
      String jsonTest3 = "{}";
      String jsonTest4 = "";

      System.out.println("Deserialize test 1: " + customGson.fromJson(jsonTest1, Command.class));
      System.out.println("Deserialize test 2: " + customGson.fromJson(jsonTest2, Command.class));
      System.out.println("Deserialize test 3: " + customGson.fromJson(jsonTest3, Command.class));
      System.out.println("Deserialize test 4: " + customGson.fromJson(jsonTest4, Command.class));

   }

}

这就是结果(我避免使用toString,向你展示类类型)

Deserialize test 1: stackoverflow.questions.qww.Q20185625$CommandSpecific1@30d5aa
Deserialize test 2: stackoverflow.questions.qww.Q20185625$CommandSpecific2@13552ed
Deserialize test 3: null
Deserialize test 4: null

修改 如果您的JSON包含id字段,则更容易。您总是可以使用TypeAdapter,但方式很简单:

public static class CustomDeserializer implements JsonDeserializer<Command> {

      public Command deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

         if (json == null)
            return null;
         else {
            // null management can be improved
            int id = json.getAsJsonObject().get("id").getAsInt();
            switch(id){
               case COMMAND_TYPE_1:
                  return context.deserialize(json, CommandSpecific1.class);
               case COMMAND_TYPE_2:
                  return context.deserialize(json, CommandSpecific2.class);
               default:
                  return null; 
            }
         }

      }

   }

但是,如果您对JSON的其余部分感兴趣并且您对性能感到担心(但这不是原始问题,您询问了子类化问题),您可以尝试TypeAdapter

答案 1 :(得分:1)

fromJson(String json, Class classOfT)

来自docs:

  

此方法将指定的Json反序列化为对象   指定的类。如果指定的类是a,则不适合使用   泛型类型,因为它不具有泛型类型信息   因为Java的Type Erasure功能。因此,这种方法   如果所需类型是泛型类型,则不应使用。注意   如果指定的任何字段,此方法工作正常   对象是泛型,只是对象本身不应该是通用的   类型。

所以你必须在使用fromJson方法时使用确切的类。

如果要创建通用的反序列化方法,可以实现如下所示:

public Command deseralizeJson(String jsonString, Class targetClass) {
    return (Command)gson.fromJson(jsonString, targetClass);      
}

或更通用:

public Object deseralizeJson(String jsonString, Class targetClass) {
    return gson.fromJson(jsonString, targetClass);      
}

使用此方法时:

CommandSpecific1 cmd1 = (CommandSpecific1)deseralizeJson(json, CommandSpecific1.class);

CommandSpecific2 cmd2 = (CommandSpecific2)deseralizeJson(json, CommandSpecific2.class);

修改 我现在理解你的主要问题。在读取id的值之前,您不知道json响应的类型。

您可以像现在一样继续反序列化两次。 (因为GSON在反序列化时需要精确的目标类) 或者使用android的JSONObject。如;

JSONObject jsonObject = new JSONObject(json);
// JsonString is converted to a JSONObject, it is much more efficient than gson serialization
int id = jsonObject.getInt("id");
if(id == COMMAND_ID_SPECIAL1) {
    CommandSpecific1 cmd1 = (CommandSpecific1)deseralizeJson(json, CommandSpecific1.class);
    //do sth with cmd1.specialStr
} else if(id == COMMAND_ID_SPECIAL2) {
    CommandSpecific2 cmd2 = (CommandSpecific2)deseralizeJson(json, CommandSpecific2.class);
    //do sth with cmd2.specialInt
}

答案 2 :(得分:0)

试试这个:

Command genericCmd = gson.fromJson(json, Command.class)
if(genericCmd.id == COMMAND_ID_SPECIAL1) {
    CommandSpecific1 cmd1 = gson.fromJson(json, CommandSpecific1.class);
    //do sth with cmd1.specialStr
} else if(genericCmd.id == COMMAND_ID_SPECIAL2) {
    CommandSpecific2 cmd2 = gson.fromJson(json, CommandSpecific2.class);
    //do sth with cmd2.specialInt
}