使用Jackson和WebClient将JSON数组反序列化为对象

40

使用Spring进行JSON数组反序列化时,我遇到了问题。 我从服务中得到了以下JSON响应:

[
    {
        "symbol": "XRPETH",
        "orderId": 12122,
        "clientOrderId": "xxx",
        "price": "0.00000000",
        "origQty": "25.00000000",
        "executedQty": "25.00000000",
        "status": "FILLED",
        "timeInForce": "GTC",
        "type": "MARKET",
        "side": "BUY",
        "stopPrice": "0.00000000",
        "icebergQty": "0.00000000",
        "time": 1514558190255,
        "isWorking": true
    },
    {
        "symbol": "XRPETH",
        "orderId": 1212,
        "clientOrderId": "xxx",
        "price": "0.00280000",
        "origQty": "24.00000000",
        "executedQty": "24.00000000",
        "status": "FILLED",
        "timeInForce": "GTC",
        "type": "LIMIT",
        "side": "SELL",
        "stopPrice": "0.00000000",
        "icebergQty": "0.00000000",
        "time": 1514640491287,
        "isWorking": true
    },
    ....
]

我使用Spring WebFlux中的新WebClient获取了这个json,这是代码:

@Override
    public Mono<AccountOrderList> getAccountOrders(String symbol) {
        return binanceServerTimeApi.getServerTime().flatMap(serverTime -> {
            String apiEndpoint = "/api/v3/allOrders?";
            String queryParams = "symbol=" +symbol.toUpperCase() + "&timestamp=" + serverTime.getServerTime();
            String signature = HmacSHA256Signer.sign(queryParams, secret);
            String payload = apiEndpoint + queryParams + "&signature="+signature;
            log.info("final endpoint:"+ payload);
            return this.webClient
                    .get()
                    .uri(payload)
                    .accept(MediaType.APPLICATION_JSON)
                    .retrieve()
                    .bodyToMono(AccountOrderList.class)
                    .log();
        });
    }

账户订单列表

public class AccountOrderList {

    private List<AccountOrder> accountOrders;

    public AccountOrderList() {
    }

    public AccountOrderList(List<AccountOrder> accountOrders) {
        this.accountOrders = accountOrders;
    }

    public List<AccountOrder> getAccountOrders() {
        return accountOrders;
    }

    public void setAccountOrders(List<AccountOrder> accountOrders) {
        this.accountOrders = accountOrders;
    }
}

AccountOrder是一个简单的pojo,用于映射字段。

实际上,当我执行get操作时,它会显示:

org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize instance of `io.justin.demoreactive.domain.AccountOrder` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `io.justin.demoreactive.domain.AccountOrder` out of START_ARRAY token
 at [Source: UNKNOWN; line: -1, column: -1]
我该如何使用新的webflux模块正确反序列化JSON?我做错了什么吗? 更新于05/02/2018 两个答案都是正确的。它们完美地回答了我的问题,但最终我决定采用略有不同的方法:
@Override
    public Mono<List<AccountOrder>> getAccountOrders(String symbol) {
        return binanceServerTimeApi.getServerTime().flatMap(serverTime -> {
            String apiEndpoint = "/api/v3/allOrders?";
            String queryParams = "symbol=" +symbol.toUpperCase() + "&timestamp=" + serverTime.getServerTime();
            String signature = HmacSHA256Signer.sign(queryParams, secret);
            String payload = apiEndpoint + queryParams + "&signature="+signature;
            log.info("final endpoint:"+ payload);
            return this.webClient
                    .get()
                    .uri(payload)
                    .accept(MediaType.APPLICATION_JSON)
                    .retrieve()
                    .bodyToFlux(AccountOrder.class)
                    .collectList()
                    .log();
        });
    }

另一种选择是直接返回Flux,这样就不需要将其转换为列表。(因为Flux是n个元素的集合)。


你是自己创建这个响应还是从第三方获取的? - Ravi
这是来自第三方的响应。我无法更改响应 @Ravi - Justin
3个回答

60

关于您对问题的更新答案,使用bodyToFlux是不必要的低效操作,语义上也不太合适,因为您并不需要一系列的订单流。您想要的只是能够将响应解析为列表。

bodyToMono(List<AccountOrder>.class)由于类型擦除而无法正常工作。您需要能够在运行时保留类型,Spring提供了ParameterizedTypeReference来实现此功能:

bodyToMono(new ParameterizedTypeReference<List<AccountOrder>>() {})

谢谢!我曾经处于同样的困境,即端点的响应是一个项目对象数组。然而,我想进一步提出这个问题,有没有一种好的方法将此响应映射到响应包装器类?例如,像下面这样的东西: { "itemCount" : 10, "itemsList" : [ { item1 }, { item2 }, ... ] } - Tarnished-Coder
1
可以实现,但需要使用自定义反序列化器。个人不太喜欢这样做,我更喜欢通过在数据对象和领域对象之间创建一个映射函数来明确进行转换。 - Pin
明白了,谢谢!我创建了不同的Response类,并使用map步骤和Builder()来构建新的响应! - Tarnished-Coder
谢谢,这很有帮助。 - Dhrumil Shah

27
为了将响应与 AccountOrderList 类匹配,JSON 必须像这样:
{
  "accountOrders": [
    {
        "symbol": "XRPETH",
        "orderId": 12122,
        "clientOrderId": "xxx",
        "price": "0.00000000",
        "origQty": "25.00000000",
        "executedQty": "25.00000000",
        "status": "FILLED",
        "timeInForce": "GTC",
        "type": "MARKET",
        "side": "BUY",
        "stopPrice": "0.00000000",
        "icebergQty": "0.00000000",
        "time": 1514558190255,
        "isWorking": true
    },
    {
        "symbol": "XRPETH",
        "orderId": 1212,
        "clientOrderId": "xxx",
        "price": "0.00280000",
        "origQty": "24.00000000",
        "executedQty": "24.00000000",
        "status": "FILLED",
        "timeInForce": "GTC",
        "type": "LIMIT",
        "side": "SELL",
        "stopPrice": "0.00000000",
        "icebergQty": "0.00000000",
        "time": 1514640491287,
        "isWorking": true
    },
    ....
]
}

这是错误信息的内容 "out of START_ARRAY token"。

如果您无法更改响应,则可以更改您的代码以接受类似以下的数组:

this.webClient.get().uri(payload).accept(MediaType.APPLICATION_JSON)
                        .retrieve().bodyToMono(AccountOrder[].class).log();

你可以将这个数组转换为List,然后返回。


13

你的响应只是简单的 List<AccountOrder>。但是,你的 POJO 已经封装了 List<AccountOrder>。因此,根据你的 POJO,你的 JSON 应该是:

{"data": List<AccountOrder>}
{
  "accountOrders": [
    {

但是,你的JSON有误。

[
    {
       "symbol": "XRPETH",
       "orderId": 12122,
        ....

所以,存在不匹配问题并导致反序列化失败。你需要进行更改:

So, 存在不匹配问题并导致反序列化失败。你需要进行更改:

bodyToMono(AccountOrder[].class)

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