@JsonFilter抛出“JsonMappingException:无法解析BeanPropertyFilter”

23

在运行时有没有可能有选择性地确定何时使用@JsonFilter注释?

如果我没有提供过滤器,就会出现JsonMappingException异常(请参见下文)。

背景:

我从最近的StackOverflow帖子中了解到,我可以使用@JsonFilter动态过滤序列化的bean属性。这很好用。在我的域类中添加@JsonFilter("apiFilter")后,在我的jax-rs服务(使用CXF实现)中添加了这段代码后,我能够动态地过滤由我的RESTful API返回的属性:

// shortened for brevity
FilterProvider filters = new SimpleFilterProvider().addFilter("apiFilter", SimpleBeanPropertyFilter.filterOutAllExcept(filterProperties));

return mapper.filteredWriter(filters).writeValueAsString(user);

问题在于有不同的服务调用,我不想对所有调用应用过滤器。在这些情况下,我希望返回完整的域类而不过滤任何属性。当我尝试只返回域类时,我会得到以下异常:

Caused by: org.codehaus.jackson.map.JsonMappingException: Can not resolve BeanPropertyFilter with id 'apiFilter'; no FilterProvider configured

at org.codehaus.jackson.map.ser.BeanSerializer.findFilter(BeanSerializer.java:252)
at org.codehaus.jackson.map.ser.BeanSerializer.serializeFieldsFiltered(BeanSerializer.java:216)
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:140)
6个回答

31

我知道这个问题已经被回答了,但是对于任何新手来说,Jackson实际上已经添加了不会因为缺少过滤器而失败的功能(JACKSON-650):
你只需要调用SimpleFilterProvider.setFailOnUnknownId(false),就不会再收到此异常了。


7
即使您在使用的映射器中不使用过滤器,仍然需要配置SimpleFilterProvider。噢,好吧。 - Jules

21

对于Spring Boot / Jackson配置,只需添加:

@Configuration 
public class JacksonConfiguration { 
    public JacksonConfiguration(ObjectMapper objectMapper) { 
        objectMapper.setFilterProvider(new SimpleFilterProvider().setFailOnUnknownId(false)); 
    } 
}

这个解决方案完美地解决了我遇到的问题! - zym

13

我认为你可以通过定义一个空的序列化过滤器来欺骗过滤器编写器,以便在需要序列化所有属性的情况下使用:

FilterProvider filters = new SimpleFilterProvider().addFilter("apiFilter", SimpleBeanPropertyFilter.serializeAllExcept(emptySet));
这样,当引擎查找在@JsonFilter注释中定义的“apiFilter”过滤器时,它会找到它,但不会产生任何影响(因为它将序列化所有属性)。
编辑: 此外,您可以调用工厂方法writer()而不是filteredWriter():
ObjectWriter writer=null;
if(aplyFilter) {
    FilterProvider filters = new SimpleFilterProvider().addFilter("apiFilter", SimpleBeanPropertyFilter.filterOutAllExcept(filterProperties));
    writer=mapper.filteredWriter(filters);
} else {
   writer=mapper.writer();
}

return writer.writeValueAsString(user);

我认为这个最后的解决方案更加简洁,而且确实更好。


在您编辑的示例中,我是否需要在每个JAX-RS服务调用中包含检查要调用的writer方法的代码?在某些服务方法中,我返回的是一个实际的User对象而不是一个字符串。非常感谢您的建议! - Justin
1
好的,我有机会尝试了一下。你建议的“技巧”有效,但是我无法使你第二个“更简洁”的建议起作用。在那种情况下,我仍然收到“未配置FilterProvider”的错误。再次感谢。 - Justin
@Justin:在我看来,解决问题的“不太干净”的解决方法总比“干净”但无法解决问题的方法好。希望这有助于解决你的问题。 - Tomas Narros

2
我遇到了同样的问题,也得到了相同的异常。但是,被接受的答案并没有真正帮助我解决问题。下面是对我有用的解决方案:
在我的设置中,我使用了自定义的JacksonSerializer,如下所示:
@JsonSerialize(using = MyCustomSerializer.class)
private Object someAttribute;

