Reputation: 4781
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
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 ObjectMapper
s, 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" } ] }
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