0

I'm receiving JSON from REST API looks like:

{
    "items": [
        {
            "id": 60659,
            "name": "Display",
            "active": true,
            "account_id": 235
        },
        {
            "id": 36397,
            "name": " Mail Display",
            "active": true,
            "account_id": 107
        }
    ]
}

I'm using this method to parse it:

Mono<List<Item>> getItems(String token) {
        return webCLient
                .get()
                .headers(httpHeaders -> httpHeaders.setBearerAuth(token))
                .retrieve()
                .bodyToMono(ItemResponse.class)
                .map(ItemResponse::getResponse)
                .retryBackoff(RetrySettings.RETRIES, RetrySettings.FIRST_BACKOFF, RetrySettings.MAX_BACKOFF)
                .doOnError(e -> log.error("error: " + e.getCause().toString()))

Response:

public class ItemResponse {

    @JsonProperty("items")
    private List<Item> response;
}

But sometimes 3rd party API returns different response without top level items property and looks like:

[
        {
            "id": 60659,
            "name": "Display",
            "active": true,
            "account_id": 235
        },
        {
            "id": 36397,
            "name": " Mail Display",
            "active": true,
            "account_id": 107
        }
]

At this point my app is crashing with JSON decoding error. I used for this case:

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

But I can't always refactoring this part of code just to handle their json. How to do it in dynamical way with Spring WebFlux? Like try -> parse#1 -> catch -> parse#2. So i need to parse json in way#1 and if error occurs app should try to parse it with way#2.

funnelCONN
  • 149
  • 1
  • 11
  • 3
    the second response is not valid json, and should be fixed from the source since it is providing broken responses, but if the parsing fails, you could return a `Mono#error` and then use the `onErrorResume` and parse it in another way. – Toerktumlare Apr 06 '21 at 11:09
  • I fixed json, that was my bad. Can you, please, provide example? I work with Spring WebFlux few hours. – funnelCONN Apr 06 '21 at 12:19
  • no i can't provide a working example, if you have only worked with it for a couple of hours, i suggest you read up more on how to work with it before you ask a question on stack overflow, and at least try and then post what you have tried and how it fails. Its part of the learning process as a developer. – Toerktumlare Apr 06 '21 at 12:21

1 Answers1

1

You can get the response as a string .bodyToMono(String.class) and do whatever you want, with multiple try catches... but I think your best bet is to create a custom Deserializer and use it with your WebClient via ExchangeStrategies like described here: How to customize SpringWebFlux WebClient JSON deserialization? .

class MyResponse {

    List<Object> data;

    MyResponse(List<Object> data) {
        this.data = data;
    }
}

class MyResponseDeserializer extends JsonDeserializer<MyResponse> {

    @Override
    public MyResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser);

        List<Object> data = new ArrayList<>();

        if (treeNode.isArray()) {
            // parse it as array
        } else {
            // parse it as object and put inside list
        }

        MyResponse myResponse = new MyResponse(data);

        return myResponse;
    }
}

And then

WebClient getWebClient() {
    ObjectMapper objectMapper = new ObjectMapper();
    SimpleModule simpleModule = new SimpleModule();
    simpleModule.addDeserializer(MyResponse.class, new MyResponseDeserializer());

    objectMapper.registerModule(simpleModule);

    ExchangeStrategies strategies = ExchangeStrategies
            .builder()
            .codecs(clientDefaultCodecsConfigurer -> {
                clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper, MediaType.APPLICATION_JSON));
                clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper, MediaType.APPLICATION_JSON));
            }).build();

    return WebClient.builder().exchangeStrategies(strategies).build();
}

Mono<List<Item>> getItems(String token) {
        return getWebClient()
                .get()
                .headers(httpHeaders -> httpHeaders.setBearerAuth(token))
                .retrieve()
                .bodyToMono(MyResponse.class)
                .map(MyResponse::data)
                .retryBackoff(RetrySettings.RETRIES, RetrySettings.FIRST_BACKOFF, RetrySettings.MAX_BACKOFF)
                .doOnError(e -> log.error("error: " + e.getCause().toString()))
}

The rest is the same as in your example just change the class name and add appropriate fields. And of course this is just a fast written demo and everything hardcoded and within a one method, better to have them injected

Serg
  • 225
  • 2
  • 10
  • Can you please guide me here: https://stackoverflow.com/questions/70907506/how-to-get-the-flux-response-into-map? – PAA Jan 30 '22 at 04:05