【问题标题】:Deserializing unwrapped/flattened Json in Java在 Java 中反序列化解包/展平的 Json
【发布时间】:2015-12-22 00:46:44
【问题描述】:

我正在为一个有点复杂的 API 构建一个客户端库。但是,不同的响应对象之间存在显着的共性,如下所示:

{
    "response": "Success",
    "delay": 0.241,
    "time": 125425234,
    "message": null,
    "a": "Payloads"
}

{
    "response": "AuthFailure",
    "delay": 0.112,
    "time": 1324515123,
    "message": "Wrong password",
    "b": 1234
}

{
    "response": "Success",
    "delay": 0.294,
    "time": 12461246123,
    "message": null,
    "c": True
    "d": 245.1
}

我想分解出公共部分,并希望将它们反序列化为对象的组合:

Response<AData>
Response<BData>
Response<CDData>

(类定义看起来像):

class Response<T> {
    final Response response;
    final Double delay;
    final Timestamp time;
    final String message;
    final T inner;
    ...
}

class AData {
    final String a;
    ...
}

class BData {
    final int b;
    ...
}

class BData {
    final bool c;
    final double d;
    ...
}

这很像“JsonUnwrapped”杰克逊注解的逆。继承也可以。

不幸的是,我无法在 Jackson 中找到一种在不编写重要的附加模块的情况下与 ObjectMapper 系统的其余部分组合的方法。我错过了什么吗?有没有更好的方法来做这种事情?

【问题讨论】:

  • 对代码有几个小的更正:Response 类的response 字段应该是String 类型,有两个BData 类,所以第二个可能是CDataCData 中的 c 字段应为 boolean 类型。

标签: java json jackson gson deserialization


【解决方案1】:

这里的问题是您(或杰克逊)需要知道需要使用什么对象来转换请求。有两种方法:

1) 使用继承。由于杰克逊会为您处理所有事情,因此这种方法更加健壮,但是这种方法需要添加一个标记,杰克逊将使用该标记来选择应使用哪种对象类型进行转换。不确定您是否可以添加这些标记,但下面的代码应该让您了解如何完成。 它非常简单——你只需要添加@JsonTypeInfo 来配置哪个字段将用作标记,并添加@JsonSubTypes 来定义所有可用于转换响应的类。

class ResponseA extends BaseResponse {
    private String a;
}

class ResponseB extends BaseResponse {
    private String b;
}

class ResponseCD extends BaseResponse {
    private boolean c;
    private double d;
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = ResponseA.class, name = "a"),
        @JsonSubTypes.Type(value = ResponseB.class, name = "b"),
        @JsonSubTypes.Type(value = ResponseCD.class, name = "cd")
})
class BaseResponse {
    private String response;
    private double delay;
    private long time;
    private String message;
}

public class DynamicResponseInheritance {
    private static final String RESPONSE_A = "{\n" +
            "    \"type\": \"a\",\n" +
            "    \"response\": \"Success\",\n" +
            "    \"delay\": 0.241,\n" +
            "    \"time\": 125425234,\n" +
            "    \"message\": null,\n" +
            "    \"a\": \"Payloads\"\n" +
            "}";

    private static final String RESPONSE_B = "{\n" +
            "    \"type\": \"b\",\n" +
            "    \"response\": \"AuthFailure\",\n" +
            "    \"delay\": 0.112,\n" +
            "    \"time\": 1324515123,\n" +
            "    \"message\": \"Wrong password\",\n" +
            "    \"b\": 1234\n" +
            "}";

    private static final String RESPONSE_CD = "{\n" +
            "    \"type\": \"cd\",\n" +
            "    \"response\": \"Success\",\n" +
            "    \"delay\": 0.294,\n" +
            "    \"time\": 12461246123,\n" +
            "    \"message\": null,\n" +
            "    \"c\": true,\n" +
            "    \"d\": 245.1\n" +
            "}";

    public static void main(String []args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();

        BaseResponse responseA = objectMapper.readValue(RESPONSE_A, BaseResponse.class);
        BaseResponse responseB = objectMapper.readValue(RESPONSE_B, BaseResponse.class);
        BaseResponse responseCD = objectMapper.readValue(RESPONSE_CD, BaseResponse.class);

        System.out.println(responseA);
        System.out.println(responseB);
        System.out.println(responseCD);
    }
}

