【问题标题】:Gson: Dynamic field parsing based on another fieldGson:基于另一个字段的动态字段解析
【发布时间】:2019-07-17 21:18:26
【问题描述】:

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

{"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 消息的“部分”(即“类型”部分),然后才能决定使用哪个类来继续解析“内容”。

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

我试过以下方法:
先把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"}}

我得到了预期的错误是 String 但是 Object 所以我不知道如何继续。任何建议将不胜感激!

【问题讨论】:

    标签: java json gson


    【解决方案1】:

    也许您想使用 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());   
    }
    

    上面的输出类似于:

    类 org.example.gson.LoginRequest
    类 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\"}}"
          + "]";
    

    因此该类型包含简单的类名称(通常是 case) 你可以像这样注册子类型:

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

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

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

    【讨论】:

    • 非常感谢您提供的信息和班级组织建议。工作完美!只是一件小事,似乎在反序列化之后,请求对象上的“类型”字段为空。但这不是很重要。再次感谢您的回答!
    【解决方案2】:
    • 您可以使用 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"));
    

    【讨论】:

      猜你喜欢
      • 2011-11-30
      • 2016-04-19
      • 1970-01-01
      • 2017-02-02
      • 2011-08-13
      • 1970-01-01
      • 2016-03-08
      • 2021-02-24
      • 1970-01-01
      相关资源
      最近更新 更多