Spring和Jackson Json,用视图序列化不同的字段

时间:2011-10-17 12:24:35

标签: json spring serialization spring-mvc jackson

previous similar question中,我问过如何使用JacksonJson和Spring序列化两组不同的字段。

我的用例是带有@ResponseBody注释的典型Controller映射,直接返回特定对象或对象集合,然后只要客户端在application/json中添加Accept,就会使用JacksonJson进行呈现报头中。

我有两个答案,第一个建议使用不同的getter列表返回不同的接口,第二个建议使用Json Views。

我对第一种方式没有问题,但是,对于第二种方式,在阅读JacksonJsonViews上的文档之后,我不知道如何用Spring实现它。

为了继续这个例子,我将在类Views:

中声明三个存根类
// View definitions:
public class Views {
    public static class Public { }
    public static class ExtendedPublic extends PublicView { }
    public static class Internal extends ExtendedPublicView { }
}

然后我要声明所提到的类:

public class PublicView { }   
public class ExtendedPublicView { }

为什么他们声称空的静态类和外部空类,我不知道。我知道他们需要一个“标签”,但是View的静态成员就足够了。并不是ExtendedPublic扩展Public,因为它是合乎逻辑的,但实际上它们完全不相关。

最后,bean将使用注释指定视图或视图列表:

//changed other classes to String for simplicity and fixed typo 
//in classname, the values are hardcoded, just for testing
public class Bean {
    // Name is public
    @JsonView(Views.Public.class)
    String name = "just testing";

    // Address semi-public
    @JsonView(Views.ExtendedPublic.class)
    String address = "address";

    // SSN only for internal usage
    @JsonView(Views.Internal.class)
    String ssn = "32342342";
}

最后在Spring Controller中,我要考虑如何更改测试bean的原始映射:

@RequestMapping(value = "/bean") 
@ResponseBody
public final Bean getBean() {
    return new Bean();
}

它说要打电话:

//or, starting with 1.5, more convenient (ObjectWriter is reusable too)
objectMapper.viewWriter(ViewsPublic.class).writeValue(out, beanInstance);

所以我有一个ObjectMapper实例无处不在,out不是典型的PrintWriter out = response.getWriter(); servlet,而是JsonGenerator的实例,可以'用新的操作员获得。所以我不知道如何修改方法,这里是一个不完整的尝试:

@RequestMapping(value = "/bean")
@ResponseBody
public final Bean getBean() throws JsonGenerationException, JsonMappingException, IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    JsonGenerator out; //how to create?
    objectMapper.viewWriter(Views.Public.class).writeValue(out, new Bean());
    return ??; //what should I return?
}

所以我想知道是否有人使用JsonView与Spring一起成功,以及他/她是如何做到的。整个概念似乎很有趣,但文档似乎缺乏,也缺少示例代码。

如果不可能,我将只使用相互扩展的接口。很抱歉这个问题很长。

3 个答案:

答案 0 :(得分:6)

基于@igbopie和@chrislovecnm的答案,我整理了一个注释驱动的解决方案:

@Controller
public class BookService
{
    @RequestMapping("/books")
    @ResponseView(SummaryView.class)
    public @ResponseBody List<Book> getBookSummaries() {}

    @RequestMapping("/books/{bookId}")
    public @ResponseBody Book getBook(@PathVariable("bookId") Long BookId) {}
}

SummaryView模型上注释Book,如下所示:

@Data
class Book extends BaseEntity
{
    @JsonView(SummaryView.class)
    private String title;
    @JsonView(SummaryView.class)
    private String author;
    private String review;

    public static interface SummaryView extends BaseView {}
}

@Data
public class BaseEntity
{
    @JsonView(BaseView.class)
    private Long id;    
}

public interface BaseView {}

然后将自定义HandlerMethodReturnValueHandler连接到Spring MVC的上下文中以检测@ResponseView注释,并相应地应用Jackson视图。

我提供了完整的代码over on my blog

答案 1 :(得分:4)

您需要手动连接MappingJacksonHttpMessageConverter。在3.1版本中,您可以使用mvc xml标签,如下所示:

<mvc:annotation-driven >
    <mvc:message-converter>
        <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
    </mvc:message-converters>
</mvc:annotation-driven>

不使用spring 3.1非常难看,它会为你节省大约20行xml。 mvc:annotation标签执行ALOT。

您需要使用正确的视图编写器连接对象映射器。我最近注意到使用@Configuration类可以使这样的复杂布线变得更容易。使用@Configuration类并使用MappingJacksonHttpMessageConverter创建一个@Bean,并将引用连接到该bean而不是上面的MappingJacksonHttpMessageConverter。

答案 2 :(得分:2)

我设法以这种方式解决问题:

  • 创建自定义抽象类以包含json响应对象:
public abstract AbstractJson<E>{
    @JsonView(Views.Public.class)
    private E responseObject;

    public E getResponseObject() {
        return responseObject;
    }
    public void setResponseObject(E responseObject) {
        this.responseObject = responseObject;
    }
}
  • 为每个可见性创建一个类(仅用于标记响应):
public class PublicJson<E> extends AbstractJson<E> {}
public class ExtendedPublicJson<E> extends AbstractJson<E> {}
public class InternalJson<E> extends AbstractJson<E> {}
  • 更改方法声明:
    @RequestMapping(value = "/bean")
    @ResponseBody
    public final PublicJson<Bean> getBean() throws JsonGenerationException, JsonMappingException, IOException {
         return new PublicJson(new Bean());
    }
  • 创建海关MessageConverter:
public class PublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{

    public PublicApiResponseMessageConverter(){
        super();
        org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
        objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
        objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Public.class));
        this.setObjectMapper(objMapper);
    }

     public boolean canWrite(Class<?> clazz, MediaType mediaType) {
         if(clazz.equals(PublicJson.class)){
             return true;
         }
         return false;
     }

}

public class ExtendedPublicJsonMessageConverter extends MappingJacksonHttpMessageConverter{

    public ExtendedPublicJsonMessageConverter(){
        super();
        org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
        objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
        objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.ExtendedPublic.class));
        this.setObjectMapper(objMapper);
    }

     public boolean canWrite(Class<?> clazz, MediaType mediaType) {
         if(clazz.equals(ExtendedPublicJson.class)){
             return true;
         }
         return false;
     }

}

public class InternalJsonMessageConverter extends MappingJacksonHttpMessageConverter{

    public InternalJsonMessageConverter(){
        super();
        org.codehaus.jackson.map.ObjectMapper objMapper=new org.codehaus.jackson.map.ObjectMapper();
        objMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
        objMapper.setSerializationConfig(objMapper.getSerializationConfig().withView(Views.Internal.class));
        this.setObjectMapper(objMapper);
    }

     public boolean canWrite(Class<?> clazz, MediaType mediaType) {
         if(clazz.equals(Internal.class)){
             return true;
         }
         return false;
     }

}
  • 将以下内容添加到xml中:
<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="PublicJsonMessageConverter"></bean>
        <bean class="ExtendedPublicJsonMessageConverter"></bean>
        <bean class="InternalJsonMessageConverter"></bean>
    </mvc:message-converters>
</mvc:annotation-driven>

就是这样!我不得不更新到春季3.1但这就是全部。我使用responseObject发送有关json调用的更多信息,但您可以覆盖MessageConverter的更多方法以使其完全透明。我希望有一天会有一个注释。

希望这有帮助!