Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document WebFlux rendering of String and Flux<String> with JSON [SPR-16260] #20807

Closed
spring-issuemaster opened this issue Dec 4, 2017 · 5 comments

Comments

Projects
None yet
2 participants
@spring-issuemaster
Copy link
Collaborator

commented Dec 4, 2017

Victor Herraiz Posada opened SPR-16260 and commented

The following method produces an invalid JSON output:

 @GetMapping(value = "/list", produces = APPLICATION_JSON_UTF8_VALUE)
 public Flux<String> getStringList() {
     return Flux.fromIterable(Arrays.asList("Hola", "Mundo"));
}

In my opinion, this should be:

["Hola", "Mundo"]

Instead of:

HolaMundo

Affects: 5.0.2

Referenced from: commits 7bf9b76, 542de82

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 4, 2017

Victor Herraiz Posada commented

Sorry, I pressed "enter" before pasting the description. :P

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 4, 2017

Victor Herraiz Posada commented

I did the following tests using kotlin, see the outputs in the code before the method. kotlin.CharSequence writes a JSON Array.

@RestController
class PoCController {

    //Outputs 102030
    @GetMapping("/list1")
    fun getList1(): Flux<String> = arrayListOf("10", "20", "30").toFlux()
    
    //Outputs [10,20,30]
    @GetMapping("/list2")
    fun getList2(): Flux<Int> = arrayListOf(10, 20, 30).toFlux()
    
    //Outputs ["10", "20", "30"]
    @GetMapping("/list3")
    fun getList3(): Flux<Any> = arrayListOf("10", "20", "30").toFlux()

    //Error... No representation available
    @GetMapping("/list4")
    fun getList4(): Flux<Comparable<String>> = arrayListOf("10", "20", "30").toFlux()

    //Outputs ["10","20","30"]
    @GetMapping("/list5")
    fun getList5(): Flux<CharSequence> = arrayListOf("10", "20", "30").toFlux()
}
@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 4, 2017

Rossen Stoyanchev commented

This is expected behavior. By default byte arrays, byte buffers, and String are treated as low level content (serialized output) and is rendered as is. In fact the Flux<String> is streamed with each string written and flushed immediately.

The Jackson encoder explicitly backs out for element type String. I realize that String and an array of String's can be rendered as JSON but there are two ways to treat String content and this is what we've chosen by default.

One reason for this default is because it's easy to be more explicit by returning a list via flux.collectToList, or the list if that's what you have to begin with. If you really have a stream then application/json is not the right media type to use. It would have to be application/stream+json.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 4, 2017

Victor Herraiz Posada commented

In my humble opinion, this behaviour is rather misleading. Flux<CharSequence>, Flux<Any>, Flux<Int> become a JSON array but Flux<String> don't. I think that String with or without quotes is not a valid response for "application/json" content type, only a collection of name/value pairs and an ordered list of values are valid.

Anyway, I understood the reasons for that decision. Do you have that behaviour documented?

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Dec 4, 2017

Rossen Stoyanchev commented

Yes a String without quotes is not valid JSON but the point is that you are in charge of setting the (serialized JSON) content. The Jackson encoder does not even get involved.

Indeed the behavior is surprising to discover coming from this angle. However it is equally surprising if you're trying to write serialized JSON to find out it gets treated as JSON Strings. One reason we chose this default despite the confusion is because rendering String's as JSON less common realistically and even if you do need it, it's easy to be more explicit by returning a List.

The rendering of String is actually more aligned with bytes (i.e. content) than with integers (data). This is also consistent with how Spring MVC has always worked if a String is returned for application/json.

I'm turning this into a documentation task. We are still wrapping up documentation for WebFlux, so this is a good one to reflect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.