Jackson @JsonView, @JsonFilter et Spring
Peut-on utiliser le Jackson @JsonView
et @JsonFilter
annotations pour modifier le JSON retourné par un contrôleur à ressort MVC, tout en utilisant MappingJacksonHttpMessageConverter
et le Printemps @ResponseBody
et @RequestBody
annotations?
public class Product
{
private Integer id;
private Set<ProductDescription> descriptions;
private BigDecimal price;
...
}
public class ProductDescription
{
private Integer id;
private Language language;
private String name;
private String summary;
private String lifeStory;
...
}
lorsque le client demande une collecte de Products
, j'aimerais revenir à une version minimale de chaque ProductDescription
, peut-être juste sa carte D'identité. Ensuite, lors d'un appel ultérieur, le client peut utiliser cet ID pour demander une instance complète de ProductDescription
avec toutes les propriétés.
Il serait idéal pour pouvoir spécifier ceci sur les méthodes du contrôleur MVC Spring, car la méthode invoquée définit le contexte dans lequel le client demandait les données.
5 réponses
Ce problème est résolu!
Suivez
Ajout du support pour Jackson sérialisation vues
Spring MVC supporte maintenant les vues de sérialisation de Jackon pour le rendu différents sous-ensembles d'un même POJO de différents Controllers méthodes (p. ex. page détaillée vs vue sommaire). Numéro: SPR-7156
C'est le SPR-7156.
Statut: Résolu
Description
L'annotation Jsonview de Jackson permet au développeur de contrôler quels aspects d'une méthode sont sérialisés. Avec l'implémentation actuelle, le rédacteur de Jackson view doit être utilisé mais le type de contenu n'est pas disponible. Il serait préférable que dans le cadre de L'annotation RequestBody, un JSONView puisse être spécifié.
Disponible sur printemps ver >= 4.1
UPDATE
Suivre ce lien. Explique avec un exemple l'annotation @JsonView.
finalement, nous voulons utiliser une notation similaire à ce que StaxMan a montré pour JAX-RS. Malheureusement, le printemps ne supporte pas cela, donc nous devons le faire nous-mêmes.
C'est ma solution, c'est pas très joli, mais il fait le travail.
@JsonView(ViewId.class)
@RequestMapping(value="get", method=RequestMethod.GET) // Spring controller annotation
public Pojo getPojo(@RequestValue Long id)
{
return new Pojo(id);
}
public class JsonViewAwareJsonView extends MappingJacksonJsonView {
private ObjectMapper objectMapper = new ObjectMapper();
private boolean prefixJson = false;
private JsonEncoding encoding = JsonEncoding.UTF8;
@Override
public void setPrefixJson(boolean prefixJson) {
super.setPrefixJson(prefixJson);
this.prefixJson = prefixJson;
}
@Override
public void setEncoding(JsonEncoding encoding) {
super.setEncoding(encoding);
this.encoding = encoding;
}
@Override
public void setObjectMapper(ObjectMapper objectMapper) {
super.setObjectMapper(objectMapper);
this.objectMapper = objectMapper;
}
@Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
Class<?> jsonView = null;
if(model.containsKey("json.JsonView")){
Class<?>[] allJsonViews = (Class<?>[]) model.remove("json.JsonView");
if(allJsonViews.length == 1)
jsonView = allJsonViews[0];
}
Object value = filterModel(model);
JsonGenerator generator =
this.objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(), this.encoding);
if (this.prefixJson) {
generator.writeRaw("{} && ");
}
if(jsonView != null){
SerializationConfig config = this.objectMapper.getSerializationConfig();
config = config.withView(jsonView);
this.objectMapper.writeValue(generator, value, config);
}
else
this.objectMapper.writeValue(generator, value);
}
}
public class JsonViewInterceptor extends HandlerInterceptorAdapter
{
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
JsonView jsonViewAnnotation = handlerMethod.getMethodAnnotation(JsonView.class);
if(jsonViewAnnotation != null)
modelAndView.addObject("json.JsonView", jsonViewAnnotation.value());
}
}
Au printemps-servlet.xml
<bean name="ViewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
</map>
</property>
<property name="defaultContentType" value="application/json" />
<property name="defaultViews">
<list>
<bean class="com.mycompany.myproject.JsonViewAwareJsonView">
</bean>
</list>
</property>
</bean>
et
<mvc:interceptors>
<bean class="com.mycompany.myproject.JsonViewInterceptor" />
</mvc:interceptors>
je ne sais pas comment les choses fonctionnent avec le Printemps (désolé!), mais Jackson 1.9 peut utiliser @JsonView annotation des méthodes JAX-RS, donc vous pouvez faire:
@JsonView(ViewId.class)
@GET // and other JAX-RS annotations
public Pojo resourceMethod()
{
return new Pojo();
}
et Jackson utilisera la vue identifiée par ViewId.la classe de la vue active. Peut-être que le Printemps a (ou aura) des capacités similaires? Avec JAX-RS, cela est géré par le JacksonJaxrsProvider standard, pour ce que cela vaut.
à la recherche de la même réponse, j'ai eu l'idée d'envelopper L'objet responsable d'une vue.
morceau de la classe de contrôleur:
@RequestMapping(value="/{id}", headers="Accept=application/json", method= RequestMethod.GET)
public @ResponseBody ResponseBodyWrapper getCompany(HttpServletResponse response, @PathVariable Long id){
ResponseBodyWrapper responseBody = new ResponseBodyWrapper(companyService.get(id),Views.Owner.class);
return responseBody;
}
public class ResponseBodyWrapper {
private Object object;
private Class<?> view;
public ResponseBodyWrapper(Object object, Class<?> view) {
this.object = object;
this.view = view;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
@JsonIgnore
public Class<?> getView() {
return view;
}
@JsonIgnore
public void setView(Class<?> view) {
this.view = view;
}
}
Puis-je remplacer writeInternal
forme de la méthode MappingJackson2HttpMessageConverter
pour vérifier si object to serialize est instanceof wrapper, si c'est le cas, je serialize object avec la vue requise.
public class CustomMappingJackson2 extends MappingJackson2HttpMessageConverter {
private ObjectMapper objectMapper = new ObjectMapper();
private boolean prefixJson;
@Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
JsonGenerator jsonGenerator =
this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
try {
if (this.prefixJson) {
jsonGenerator.writeRaw("{} && ");
}
if(object instanceof ResponseBodyWrapper){
ResponseBodyWrapper responseBody = (ResponseBodyWrapper) object;
this.objectMapper.writerWithView(responseBody.getView()).writeValue(jsonGenerator, responseBody.getObject());
}else{
this.objectMapper.writeValue(jsonGenerator, object);
}
}
catch (IOException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
super.setObjectMapper(objectMapper);
}
public ObjectMapper getObjectMapper() {
return this.objectMapper;
}
public void setPrefixJson(boolean prefixJson) {
this.prefixJson = prefixJson;
super.setPrefixJson(prefixJson);
}
}