Reputation: 6595
I'm building a Java REST application with JAX-RS.
In the context of the application, I have the following bean classes:
public class ContentView {
// Field definitions, getters/setters
}
public class ArticleDetailView extends ContentView {
// more definitions, getters/setters
}
public class UpsellDetailView extends ContentView {
// more definitions, getters/setters
}
UpsellDetailView and ArticleDetailView have more attributes and they both extend ContentView. I've got all the mappers correctly wired up, so all the instances of each respective class gets all its properties set correctly. I don't use any extra mappers - object properties get deserialized based on the public getters in the respective bean classes.
SeriesDetailView is similar:
public class SeriesDetailView extends SeriesLiteView {
private List<Content> content;
@JsonIgnore //We don't want content appear on the Series detail page
public List<Content> getContent() {
return content;
}
public void setContent(List<Content> content) {
this.content = content;
}
}
Now, I have a REST resource class as follows:
@Produces({MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_FORM_URLENCODED})
@Path("/")
public class ApiSiteResource extends AbstractContentResource {
...
@GET
@Path("/series/{uuid}/contents")
public List<ContentView> getSessionsForSeries(@Context HttpServletRequest request, @Context HttpServletResponse response, @BeanParam ApiParams params) {
final SeriesDetailView series = seriesService.findByUuid(params.getUuid());
if (series == null) {
response.setStatus(SC_NOT_FOUND);
return null;
}
List<ContentView> contentViewList = contentMapper.map(series.getContent());
List<ContentView> results = contentViewList.stream().map(e -> mapContent(e, e.getUuid())).collect(Collectors.toList());
return results;
}
@GET
@Path("/contents/{uuid}")
public ContentView uniqueContent(@Context HttpServletRequest request, @Context HttpServletResponse response, @BeanParam ApiParams params) {
ContentView beanView = contentService.findByUuid(params.getUuid());
if (beanView == null) {
response.setStatus(SC_NOT_FOUND);
return null;
}
beanView = mapContent(beanView, params.getUuid());
return beanView;
}
private ContentView mapContent(ContentView beanView, String uuid){
if (ArticleType.TypeGroup.upsell.toString().equals(beanView.getType())) {
UpSell upsell = ((UpSell)contentService.findByUuidRaw(uuid));
beanView = (UpsellDetailView)upsellMapper.map(upsell);
rewriteBodyHtml(beanView, upsell.getBody());
}
else if (ArticleType.TypeGroup.article.toString().equals(beanView.getType()) ||
ArticleType.TypeGroup.audio.toString().equals(beanView.getType()) ||
ArticleType.TypeGroup.video.toString().equals(beanView.getType())) {
Article article = ((Article)contentService.findByUuidRaw(uuid));
beanView = (ArticleDetailView)articleMapper.map(article);
rewriteBodyHtml(beanView, article.getBody());
}
return beanView;
}
}
Now, here's the problem:
when I invoke /contents/{uuid} in the browser, I get fully deserialized correct content type json (Article or UpsellDetailView), but
when I invoke /series/{uuid}/contents, I get a list of short-form json elements corresponding to the ContentView structure.
I do confirm that the List results in getSessionsForSeries() is a list of correct types (Article or UpsellDetailView).
But I can't find for the life of mine why these don't get properly deserialized when in the List. What am I missing?
Upvotes: 2
Views: 588
Reputation: 6595
Here's the answer:
For some reason, List's serializer was messed up and was when serializing was defaulting to the declared content class (ContentView).
If anyone has an idea why I'd appreciate enlightment.
So, I had to use brute force and provide my own serialization (do note the method's response type is changed to String):
@GET
@Path("/series/{uuid}/contents")
public String getSessionsForSeries(@Context HttpServletRequest request, @Context HttpServletResponse response, @BeanParam ApiParams params) {
final SeriesDetailView series = seriesService.findByUuid(params.getUuid());
if (series == null) {
response.setStatus(SC_NOT_FOUND);
return null;
}
List<ContentView> contentViewList = contentMapper.map(series.getContent());
List<ContentView> results = contentViewList.stream()
.map(e -> mapContent(e, e.getUuid())).collect(Collectors.toList());
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.writeValueAsString(results);
} catch (IOException e) {
response.setStatus(SC_INTERNAL_SERVER_ERROR);
return null;
}
}
Thanks to the tutorial here: http://www.davismol.net/2015/03/05/jackson-json-deserialize-a-list-of-objects-of-subclasses-of-an-abstract-class/
Upvotes: 1