복붙노트

[SPRING] Jackson의 @JsonView, @JsonFilter 및 Spring

SPRING

Jackson의 @JsonView, @JsonFilter 및 Spring

Spring의 @ResponseBody와 @RequestBody annotation을 사용하면서 Spring MVC 컨트롤러에 의해 반환 된 JSON을 수정하기 위해 Jackson @JsonView와 @JsonFilter 주석을 사용할 수 있습니까?

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

클라이언트가 제품 모음을 요청하면 각 ProductDescription의 최소 버전 (아마도 ID 만)을 반환하고 싶습니다. 이후에 클라이언트는이 ID를 사용하여 모든 속성이있는 ProductDescription의 전체 인스턴스를 요청할 수 있습니다.

호출 된 메소드가 클라이언트가 데이터를 요청한 컨텍스트를 정의하기 때문에 Spring MVC 컨트롤러 메소드에서 이것을 지정할 수있는 것이 이상적이다.

해결법

  1. ==============================

    1.이 문제는 해결되었습니다! 팔로우

    이 문제는 해결되었습니다! 팔로우

    이것은 SPR-7156입니다.

    Spring 버전 4.1 이상에서 사용 가능

    이 링크를 따라 가십시오. @JsonView 주석의 예를 설명합니다.

  2. ==============================

    2.궁극적으로 우리는 StaxMan이 JAX-RS에서 보여준 것과 유사한 표기법을 사용하고자합니다. 불행히도, Spring은 이것을 기본적으로 지원하지 않으므로 직접 처리해야합니다.

    궁극적으로 우리는 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. ==============================

    3.JAX-RS 메소드의 @JsonView 주석을 사용할 수있는 Jackson 1.9는 Spring (일종의)이 어떻게 작동하는지 알지 못합니다.

    JAX-RS 메소드의 @JsonView 주석을 사용할 수있는 Jackson 1.9는 Spring (일종의)이 어떻게 작동하는지 알지 못합니다.

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

    Jackson은 ViewId.class로 식별 된 View를 활성보기로 사용합니다. 아마도 스프링은 비슷한 기능을 가지고있을 것인가? JAX-RS에서는 표준 JacksonJaxrsProvider가이를 처리합니다.

  4. ==============================

    4.같은 대답을 찾고 나는 ResponseBody 객체를보기로 감싸는 아이디어를 생각해 냈습니다.

    같은 대답을 찾고 나는 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 메소드 form MappingJackson2HttpMessageConverter를 재정 의하여 직렬화 할 객체가 instanceof wrapper인지 확인합니다. 필요한 경우 객체를 직렬화합니다.

    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. ==============================

    5.많은 사람들이 죽은 목숨을 위협하고 괴상한 분노를 겪은 후 이에 대한 답은 .... 너무 간단합니다. 이 유즈 케이스에서는 복잡한 객체 주소가 포함 된 Customer Bean이 포함되어 있으며 json 직렬화가 발생할 때 주소의 주소 surburb 및 street의 직렬화를 방지하려고합니다.

    많은 사람들이 죽은 목숨을 위협하고 괴상한 분노를 겪은 후 이에 대한 답은 .... 너무 간단합니다. 이 유즈 케이스에서는 복잡한 객체 주소가 포함 된 Customer Bean이 포함되어 있으며 json 직렬화가 발생할 때 주소의 주소 surburb 및 street의 직렬화를 방지하려고합니다.

    우리는 고객 클래스의 필드 주소에 @JsonIgnoreProperties ({ "suburb"}) 주석을 적용하여이 작업을 수행합니다. 무시할 필드의 수는 무한합니다. 예를 들어 나는 교외와 거리 모두를 잉태하고 싶습니다. @JsonIgnoreProperties ({ "suburb", "street"})로 주소 필드에 주석을 달았습니다.

    이 모든 것을함으로써 우리는 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. ==============================

    6.@ user356083 외에도 @ResponseBody가 반환 될 때이 예제가 작동하도록 일부 수정했습니다. ThreadLocal을 사용하는 약간의 해킹이지만 Spring은 좋은 방법을 수행하는 데 필요한 컨텍스트를 제공하지 않는 것 같습니다.

    @ 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. ==============================

    7.4.2.0.Release 이후부터는 다음과 같이 간단하게 할 수 있습니다.

    4.2.0.Release 이후부터는 다음과 같이 간단하게 할 수 있습니다.

    @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

  8. from https://stackoverflow.com/questions/7637467/jacksons-jsonview-jsonfilter-and-spring by cc-by-sa and MIT license