Android JSON(Gson)反序列化到超级类对象然后强制转换到子类对象的最佳方式是什么
我目前正在学习在Android上使用Gson for Json,我刚刚遇到了这个问题。假设我们有如下类别:Android JSON(Gson)反序列化到超级类对象然后强制转换到子类对象的最佳方式是什么,android,json,serialization,deserialization,gson,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 () {
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.fromJsonjson、Command.class仅为super类创建对象。
我知道我可以通过指定真实的类类型来调用fromJson,但是有没有更好的方法?
我应该使用定制的反序列化方法来解决这个问题吗?怎么做?试试这个:
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
}
试试这个:
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
}
从文档:
此方法将指定的Json反序列化为
指定的类。如果指定的类是
泛型类型,因为它没有泛型类型信息
因为Java的类型擦除特性。因此,这种方法
如果所需类型是泛型类型,则不应使用。注意
如果指定的
对象是泛型,只是对象本身不应该是泛型
类型
因此,在使用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);
或
编辑:
我现在明白你的主要问题了。在读取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
}
从文档:
此方法将指定的Json反序列化为
指定的类。如果指定的类是
泛型类型,因为它没有泛型类型信息
因为Java的类型擦除特性。因此,这种方法
如果所需类型是泛型类型,则不应使用。注意
如果指定的
对象是泛型,只是对象本身不应该是泛型
类型
因此,在使用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);
或
编辑:
我现在明白你的主要问题了。在读取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
}
我会尝试像这样反序列化准备运行的示例。作为注释,您不希望使用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));
}
}
编辑
如果您的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。我会尝试像这个准备运行的示例那样反序列化。作为注释,您不希望使用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));
}
}
编辑
如果您的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。这就是我目前所做的。但是除了再次调用fromJson方法,还有什么更好的方法吗?在这种情况下,不是首先将json转换为命令对象,使用字符串比较检查json字符串是否包含COMMAND_ID_SPECIAL1或COMMAND_ID_SPECIAL2,然后相应地将其转换为特定的COMMAND…COMMAND_ID_SPECIAL1和COMMAND_ID_SPECIAL2是整数。传递给fromJson方法的json是字符串,所以如果json.indexOfnew StringCOMMAND_ID_SPECIAL1>-1,请执行类似操作{CommandSpecific1 cmd1=gson.fromJsonjson,CommandSpecific1.class;}如果json.indexOfnew StringCOMMAND_ID_SPECIAL2>-1{CommandSpecific2 cmd2=gson.fromJsonjson,CommandSpecific2.class;}COMMAND_ID_SPECIAL1和COMMAND_ID_SPECIAL2是字段值,它是常量整数。它不会出现在json中。这是我目前所做的。但是除了再次调用fromJson方法之外,还有什么更好的方法吗?在这种情况下,不是首先将json转换为COMMAND对象,
使用字符串比较检查json字符串是否包含COMMAND_ID_SPECIAL1或COMMAND_ID_SPECIAL2,然后相应地将其转换为特定的COMMAND…COMMAND_ID_SPECIAL1和COMMAND_ID_SPECIAL2是整数。传递给fromJson方法的json是字符串,所以如果json.indexOfnew StringCOMMAND_ID_SPECIAL1>-1,请执行类似操作{CommandSpecific1 cmd1=gson.fromJsonjson,CommandSpecific1.class;}如果json.indexOfnew StringCOMMAND_ID_SPECIAL2>-1{CommandSpecific2 cmd2=gson.fromJsonjson,CommandSpecific2.class;}COMMAND_ID_SPECIAL1和COMMAND_ID_SPECIAL2是字段值,它是常量整数。它不会出现在json中。这仍然需要执行两次反序列化。我想要一些自定义方法,允许我只执行一次反序列化来查看ID字段并执行静态强制转换。反序列化两次?不,json反序列化到java对象一次。然后将其转换为相关类。要点是,gson在反序列化CommandSpecific1或CommandSpecific2时需要目标类。如果您传递了其中的一个超类,那么您只能反序列化超类的属性。根据我的情况,我需要执行genericCmd=gson.fromJsonjson,Command.class以获得查看ID字段,根据您的建议,我应该执行另一个cmd1=CommandSpecific1DeserializeJSONJSON,CommandSpecific1.class;来获取目标对象。两次,对吗?只有CommandSpecific1 cmd1=CommandSpecific1DeserializeJSONJSON,CommandSpecific1.class;就足够了。CommandSpecific1是Command的子类,所以它有属性ID。哦,我明白了。如果您如果只想继续使用gson,则必须反序列化twice,因为除非读取id值,否则您不知道目标类。您可以使用android的默认JSONObject类进行第一次解析。请参阅我的编辑。这仍然需要进行两次反序列化。我想要一些自定义方法,允许我只进行一次反序列化以查看id field并执行静态cast.deserialization两次?不,json反序列化为java对象一次。然后将其转换为相关类。要点是,gson在反序列化CommandSpecific1或CommandSpecific2时需要目标类。如果传递其中的一个超类,则只反序列化超类的属性。根据我的情况ion,我需要执行genericCmd=gson.fromJsonjson,Command.class以获得查看ID字段的机会,根据您的建议,我应该执行另一个cmd1=CommandSpecific1DeserializeJSONJSON,CommandSpecific1.class;以获取目标对象。两次,对吗?只有CommandSpecific1 cmd1=CommandSpecific1DeserializeJSONJSON,CommandSpecific1.class;够了。CommandSpecific1是Command的子类,因此它有属性id。哦,我明白了。如果你只想继续使用gson,你必须反序列化Twice1,因为你不知道目标类,除非你读取id值。你可以使用android的默认JSONObject类进行第一次解析。请参阅我的编辑。嗨,谢谢你发布了一些新的内容。你的swer与上面的不同。但是,我并没有说我不想切换ID字段。相反,我可以想象的最好的方法是这样做:步骤1。反序列化JSON只查看第一个ID字段。步骤2。反序列化JSON的左侧部分以获得正确的对象。这与步骤1类似,我们只是使用它第一个4字节的ID整数,在第二步中,我们使用了左边的字节。我认为这对于大型类来说很有意义。嗨,谢谢你发布了一些新的东西。你的答案与上面的答案真的不同。但是,我没有说我不想切换ID字段。相反,我能想到的最好方法是这样做:第1步。反序列化JSON仅用于查看第一个ID字段。STEP2.反序列化JSON的左侧部分以获得正确的对象。这类似于在STEP1中,我们只使用前4个字节的ID整数,而在STEP2中,我们使用左侧字节。我认为这对于大型类来说非常有意义。