javajacksonjackson2

Get jackson JsonNode from jakarta.StreamingOutput entity


I have a rest resource method returning a jakarta.ws.rs.core.StreamingOutput entity. This class provides a convinient progressive streamingoutput.write(OutputStream os) output to a http response.

I need to reuse a method internally without the actual http requests. I made this test code and it works fine.

Question: Is it somehow possible to provide StreamingOutput directly to JsonNode jsonRoot = objectMapper.readTree() function? Or other trick without first creating a temporary String sData | os.toByteArray() bytes variable?

// rest resource function
public Response getList(long custId, String keyword) {
  List<MyEntity> list = JPADB.getMyEntities(custId, keyword);
  StreamingOutput stream = createStream(list);
  CacheControl cc = new CacheControl();
  cc.setNoCache(true);
  return Response.ok().type(TYPE_JSON).cacheControl(cc).entity(stream).build();
}

// somewhere else reuse an existing rest method
MyEntityRest mrest = new MyEntityRest();
Object retvalObj = mrest.getList(1, "test").getEntity();
ByteArrayOutputStream os = new ByteArrayOutputStream(4*1024);           
((StreamingOutput)retvalObj).write(os); 
String sData = os.toString("UTF-8");
JsonNode jsonRoot = om.readTree(sData);
JsonNode jsonItem = jsonRoot.path("items").get(0);
System.out.println(String.format("%s %s"
   , jsonItem.path("code").asText("")
   , jsonItem.path("names").get(0).path("value").asText("")
));

Solution

  • As StreamingOutput only supports an OutputStream by its write method and does not implement any other interfaces, you have to transform it anyhow to one of the ObjectMappers supported input types (String, Reader, InputStream etc.).

    And from an architectural perspective it sounds strange, to call a method from a REST controller internally, extracting the data from the Response object.

    If I get you right, your requirement is to reuse the List<MyEntity> list = JPADB.getMyEntities(custId, keyword); part as JSON object, right? So why not providing that piece of logic together with the JSON serialization inside your createStream method as a single method you can call inisde the public Response getList(long custId, String keyword), as well as reusing it in subsequent code you mentioned. By this you get the JsonNode for free, without the overhead of serialize/deserialize via StreamingOutput object.

    Something like this:

    // rest resource function
      public Response getList(long custId, String keyword) {
        StreamingOutput stream = createStream(getData(long custId, String keyword));
        CacheControl cc = new CacheControl();
        cc.setNoCache(true);
        return Response.ok().type(TYPE_JSON).cacheControl(cc).entity(stream).build();
      }
    
    
      public JsonNode getData(long custId, String keyword) throws JsonProcessingException {
        List<MyEntity> list = JPADB.getMyEntities(custId, keyword);
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readTree(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(list));
      }
    
      // somewhere else reuse an existing rest method
      JsonNode jsonRoot = getData(1, "test")
      JsonNode jsonItem = jsonRoot.path("items").get(0);
      System.out.println(String.format("%s %s"
         , jsonItem.path("code").asText("")
         , jsonItem.path("names").get(0).path("value").asText("")
      ));