Gson:基于另一个字段的动态字段解析

5
我有以下两种类型的JSON字符串,它们都有一个"类型"字段,但"内容"字段结构不同。
{"type": "Login", "content": {"username": "a", "password": "b"}}

{"type": "Forward", "content": {"deviceId": "a", "password": "b"}}

我愿意将它们解析为Java对象。现在我有两个类:
class Login {
    String username;
    String password;
}

class Forward {
    String deviceId;
}

现在的问题是,我必须首先解析json消息的“type”部分,然后才能决定使用哪个类来继续解析“content”。
有没有一种方法可以进行“两步”解析?
我已经尝试了以下方法:
首先将json消息解析为这个类的实例:
class Request {
    RequesType type;
    String content;
}

根据类的解析“类型”字段,我可以决定使用哪个类,即使用res = gson.fromJson(request.content, Forward.class);res = gson.fromJson(content, Login.class);但是我收到的json如下:
{"type": "Forward", "content":{"deviceId":"1"}}

我遇到了一个错误,期望是字符串类型,但实际上是对象类型,所以我不知道该怎样继续操作。欢迎提出任何建议!

2个回答

2
也许您想使用GSON的RuntimeTypeAdapterFactory。它允许您从类型字段自动确定要反序列化的类的类型:默认情况下方便地命名为type
为了使GSON能够反序列化,我建议对您的POJO进行一些小修改。像这样创建两个用于Request的类:
@Getter @Setter
public class Request {
    private String type;
    @Getter @Setter
    public static class Content {
        String password;
    }
}

覆盖它并根据请求类型设置内容,例如:
@Getter @Setter
public class ForwardRequest extends Request {
    @Getter @Setter
    public static class ForwardContent extends Content {
        private String deviceId;
    }
    private ForwardContent content;
}

并且

@Getter @Setter
public class LoginRequest extends Request  {
    @Getter @Setter
    public static class LoginContent extends Content {
        private String username;
    }
    private LoginContent content;
}

像上面这样的课程只需要:
@Test
public void test() {
    // Tell the top class
    RuntimeTypeAdapterFactory<Request> rttaf = RuntimeTypeAdapterFactory.of(Request.class)
        // Register inheriting types and the values per type field
        .registerSubtype(ForwardRequest.class, "Forward")
        .registerSubtype(LoginRequest.class, "Login");
    Gson gson = new GsonBuilder().setPrettyPrinting()
        .registerTypeAdapterFactory(rttaf)
        .create();
    // Constructed an array of your two types to be deserialized at the same time  
    String jsonArr = "["
          + "{\"type\": \"Login\", \"content\": {\"username\": \"a\", \"password\": \"b\"}}"
          + ","
          + "{\"type\": \"Forward\", \"content\": {\"deviceId\": \"a\", \"password\": \"b\"}}"
          + "]";
    // Deserialize the array as Request[]
    Request[] requests = gson.fromJson(jsonArr, Request[].class);
    log.info("{}", requests[0].getClass());
    log.info("{}", requests[1].getClass());   
}

上述输出类似于:
class org.example.gson.LoginRequest class org.example.gson.ForwardRequest
您只需要复制答案顶部提供的链接中提供的文件以包含 RuntimeTypeAdapterFactory 到您的项目中。
更新:关于反序列化 type 或任何其他包含类型信息的字段:GSON 有意省略它。它不是一种瞬态字段吗?在反序列化之前,您需要知道 type 的值,因此 GSON 不再费心去反序列化它。
作为另一个例子,为了澄清这一点,如果您将测试字符串更改为:
String jsonArr = "["
      + "{\"type\": \"LoginRequest\", \"content\": {\"username\": \"a\", \"password\": \"b\"}}"
      + ","
      + "{\"type\": \"ForwardRequest\", \"content\": {\"deviceId\": \"a\", \"password\": \"b\"}}"
      + "]";

假设type保存的是类的简单名称(通常如此),你可以像下面这样注册子类型:
.registerSubtype(ForwardRequest.class)
.registerSubtype(LoginRequest.class);

GSON期望JSON的type属性的值为类的简单名称。为什么你会将类名保存在单独的字段中呢?因为它可以通过Class.getSimpleName()方法获取。
然而,有时候你可能需要将它序列化给其他客户端。

非常感谢您提供的信息和课程组织建议。效果非常好!只有一个小问题,似乎在反序列化后,请求对象上的“type”字段为空。但这并不是很重要。再次感谢您的回答! - Patrick

1
  • 您可以使用JSONObject解析您的json,使用其has(String key)方法来检查此Json中是否存在键
  • 根据deviceId键是否存在,您肯定可以将json字符串解析为所需的Java类。

在此处检查代码:

 String str="{\"type\": \"Forward\", \"content\": {\"deviceId\": \"a\", \"password\": \"b\"}}";
     Object obj=JSONValue.parse(str);
     JSONObject json = (JSONObject) obj;
     //Then use has method to check if this key exists or not
     System.out.println(json.has("deviceId"));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接