Джексон @JsonView, @JsonFilter и Spring - PullRequest
30 голосов
/ 03 октября 2011

Можно ли использовать аннотации Jackson @JsonView и @JsonFilter для изменения JSON, возвращаемого контроллером Spring MVC, при использовании аннотаций 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, так каквызываемый метод определяет контекст, в котором клиент запрашивал данные.

Ответы [ 7 ]

13 голосов
/ 17 мая 2014

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

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

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

Это SPR-7156 .

Статус: Решено

Описание

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

Доступно в Spring ver> = 4,1

UPDATE

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

13 голосов
/ 19 апреля 2012

В конечном счете, мы хотим использовать нотацию, аналогичную той, что показала 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());
    }
}

In 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>
12 голосов
/ 12 октября 2011

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

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

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

4 голосов
/ 31 декабря 2013

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

Мы делаем это, применяя аннотацию @ JsonIgnoreProperties ({"пригород"}) к адресу поля в классе 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 общедоступный адрес класса {

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

}

4 голосов
/ 15 августа 2012

В поисках того же ответа у меня возникла идея обернуть объект 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, чтобы проверить, еслиОбъект для сериализации является экземпляром оболочки, если это так, я сериализую объект с требуемым представлением.

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

}

2 голосов
/ 19 октября 2015

Еще лучше, начиная с 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

2 голосов
/ 30 июля 2012

В дополнение к @ 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); 
        } 
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...