java - Spring MVC 中 REST 响应对象的部分 JSON 序列化

标签 java json rest spring-mvc jackson

到目前为止,这个网站上已经提出了很多类似的问题,但都没有令人满意的答案,所以我将尝试稍微关注这个问题以获得答案。

这是目标,我希望我的 Spring MVC 4(带有 Spring HATEOAS)Web 服务接受以下请求并使用适当序列化的实体进行响应:

GET http://myapp/api/people
==> [{
        "id": 1,
        "name": "Joe",
        "email": "joe@domain.com",
        "links": [...]
    },
    {...etc}]

GET http://myapp/api/people?fields=id,email
==> [{
        "id": 1,
        "email": "joe@domain.com"
    },
    {...etc}]

我想在我的 Controller 类中保留我的域模型并在 JSON 序列化期间进行字段过滤,因此我将我的响应包装在一个简单的类中,其中包含一个字段来存储要在过滤中使用的字段:

public class RestResponseEnvelope<T> {

    private Set<String> fieldSet;
    private T entity;

    public RestResponseEnvelope(T entity) {
        this.entity = entity;
    }

    public T getEntity() {
        return entity;
    }

    public Set<String> getFieldSet() {
        return fieldSet;
    }

    public void setFieldSet(Set<String> fieldSet) {
        this.fieldSet = fieldSet;
    }

    public void setFields(String fields) {
        Set<String> fieldSet = new HashSet<>();
        if (fields != null) {
            for (String field : fields.split(",")) {
                fieldSet.add(field);
            }
        }
        this.fieldSet = fieldSet;
    }
}

所以我的 Controller 方法最终看起来像这样:

@RequestMapping(value = "", method = RequestMethod.GET)
public HttpEntity<RestResponseEnvelope<Resources<<Resource<Person>>>> findAllPeople(
        @RequestParam(value = "fields", required = false) String fields
){

    List<Resource<Person>> peopleResourceList = new ArrayList<>();
    for (Person person: personService.findAllPeople()){
        Resource<Person> resource = new Resource<>(person);
        resource.add(link...);
        peopleResourceList.add(resource);
    }

    Resources<Resource<Person>> resources = new Resources<>(personResourceList);
    resources.add(link...);

    RestResponseEnvelope<Resources<Resource<Person>>> responseEnvelope = new RestResponseEnvelope<>(resources);
    responseEnvelope.setFields(fields);

    return new ResponseEntity<>(responseEnvelope, HttpStatus.OK);

}

为了执行过滤,我尝试通过扩展 Jackson 的 MappingJackson2HttpMessageConverter 来创建自定义 HttpMessageConverter,以便它识别 RestResponseEnvelope 对象并使用它们fieldList 应用 filterOutAllExcept 过滤器:

@Component
public class FilteringJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

    private boolean prefixJson = false;

    @Override
    public void setPrefixJson(boolean prefixJson) {
        this.prefixJson = prefixJson;
        super.setPrefixJson(prefixJson);
    }

    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage)
        throws IOException, HttpMessageNotWritableException {

        ObjectMapper objectMapper = getObjectMapper();
        JsonGenerator jsonGenerator = objectMapper.getFactory().createGenerator(outputMessage.getBody());

        try { 
            if (this.prefixJson) {
                jsonGenerator.writeRaw("{} && ");
            }
            if (object instanceof RestResponseEnvelope){
                Set<String> fieldSet = ((RestResponseEnvelope) object).getFieldSet();
                Object entity = ((RestResponseEnvelope) object).getEntity();
                if (fieldSet != null && !fieldSet.isEmpty()) {
                    objectMapper.addMixInAnnotations(CellLine.class, PropertyFilterMixin.class);
                    FilterProvider filters = new SimpleFilterProvider()
                            .addFilter("filterPropertiesByName",
                                    SimpleBeanPropertyFilter.filterOutAllExcept(fieldSet));
                    objectMapper.setFilters(filters);
                } 
                objectMapper.writeValue(jsonGenerator, entity);
            } else {
                objectMapper.writeValue(jsonGenerator, object);
            }
        } catch (JsonProcessingException e){
            throw new HttpMessageNotWritableException("Could not write JSON: " + e.getMessage());
        }
    }
}

