Midiparse
Midiparse

Reputation: 4781

Force JAX-RS to serialize my class to a JSON object

I have class which is a decorator around an inner list. I would like to use this class as a DTO in my JAX-RS service. Its code as follows:

@XmlRootElement(name = "movies")
public class MoviesResponse implements List<Movie> {

    @XmlElement(name = "movie")
    protected List<Movie> movies;

    /* tons of delegate methods */

}

I need to support both application/xml, and application/json. The format is fixed, it has to be like

<movies>
 <movie>
  <foo />
 </movie>
 <movie>
  <foo /> 
 </movie>
</movies>

... in XML, and

{
 "movie": [
 {},{}
 ]
}

...in JSON. XML works perfectly fine, but JSON looks like this:

[{},{}]

As you may suspect, if I don't implement the List interface, it generates the the format I need. So I guess the serializer is being smart and treating it as List thus serializing it into an array. But I need to serialize it into an object. How can I do this, implementing the List interface?

Upvotes: 2

Views: 3680

Answers (1)

Paul Samsotha
Paul Samsotha

Reputation: 209132

Assuming Jackson is your serializer, you could configure the ObjectMapper to WRAP_ROOT_VALUE. You would do that in the ContextResolver. So that the same configuration is not used for all types, you can use two different configured ObjectMappers, one for the list class, and one for the rest. For example

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {

    final ObjectMapper listMapper = new ObjectMapper();
    final ObjectMapper defaultMapper = new ObjectMapper();

    public ObjectMapperContextResolver() {
        listMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
        listMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);

        listMapper.registerModule(new JaxbAnnotationModule());
        defaultMapper.registerModule(new JaxbAnnotationModule());
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        if (type == MovieList.class) {
            return listMapper;
        }
        return defaultMapper;
    }  
}

The MessageBodyWriter used for marshalling will call the getContext method, passing in the class it's trying to marshal. Based on the the result, that is the ObjectMapper that will be used. What WRAP_ROOT_VALUE does, is wrap the root value in a object, with the name being the value in @JsonRootName or @XmlRootElement (given JAXB annotation support is enabled- see here)

Test:

@Path("/movies")
public class MovieResource {

    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response getMovieList() {
        MovieList list = new MovieList();
        list.add(new Movie("I Origins"));
        list.add(new Movie("Imitation Game"));
        return Response.ok(list).build();
    }
}

C:\>curl -v -H "Accept:application/json" http://localhost:8080/api/movies
Result:
{ "movies" : [ { "name" : "I Origins" }, { "name" : "Imitation Game" } ] }

UPDATE

So I noticed you have the list as being protected. Maybe you might later want to extend the MovieList class. In which case, this

if (type == MovieList.class) {
    return listMapper;
}

would bot be viable. You would instead need to check is the type isAssignableFrom

if (MovieList.class.isAssignableFrom(type)) {
    return listMapper;
}

Upvotes: 1

Related Questions