2014-10-23 4 views
2

На этом сайте было задано много одинаковых вопросов без удовлетворительных ответов, поэтому я попытаюсь немного сфокусировать этот вопрос, чтобы получить ответ.Частичная JSON-сериализация объектов ответа REST в Spring MVC

Вот цель, я хотел бы мой Spring MVC 4 (с Spring HATEOAS) веб-сервис, чтобы принять следующие запросы и отвечать соответствующим образом сериализованными лиц:

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

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

я хотел бы сохранить мой модели предметной области в моих классах контроллеров и сделать фильтрацию поля во время сериализации 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; 
    } 
} 

так что мой контроллер методы что-то вроде этого:

@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); 

} 

Чтобы выполнить фильтрацию, я попытался создать собственный HttpMessageConverter путем расширения Джексона MappingJackson2HttpMessageConverter так, что он идентифицирует 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, чтобы я мог покончить с классом-оболочкой?

ответ

0

После нескольких экспериментов я нашел решение, которое, кажется, делает то, что я хочу. Во-первых, я аннотированный моей модели домена классов с общим фильтром аннотацию:

@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()); 
     } 

    } 
} 

Это, кажется, делает работу, но было бы неплохо, чтобы не обернуть свои модели домена объектов в другом классе, чтобы пройти фильтруемое поле от моего контроллера JSON-сериализатор, но это небольшая жертва, с которой я могу жить.

Смежные вопросы