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

标签 java json gson

我有以下两种类型的 JSON 字符串,它们都有一个“type”字段,但“content”字段具有不同的结构。

{"type": "Login", "content": {"username": "a", "password": "b"}}

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

我想将它们解析为 Java 对象。现在我有两个类(class):

class Login {
    String username;
    String password;
}

class Forward {
    String deviceId;
}

现在的问题是,我必须先解析 json 消息的“部分”(即“类型”部分),然后才能决定使用哪个类来继续解析“内容”。

有没有办法进行这种“两步”解析?

我尝试过以下方法:
首先将json消息解析成该类的实例:

class Request {
    RequesType type;
    String content;
}

然后根据类的解析“type”字段,我可以决定使用哪个类,即使用 res = gson.fromJson(request.content, Forward.class);res = gson.fromJson(content, Login.class);

但是我传入的 json 是

{"type": "Forward", "content":{"deviceId":"1"}}

我收到的错误是预期的字符串,但实际上是对象,所以我不知道如何继续。任何建议将不胜感激!

最佳答案

也许你想使用 GSON 的 RuntimeTypeAdapterFactory 。它允许您从 type 字段确定要自动反序列化的类的类型:默认情况下方便地命名为 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 故意将其遗漏。它是一种 transient 场,不是吗?在反序列化之前,您需要知道 type 的值,这样 GSON 就不再费心去反序列化它了。

作为另一个例子 - 为了澄清这一点 - 如果您更改测试字符串,例如:

String jsonArr = "["
      + "{\"type\": \"LoginRequest\", \"content\": {\"username\": \"a\", \"password\": \"b\"}}"
      + ","
      + "{\"type\": \"ForwardRequest\", \"content\": {\"deviceId\": \"a\", \"password\": \"b\"}}"
      + "]";

因此该类型包含类的简单名称(通常是 情况)你可以注册子类型,就像:

.registerSubtype(ForwardRequest.class)
.registerSubtype(LoginRequest.class);

并且 GSON 期望类简单名称作为 JSON 类型属性的值。为什么要将类名保存在单独的字段中,因为它是可获取的 Class.getSimpleName()

但是,您当然有时可能需要将其序列化到其他客户端。

关于java - Gson:基于另一个字段的动态字段解析,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54849784/

相关文章:

java - 存储和检索系统、ip 和用户名的列表

java - Spring Cloud DataFlow - 如何在 TCP 源中使用自定义 TCP 编码器/解码器

java - 读取 Json 对象并将所需值存储在 Map 中

java - 如何使用变量中提供的类型使用 GSON.fromJson ?

Java ServiceLoader 解释

Java CLI : Cant parse arguments

c# - 如何生成以整数为键的json字符串?

c# - 读取、写入和附加到 JSON 文件

javascript - 解析 JSON 数组出现错误

java - 使用 Gson 从 JSOn 对象读取每一行