而该序列化程序的实现如下:

public class MyCustomSerializer extends JsonSerializer<Object> {
  @Override
  public void serialize(Object o, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
    if (o != null) {
      jgen.writeObject(o);
    }
  }
}

问题在于,只要您不使用任何过滤器,它就可以工作。如果您序列化原始数据类型,例如使用jgen.writeString(..),它也可以正常工作。但是,如果您使用过滤器,那么这段代码就是错误的,因为过滤器存储在SerializerProvider中的某个位置,而不是JsonGenerator中。如果在这种情况下直接使用jsongenerator,则会在内部创建一个新的SerializerProvider,该SerializerProvider不知道过滤器的存在。因此,您需要调用provider.defaultSerializeValue(o, jgen),而不是简短的jgen.writeObject(o)。这将确保过滤器不会丢失并且可以应用。

0
我使用了与已接受的解决方案相同的方法,但是当我将writer.writeValueAsString(course)作为Rest服务响应返回时,我得到的响应格式如下。
{ "status": "OK", "data": "[{\"name\":\"JPA in Use\",\"reviews\":[{\"id\":4081,\"rating\":\"4\",\"description\":\"Fine\"},{\"id\":4084,\"rating\":\"4\",\"description\":\"Ok\"}]},{\"name\":\"Spring in Use\",\"reviews\":[{\"id\":4003,\"rating\":\"3\",\"description\":\"Nice Course\"}]}]" }

但是我的预期响应是

{ "status": "OK", "data": [ { "name": "JPA in Use", "reviews": [ { "id": 4081, "rating": "4", "description": "Fine" }, { "id": 4082, "rating": "5", "description": "Great" } ] }, { "name": "Spring in Use", "reviews": [ { "id": 4003, "rating": "3", "description": "Nice Course" } ] } ] }

为了获取我的响应,我已将json字符串转换为特定的对象类型。
List<Course> resultcourse = mapper.readValue(writeValueAsString,List.class);

注意:课程具有id、名称和评论字段,我想抑制id。

我提供代码片段,希望对一些人有所帮助。

@GetMapping("/courses")
    public ResponseEntity<JpaResponse> allCourse() throws Exception {
        JpaResponse response = null;
         ObjectMapper mapper = new ObjectMapper(); 
         mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        List<Course> course = service.findAllCourse();
        SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("name","reviews");
        FilterProvider filterProvider = new SimpleFilterProvider().addFilter("jpafilter", filter).setFailOnUnknownId(false);
                ObjectWriter writer = mapper.writer(filterProvider);
        String writeValueAsString = writer.writeValueAsString(course);
        List<Course> resultcourse = mapper.readValue(writeValueAsString,List.class);
            response = new JpaResponse(HttpStatus.OK.name(),resultcourse);
            return new ResponseEntity<>(response, HttpStatus.OK);

}


public class JpaResponse {
        private String status;
        private Object data;
        public JpaResponse() {
            super();
        }
        public JpaResponse(String status, Object data) {
            super();
            this.status = status;
            this.data = data;
        }
}

0

这是我为Springboot所做的,不需要更多逻辑即可从应用程序的所有REST响应中过滤掉这些字段,如果您需要过滤更多的POJOs,只需将它们添加到FilterProvider中:

添加一个带有过滤器的配置类:

@Configuration
public class JacksonConfiguration {

    public JacksonConfiguration(ObjectMapper objectMapper) {
        SimpleFilterProvider simpleFilterProvider = new SimpleFilterProvider();
        FilterProvider filters = simpleFilterProvider.addFilter("PojoFilterDTO",
                SimpleBeanPropertyFilter.serializeAllExcept("field1", "field2")).setFailOnUnknownId(false);
        objectMapper.setFilterProvider(filters);
        objectMapper.configure(SerializationFeature.FAIL_ON_SELF_REFERENCES, false);
    }
}

在你的POJO中添加JsonFilter注解:

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonFilter("PojoFilterDTO")
public class PojoDTO {
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接