Assume we have an endpoint that returns string array, like this (ContentType is application/json)
["a","a","a"]
and retrieve it's response as Flux<String>
webClient.get().uri().retrieve()
.bodyToFlux(String.class)
.subscribe(s -> println(s))
In my opinion, it should print "a" for three times, but only prints ["a","a","a"]
.
However, there is a workaround, we can replace String.class
with String[].class
, but the result returned in semantic isn't a Flux
anymore, but a Mono
.
(The version of Spring Webflux is 5.2.3-RELEASE)
Comment From: bringyou
Looks like this method is the cause BodyExtractors.readWithMessageReaders
private static <T, S extends Publisher<T>> S readWithMessageReaders(
ReactiveHttpInputMessage message, BodyExtractor.Context context, ResolvableType elementType,
Function<HttpMessageReader<T>, S> readerFunction,
Function<UnsupportedMediaTypeException, S> errorFunction,
Supplier<S> emptySupplier) {
if (VOID_TYPE.equals(elementType)) {
return emptySupplier.get();
}
MediaType contentType = Optional.ofNullable(message.getHeaders().getContentType())
.orElse(MediaType.APPLICATION_OCTET_STREAM);
return context.messageReaders().stream()
.filter(reader -> reader.canRead(elementType, contentType))
.findFirst()
.map(BodyExtractors::<T>cast)
.map(readerFunction)
.orElseGet(() -> {
List<MediaType> mediaTypes = context.messageReaders().stream()
.flatMap(reader -> reader.getReadableMediaTypes().stream())
.collect(Collectors.toList());
return errorFunction.apply(
new UnsupportedMediaTypeException(contentType, mediaTypes, elementType));
});
}
StringReader.canRead
only test the elementType. Maybe we should choose reader by mimeType first, then use canRead
or test both elementType and contentType?
Comment From: rstoyanchev
This is described here https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-codecs-jackson.
There is an ambiguity with String
and "application/json". It could be a serialized JSON text or a JSON array. We treat it as the former for several reasons. One because you can resolve the ambiguity with a target of Mono<List<String>>
whereas the other way around there would be no option. Two because it avoids other issues with SSE streams. Three because JSON Strings are less common and a slightly less longer syntax seems acceptable.
Comment From: borissedlak
Can you provide a full request on that? I tried to follow the reference on the spring web documentation but I ended up with
Mono<List<String>>response = webClient.put()
.uri(**)
.retrieve()
.bodyToFlux(String.class)
.collectList()
.block();
and the result is still a single element though my json is of form ["a","b","c"]
Comment From: rstoyanchev
If you want a single string, reduce the list by concatenating:
webClient.get().uri("/path")
.retrieve()
.bodyToFlux(String.class)
.collect(Collectors.joining())
**Comment From: jdeex**
>
>
> Can you provide a full request on that? I tried to follow the reference on the spring web documentation but I ended up with
>
> `Mono<List<String>>response = webClient.put() .uri(**) .retrieve() .bodyToFlux(String.class) .collectList() .block();`
>
> and the result is still a single element though my json is of form `["a","b","c"]`
This in fact returns one String with the complete array in it
>
>
> If you want a single string, reduce the list by concatenating:
>
> ```java
> webClient.get().uri("/path")
> .retrieve()
> .bodyToFlux(String.class)
> .collect(Collectors.joining())
> ```
We don't want a single String, we want the List of values
If you get a application/json reply like ["a","b","c"] and process it with .bodyToFlux(String.class) .collectList() .block() you should get a List<String> right?
**Comment From: bringyou**
The workaround I mentioned is something like
```java
webClient.get().uri().retrieve().bodyToFlux(String[].class); // now we get a flux<String[]> which only has one item
webClient.get().uri().retrieve().bodyToMono(String[].class); // or a mono<String[]>
then you can convert string[] to any type you like
Comment From: jdeex
Thx for the workaround, I did something similar... I starting to think that this only happens with the String class, other pojos work as expected...
Comment From: pablo53
Having read the maintainers' response, I still can't understand why is this ticket closed, and why the following:
["a","b","c"]
is interpreted by WebClient as a single string. Former RestTemplate interpreted it correctly as a collection. So, Flux should correctly parse it as a Flux<String>
with 3 string elements, and not one string representing JSON. WebClient would be right, if the content was like this:
"[\"a\",\"b\",\"c\"]"
.
Then I'd agree that Flux should return one string element ["a","b","c"]
.