@JsonFilter("filterPropertiesByName")
class PropertyFilterMixin { }

我试过这个设置,结果好坏参半。当对筛选字段的请求正常工作时,对所有字段的后续请求将返回再次筛选的对象。其他时候过滤不起作用,或者只是过滤掉所有内容。

我希望能够获取请求字段的列表并返回序列化对象,只保留那些字段,理想情况下无需执行注释混合,但我完全不知道如何完成此操作。当被序列化的顶级对象是包装器或 Resource 对象时,我的过滤方案是否会正确地保留嵌套的属性字段?是否可以从 JSON 序列化过程的上下文中获取请求参数,以便我可以取消包装类?

最佳答案

经过更多实验后,我找到了一个似乎可以满足我要求的解决方案。首先,我使用通用过滤器注释对我的域模型类进行了注释:

@JsonFilter("fieldFilter")
public class Person {
    ...
}

然后我更新了我的消息转换器以消除注释混合并始终使用我的 ObjectMapper 注册此过滤器,但如果我的字段集参数不存在,过滤将更改为包括所有内容:

@Component
public class FilteringJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {

    private boolean prefixJson = false;

    @Override
    public void setPrefixJson(boolean prefixJson) {
        this.prefixJson = prefixJson;
        super.setPrefixJson(prefixJson);
    }

    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {

        ObjectMapper objectMapper = getObjectMapper();
        JsonGenerator jsonGenerator = objectMapper.getFactory().createGenerator(outputMessage.getBody());

        try {

            if (this.prefixJson) {
                jsonGenerator.writeRaw("{} && ");
            }

            if (object instanceof RestResponseEnvelope) {
                Object entity = ((RestResponseEnvelope) object).getEntity();
                Set<String> fieldSet = ((RestResponseEnvelope) object).getFieldSet();
                if (fieldSet != null && !fieldSet.isEmpty()) {
                    FilterProvider filters = new SimpleFilterProvider().addFilter("fieldFilter",
                                SimpleBeanPropertyFilter.filterOutAllExcept(fieldSet)).setFailOnUnknownId(false);
                    objectMapper.setFilters(filters);
                } else {
                    FilterProvider filters = new SimpleFilterProvider().addFilter("fieldFilter", 
                            SimpleBeanPropertyFilter.serializeAllExcept()).setFailOnUnknownId(false);
                    objectMapper.setFilters(filters);
                }
                objectMapper.writeValue(jsonGenerator, entity);

            } else if (object == null){
                jsonGenerator.writeNull();
            } else {
                objectMapper.writeValue(jsonGenerator, object);
            }

        } catch (JsonProcessingException e){
            e.printStackTrace();
            throw new HttpMessageNotWritableException("Could not write JSON: " + e.getMessage());
        }

    }
}

这似乎可以完成工作,但如果不必将我的域模型对象包装在另一个类中以便将可过滤字段从我的 Controller 传递到 JSON 序列化程序会更好,但这是我可以做出的一个小牺牲一起生活。

关于java - Spring MVC 中 REST 响应对象的部分 JSON 序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26529646/

相关文章:

Java File.renameTo(文件)不工作

sql - 查询 JSON 的组合返回奇怪的结果

rest - REST API 设计中的查找或创建习惯用法?

java - 简单方程未通过单元测试

Java合并排序递归的奇怪之处

c# - Asp.net Core 1.0 project.json 预发布脚本

.net - RestSharp 压缩请求,同时对服务器进行休息调用

java - Android 应用程序中用于多个 REST 端点的 AsyncTask

java - Spring 事务注解

javascript - 动态创建嵌套 JSON 对象父级和子级