Подтвердить что ты не робот

Джексон @JsonView, @JsonFilter и Spring

Можно ли использовать аннотации Jackson @JsonView и @JsonFilter для изменения JSON, возвращенного контроллером MVC Spring, при использовании аннотаций MappingJacksonHttpMessageConverter и Spring @ResponseBody и @RequestBody?

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;
    ...
}

Когда клиент запрашивает коллекцию Products, я хотел бы вернуть минимальную версию каждого ProductDescription, возможно, только его идентификатор. Затем в следующем вызове клиент может использовать этот идентификатор, чтобы запросить полный экземпляр ProductDescription со всеми имеющимися свойствами.

Было бы идеально, если бы это можно было определить в методах контроллера Spring MVC, поскольку вызванный метод определяет контекст, в котором клиент запрашивал данные.

4b9b3361

Ответ 1

Эта проблема решена!
Следуйте this

Добавить поддержку представлений сериализации Jackson

Spring MVC теперь поддерживает представления сериализации Jackon для рендеринга разные подмножества одного POJO от другого контроллера методы (например, подробные страницы и сводный обзор). Проблема: SPR-7156

Это SPR-7156.

Статус: разрешено

Описание

Аннотации Jackson JSONView позволяют разработчику контролировать, какие аспекты метода являются сериализованными. В текущей реализации должен использоваться редактор просмотра Jackson, но тогда тип контента недоступен. Было бы лучше, если бы в аннотации RequestBody можно было указать JSONView.

Доступно на Spring ver >= 4.1

UPDATE

Следуйте за этой ссылкой . Объясняет пример аннотацией @JsonView.

Ответ 2

В конечном счете, мы хотим использовать обозначения, похожие на то, что StaxMan показал для JAX-RS. К сожалению, Spring не поддерживает это из коробки, поэтому мы должны сделать это сами.

Это мое решение, оно не очень красивое, но оно выполняет эту работу.

@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());
    }
}

В spring -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>

и

<mvc:interceptors>
    <bean class="com.mycompany.myproject.JsonViewInterceptor" />
</mvc:interceptors>

Ответ 3

Я не знаю, как все работает с Spring (извините!), но Jackson 1.9 может использовать аннотацию @JsonView из методов JAX-RS, поэтому вы можете сделать:

@JsonView(ViewId.class)
@GET // and other JAX-RS annotations
public Pojo resourceMethod()
{
   return new Pojo();
} 

и Джексон будет использовать View, идентифицированный ViewId.class как активный вид. Возможно, Spring имеет (или будет иметь) аналогичную возможность? С JAX-RS это обрабатывается стандартным JacksonJaxrsProvider, для чего это стоит.

Ответ 4

В поисках того же ответа я придумал идею обернуть объект ResponseBody с представлением.

Класс контроллера:

@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;
}

}


Затем я переопределяю форму метода writeInternal MappingJackson2HttpMessageConverter, чтобы проверить, является ли объект сериализованным экземпляром обертки instanceof, если поэтому я сериализую объект с требуемым представлением.

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);
}

}

Ответ 5

Ответ на это после того, как много головокружительных тупиков и гадких ярости... так просто. В этом случае у нас есть клиент bean со сложным адресом Address, встроенным в него, и мы хотим предотвратить сериализацию имени свойства surburb и street по адресу, когда выполняется сериализация json.

Мы делаем это, применяя аннотацию @JsonIgnoreProperties ({ "suburb" }) в полевом поле в классе Customer, количество игнорируемых полей Безграничный. например, я хочу проникнуть как в пригород, так и на улицу. Я бы аннотировал поле адреса с помощью @JsonIgnoreProperties ({ "пригород", "улица" })

Выполняя все это, мы можем создать архитектуру типа HATEOAS.

Ниже приведен полный код

Customer.java

public class Customer {

private int id;
private String email;
private String name;

@JsonIgnoreProperties({"suburb", "street"})
private Address address;

public Address getAddress() {
    return address;
}

public void setAddress(Address address) {
    this.address = address;
}

public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

}

Address.java public class Address {

private String street;
private String suburb;
private String Link link;

public Link getLink() {
    return link;
}

public void setLink(Link link) {
    this.link = link;
}


public String getStreet() {
    return street;
}

public void setStreet(String street) {
    this.street = street;
}

public String getSuburb() {
    return suburb;
}

public void setSuburb(String suburb) {
    this.suburb = suburb;
}

}

Ответ 6

В дополнение к @user356083 я внесла некоторые изменения, чтобы этот пример работал, когда возвращается @ResponseBody. Это немного взломать с помощью ThreadLocal, но Spring, похоже, не обеспечивает необходимый контекст, чтобы сделать это приятным способом.

public class ViewThread { 

    private static final ThreadLocal<Class<?>[]> viewThread = new ThreadLocal<Class<?>[]>(); 

    private static final Log log = LogFactory.getLog(SocialRequestUtils.class); 

    public static void setKey(Class<?>[] key){ 
        viewThread.set(key); 
    } 

    public static Class<?>[] getKey(){ 
        if(viewThread.get() == null) 
            log.error("Missing threadLocale variable"); 

        return viewThread.get(); 
    } 
} 

public class JsonViewInterceptor extends HandlerInterceptorAdapter { 

    @Override 
    public boolean preHandle( 
            HttpServletRequest request, 
            HttpServletResponse response, 
            Object handler) { 

        HandlerMethod handlerMethod = (HandlerMethod) handler; 

        JsonView jsonViewAnnotation = handlerMethod 
                .getMethodAnnotation(JsonView.class); 

        if (jsonViewAnnotation != null) 
            ViewThread.setKey(jsonViewAnnotation.value()); 

        return true; 
    } 
} 

public class MappingJackson2HttpMessageConverter extends 
        AbstractHttpMessageConverter<Object> { 

    @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); 
        // This is a workaround for the fact JsonGenerators created by ObjectMapper#getJsonFactory 
        // do not have ObjectMapper serialization features applied. 
        // See https://github.com/FasterXML/jackson-databind/issues/12 
        if (objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) { 
            jsonGenerator.useDefaultPrettyPrinter(); 
        } 

        //A bit of a hack.  
        Class<?>[] jsonViews = ViewThread.getKey(); 

        ObjectWriter writer = null; 

        if(jsonViews != null){ 
            writer = this.objectMapper.writerWithView(jsonViews[0]); 
        }else{ 
            writer = this.objectMapper.writer(); 
        } 

        try { 
            if (this.prefixJson) { 
                jsonGenerator.writeRaw("{} && "); 
            } 

            writer.writeValue(jsonGenerator, object); 

        } 
        catch (JsonProcessingException ex) { 
            throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); 
        } 
    }

Ответ 7

Еще лучше, начиная с версии 4.2.0. Вы можете сделать это просто следующим образом:

@RequestMapping(method = RequestMethod.POST, value = "/springjsonfilter")
    public @ResponseBody MappingJacksonValue byJsonFilter(...) {
        MappingJacksonValue jacksonValue = new MappingJacksonValue(responseObj);    
        jacksonValue.setFilters(customFilterObj);
        return jacksonValue;
    }

Литература:  1. https://jira.spring.io/browse/SPR-12586  2. http://wiki.fasterxml.com/JacksonFeatureJsonFilter