Search code examples
spring-bootspring-webfluxproject-reactorreactor

Webflux subscribe to nested Publishers and serialize them to JSON


I have a UserDto with related items taken from repository which is Project-Reactor based, thus returns Flux/Mono publishers.

My idea was to add fields/getters to DTO, which themselves are publishers and lazily evaluate them (subscribe) on demand, but there is a problem:

Controller returns Flux of DTOs, all is fine, except spring doesn't serialize inner Publishers

What I'm trying to achieve in short:

@Repository
class RelatedItemsRepo {
    static Flux<Integer> findAll() {
        // simulates Flux of User related data (e.g. Orders or Articles)
        return Flux.just(1, 2, 3);
    }
}

@Component
class UserDto {

    // Trying to get related items as field
    Flux<Integer> relatedItemsAsField = RelatedItemsRepo.findAll();

    // And as a getter
    @JsonProperty("related_items_as_method")  
    Flux<Integer> relatedItemsAsMethod() {
        return RelatedItemsRepo.findAll();
    }


    // Here was suggestion to collect flux to list and return Mono
    // but unfortunately it doesn't make the trick 
    @JsonProperty("related_items_collected_to_list")
    Mono<List<Integer>> relatedItemsAsList() {
         return RelatedItemsRepo.findAll().collectList();
    }

    // .. another user data
}

@RestController
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public class MyController {

    @GetMapping
    Flux<UserDto> dtoFlux() {
        return Flux.just(new UserDto(), new UserDto(), new UserDto());
    }
}

And this is the response I get:

HTTP/1.1 200 OK
Content-Type: application/json
transfer-encoding: chunked

[
    {
        "related_items_as_method": {
            "prefetch": -1,
            "scanAvailable": true
        },
        "related_items_collected_to_list": {
            "scanAvailable": true
        }
    },
    {
        "related_items_as_method": {
            "prefetch": -1,
            "scanAvailable": true
        },
        "related_items_collected_to_list": {
            "scanAvailable": true
        }
    },
    {
        "related_items_as_method": {
            "prefetch": -1,
            "scanAvailable": true
        },
        "related_items_collected_to_list": {
            "scanAvailable": true
        }
    }
]

It seems like Jackson doesn't serialize Flux properly and just calls .toString() on it (or something similar).

My question is: Is there existing Jackson serializers for Reactor Publishers or should I implement my own, or maybe am I doing something conceptually wrong.

So in short: how can I push Spring to evaluate those fields (subscribe to them)


Solution

  • If I understand correctly, what you try to achieve is to create an API that needs to respond with the following response:

    HTTP 200
    
    [
      {
        "relatedItemsAsField": [1,2,3]
      },
      {
        "relatedItemsAsField": [1,2,3]
      },
      {
        "relatedItemsAsField": [1,2,3]
      }
    ]
    

    I would collect all the elements emitted by the Flux generated by RelatedItemsRepo#findAll by using Flux#collectList, then map this to set the UserDto object as required.

    Here is a gist.