我正在构建一个使用Spring MVC 4.10和jackson 2.3.2的应用程序。 我有一个Project类,它有子Proposal对象和Customer对象。这些Proposal对象很复杂,我想返回它们的汇总JSON视图。 Customer对象也会发生类似的情况。我试图用@JsonView注释来实现它。
我想问一下,在容器对象类视图中扩展成员对象类的视图是否是这样做的方法,如果没有,是否有更清晰的方法来实现这一点,我不知道。
在今天之前,我的错误印象是您可以使用多个视图注释您的控制器,并且相应地过滤生成的JSON表示。
@JsonView({Project.Extended.class, Proposal.Summary.class, Customer.Summary.class})
@Transactional
@RequestMapping(value="/project", method=RequestMethod.GET)
public @ResponseBody List<Project> findAll() {
return projectDAO.findAll();
}
每个类都有自己的JsonView注释和接口 e.g:
public class Customer {
...
public interface Summary {}
public interface Normal extends Summary {}
public interface Extended extends Normal {}
}
尽管如此,它只是数组中的第一个视图被考虑在内。根据{{3}}
只能使用@JsonView指定一个类或接口 注释,但您可以使用继承来表示JSON视图 层次结构(如果字段是JSON视图的一部分,它也将是一部分 父视图)。例如,此处理程序方法将序列化 用@JsonView(View.Summary.class)注释的字段 @JsonView(View.SummaryWithRecipients.class):
以及https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring
中的官方文档将其与@ResponseBody控制器方法或控制器一起使用 返回ResponseEntity的方法,只需添加@JsonView即可 带有指定视图类或类的类参数的注释 要使用的界面:
所以,我最终在容器对象的视图中扩展了成员的视图,就像这样
@Entity
public class Project {
...
public static interface Extended extends Normal, Proposal.Extended {}
public static interface Normal extends Summary, Customer.Normal {}
public static interface Summary {}
}
并将我的控制器更改为此
@JsonView(Project.Extended.class)
@Transactional
@RequestMapping(value="/project", method=RequestMethod.GET)
public @ResponseBody List<Project> findAll() {
return projectDAO.findAll();
}
这似乎可以解决问题,但我无法找到有关这种情况的文档或讨论。这是JsonViews的预期用途还是有点hackish?
提前谢谢
-Patricio Marrone
答案 0 :(得分:5)
我相信您已根据需要配置了自己的观看次数。问题的根源不是Spring的@JsonView,而是Jackson的视图实现。正如杰克逊的view documentation所述:
每个序列化只有一个活动视图;但由于视图的继承,可以通过聚合组合视图。
所以,看来Spring正在简单地传递并坚持杰克逊2所设定的限制。
答案 1 :(得分:2)
我使用泽西+杰克逊,但发出了同样的问题。
这是我为我的应用程序做的一个技巧让我在序列化期间需要几个JSON视图。我敢打赌,使用Spring MVC代替Jersey也是可能的,但不是100%肯定。它似乎也没有性能问题。也许这对你的情况来说有点复杂,但是如果你有大量可能的视图的大对象,也许它比做大量的继承更好。
所以我使用Jackson Filter方法在序列化中需要多个视图。但是,我还没有找到方法来克服将@JsonFilter(“name”)放在要映射的类之上的问题,这并没有使它如此干净。但我至少在自定义注释中掩盖它:
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonFilter(JSONUtils.JACKSON_MULTIPLE_VIEWS_FILTER_NAME)
public @interface JsonMultipleViews {}
过滤器本身如下所示:
public class JsonMultipleViewsFilter extends SimpleBeanPropertyFilter {
private Collection<Class<?>> wantedViews;
public JsonMultipleViewsFilter(Collection<Class<?>> wantedViews) {
this.wantedViews = wantedViews;
}
@Override
public void serializeAsField( Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer ) throws Exception {
if( include( writer ) ) {
JsonView jsonViewAnnotation = writer.getAnnotation(JsonView.class);
// serialize the field only if there is no @JsonView annotation or, if there is one, check that at least one
// of view classes above the field fits one of required classes. if yes, serialize the field, if no - skip the field
if( jsonViewAnnotation == null || containsJsonViews(jsonViewAnnotation.value()) ) {
writer.serializeAsField( pojo, jgen, provider );
}
}
else if( !jgen.canOmitFields() ) {
// since 2.3
writer.serializeAsOmittedField( pojo, jgen, provider );
}
}
private boolean containsJsonViews(Class<?>[] viewsOfProperty) {
for (Class<?> viewOfProperty : viewsOfProperty) {
for (Class<?> wantedView : wantedViews) {
// check also subclasses of required view class
if (viewOfProperty.isAssignableFrom(wantedView)) {
return true;
}
}
}
return false;
}
@Override
protected boolean include( BeanPropertyWriter writer ) {
return true;
}
@Override
protected boolean include( PropertyWriter writer ) {
return true;
}
}
我可以像这样使用这个过滤器:
public static String toJson( Object object, Collection<Class<?>> jsonViewClasses) throws JsonProcessingException {
// if no json view class is provided, just map without view approach
if (jsonViewClasses.isEmpty()) {
return mapper.writeValueAsString(object);
}
// if only one json view class is provided, use out of the box jackson mechanism for handling json views
if (jsonViewClasses.size() == 1) {
return mapper.writerWithView(jsonViewClasses.iterator().next()).writeValueAsString(object);
}
// if more than one json view class is provided, uses custom filter to serialize with multiple views
JsonMultipleViewsFilter jsonMultipleViewsFilter = new JsonMultipleViewsFilter(jsonViewClasses);
return mapper.writer(new SimpleFilterProvider() // use filter approach when serializing
.setDefaultFilter(jsonMultipleViewsFilter) // set it as default filter in case of error in writing filter name
.addFilter(JACKSON_MULTIPLE_VIEWS_FILTER_NAME, jsonMultipleViewsFilter) // set custom filter for multiple views with name
.setFailOnUnknownId(false)) // if filter is unknown, don't fail, use default one
.writeValueAsString(object);
}
之后,Jersey允许我们在运行应用程序时添加Jersey Filters(它在应用程序启动时通过每个Controller中的每个端点,如果有多个值,我们可以在此时轻松绑定Jersey过滤器在端点上方的@JsonView注释中)。
在针对@JsonView注释的Jersey过滤器中,在端点上面有多个值,一旦启动时根据注释修正端点,我们就可以通过调用utils方法轻松覆盖响应实体
toJson(previousResponeObjectReturned, viewClassesFromAnnoation);
由于您使用的是Spring MVC,因此没有理由在此提供Jersey Filter的代码。我只是希望在Spring MVC中以同样的方式实现它很容易。
域对象看起来像这样:
@JsonMultipleViews
public class Example
{
private int id;
private String name;
@JsonView(JsonViews.Extended.class)
private String extendedInfo;
@JsonView(JsonViews.Meta.class)
private Date updateDate;
public static class JsonViews {
public interface Min {}
public interface Extended extends Min {}
public interface Meta extends Min {}
//...
public interface All extends Extended, Meta {} // interfaces are needed for multiple inheritence of views
}
}
我们可以省略在我的情况下将Min.class放在那些始终不需要视图的字段上。我们只需将Min置于必需的视图中,它将序列化所有字段而不使用@JsonView注释。
我需要查看All.class,因为如果我们有一个特定的每个域类视图集(就像我的情况一样),那么我们需要映射一个由几个域对象组成的复杂模型使用视图方法 - 对象一的一些视图,但对象二的所有视图,更容易将它放在端点之上,如下所示:
@JsonView({ObjectOneViews.SomeView.class, ObjectTwoViews.All.class})
因为如果我们在这里省略ObjectTwoViews.All.class并且只需要ObjectOneViews.SomeView.class,那么在对象2中用注释标记的那些字段将不会被序列化。