2) 实现自定义反序列化器。它也非常简单,但在这种情况下,如果您需要添加新类,则需要更新反序列化器。这种方法的好处是您不需要修改响应。

class ResponseDeserializer extends JsonDeserializer<BaseResponse> {
    @Override
    public BaseResponse deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        ObjectMapper mapper = (ObjectMapper) parser.getCodec();
        JsonNode root = parser.getCodec().readTree(parser);
        JsonNode a = root.get("a");
        if (a != null) {
            String content = root.toString();
            return mapper.readValue(content, ResponseA.class);
        }
        JsonNode b = root.get("b");
        if (b != null) {
            String content = root.toString();
            return mapper.readValue(content, ResponseB.class);
        }
        JsonNode c = root.get("c");
        if (c != null) {
            String content = root.toString();
            return mapper.readValue(content, ResponseCD.class);
        }
        return null;
    }
}

class ResponseA extends BaseResponse {
    private String a;
}

class ResponseB extends BaseResponse {
    private String b;
}

class ResponseCD extends BaseResponse {
    private boolean c;
    private double d;
}

class BaseResponse {
    private String response;
    private double delay;
    private long time;
    private String message;
}

public class DynamicResponseCustomDeserializer {
    private static final String RESPONSE_A = "{\n" +
            "    \"response\": \"Success\",\n" +
            "    \"delay\": 0.241,\n" +
            "    \"time\": 125425234,\n" +
            "    \"message\": null,\n" +
            "    \"a\": \"Payloads\"\n" +
            "}";

    private static final String RESPONSE_B = "{\n" +
            "    \"response\": \"AuthFailure\",\n" +
            "    \"delay\": 0.112,\n" +
            "    \"time\": 1324515123,\n" +
            "    \"message\": \"Wrong password\",\n" +
            "    \"b\": 1234\n" +
            "}";

    private static final String RESPONSE_CD = "{\n" +
            "    \"response\": \"Success\",\n" +
            "    \"delay\": 0.294,\n" +
            "    \"time\": 12461246123,\n" +
            "    \"message\": null,\n" +
            "    \"c\": true,\n" +
            "    \"d\": 245.1\n" +
            "}";

    public static void main(String []args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addDeserializer(BaseResponse.class, new ResponseDeserializer());
        objectMapper.registerModule(module);


        BaseResponse responseA = objectMapper.readValue(RESPONSE_A, BaseResponse.class);
        BaseResponse responseB = objectMapper.readValue(RESPONSE_B, BaseResponse.class);
        BaseResponse responseCD = objectMapper.readValue(RESPONSE_CD, BaseResponse.class);

        System.out.println(responseA);
        System.out.println(responseB);
        System.out.println(responseCD);
    }
}

【讨论】:

    【解决方案2】:

    这不是@JsonUnwrapped 的倒数。这 IS @JsonUnwrapped,因为 json 指定了 Response 内部字段内的变量。

    您想要告诉 Jackson 您的数据的通用构造是什么,您可以使用 Jackson 的内部类型系统来实现这一点:

    这是我使用的类定义:

    public class Response<T> {
        public String response;
        public Double delay;
        public Timestamp time;
        public String message;
        @JsonUnwrapped
        public T inner;
    }
    
    public class AData {
        public String a;
    }
    
    public class BData {
        public int b;
    }
    
    public class CData {
        public boolean c;
        public double d;
    }
    

    这就是你告诉杰克逊通用结构的方式:

        ObjectMapper mapper = new ObjectMapper();
        TypeFactory f = mapper.getTypeFactory();
        JavaType responseAData = f.constructParametrizedType(Response.class, Response.class, AData.class);
    

    然后可以以通常的方式进行反序列化:

        try (InputStream is = new FileInputStream("C://Temp/xx.json")) {
            Response<AData> r = (Response<AData>) mapper.readValue(is, responseAData);
            System.out.println(r.inner.a );
        } catch (Exception e) {
            e.printStackTrace();
        }
    

    给定指定的输入,我得到的输出为Payloads
    这里唯一需要注意的是,您的客户需要提前知道响应的类型

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-11-25
      • 1970-01-01
      • 2023-03-04
      • 2020-04-14
      • 2016-05-17
      • 2017-09-16
      • 2014-08-30
      • 1970-01-01
      相关资源
      最近更新 更多