从Rest Controller

时间:2015-05-26 19:59:35

标签: java json spring rest spring-mvc

我正在构建一个使用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

2 个答案:

答案 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中用注释标记的那些字段将不会被序列化。