Stream JSON output in Spring MVC

2020-02-08 05:00发布

问题:

My application is built using Spring boot(1.3.3.RELEASE) with spring mvc, spring data jpa hibernate. MySql is the database and Jackson is the Json serializer. On java 8.

I want to return a huge data set in my controller method. Instead of retrieving all the data and then passing into the jackson serializer, i want to return a stream of objects like below:

@RequestMapping(value = "/candidates/all", method = RequestMethod.GET)
public Stream<Candidate> getAllCandidates(){
    try { 
        return candidateDao.findAllByCustomQueryAndStream();
    } catch(Exception e){
        LOG.error("Exception in getCandidates",e);
    }
    return null;
}

my DAO is like below:

@Query("select c from Candidate c")
public Stream<Candidate> findAllByCustomQueryAndStream();

However, Jackson is serializing the stream object instead of the contents of the stream. The actual output below:

{"parallel" : false}

How can I instruct Jackson to serialize the content and not the stream object?

回答1:

Thanks to this I was able to solve the issue.

I had provide a custom httpMessageConverter that understands how to handle streams. Like so:

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
 MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
 ObjectMapper objectMapper =jsonConverter.getObjectMapper();
 SimpleModule module = new SimpleModule("Stream");
 module.addSerializer(Stream.class, new JsonSerializer<Stream>() {


    @Override
    public void serialize(Stream value, JsonGenerator gen, SerializerProvider serializers)
            throws IOException, JsonProcessingException {
        serializers.findValueSerializer(Iterator.class, null)
        .serialize(value.iterator(), gen, serializers);

    }
});

 objectMapper.registerModule(module);
 jsonConverter.setObjectMapper(objectMapper);
 return jsonConverter;
}


回答2:

There is a proposed solution at https://github.com/FasterXML/jackson-modules-java8/issues/3 which may be the better way to go.

I'll not paste the code in here, as it may get updated in that issue.

So far, I've not found any issues with that proposed code which I've added in with other modules such as the Jdk8Module for Optional as

jacksonObjectMapper.registerModule(new StreamModule());


回答3:

I found that this way of adding support for streams broke the nice output of LocalDate / LocalDateTime, ended up doing it like this:

@Bean
public Module customModule() {
    SimpleModule module = new SimpleModule("Stream");
    module.addSerializer(Stream.class, new JsonSerializer<Stream>() {

        @Override
        public void serialize(Stream value, JsonGenerator gen, SerializerProvider serializers)
                throws IOException, JsonProcessingException {
            serializers.findValueSerializer(Iterator.class, null)
                    .serialize(value.iterator(), gen, serializers);

        }
    });
    return module;
}


回答4:

Convert it like this: Iterable<X> iterable = stream::iterator;. Then return Iterablе, rather than Stream.

@RequestMapping(value = "/candidates/all", method = RequestMethod.GET)
public Iterable<Candidate> getAllCandidates(){
    ...

        return candidateDao.findAllByCustomQueryAndStream()::iterator;