Как вернуть частичный ответ JSON с помощью Java?
Я создаю RESTful API и хочу предоставить разработчикам возможность выбирать, какие поля возвращаться в ответ JSON. Это сообщение в блоге показывает примеры того, как несколько API (Google, Facebook, LinkedIn) позволяют разработчикам настраивать ответ. Это называется частичным ответом.
Пример может выглядеть так:
/users/123?fields=userId,fullname,title
В приведенном выше примере API должен возвращать поля userId, fullName и заголовки для пользователя "123".
Я ищу идеи о том, как реализовать это в своем веб-сервисе RESTful. В настоящее время я использую CXF (edit: и Jackson), но желаю попробовать другую реализацию JAX-RS.
Вот что я сейчас имею. Он возвращает полный пользовательский объект. Как я могу вернуть только те поля, которые хочет запросить API во время выполнения, на основе "параметров полей"? Я не хочу делать другие поля Null. Я просто не хочу их возвращать.
@GET
@Path("/{userId}")
@Produces("application/json")
public User getUser(@PathParam("userId") Long userId,
@DefaultValue("userId,fullname,title") @QueryParam("fields") String fields) {
User user = userService.findOne(userId);
StringTokenizer st = new StringTokenizer(fields, ",");
while (st.hasMoreTokens()) {
// here where i would like to select only the fields i want to return
}
return user;
}
UPDATE:
Я следил за ссылкой unludo, которая затем была связана с этим: http://wiki.fasterxml.com/JacksonFeatureJsonFilter
С этой информацией я добавил @JsonFilter("myFilter")
в свой класс домена. Затем я изменил метод службы RESTful, чтобы вернуть String вместо User следующим образом:
@GET
@Path("/{userId}")
@Produces("application/json")
public String getUser(@PathParam("userId") Long userId,
@DefaultValue("userId,fullname,title") @QueryParam("fields") String fields) {
User user = userService.findOne(userId);
StringTokenizer st = new StringTokenizer(fields, ",");
Set<String> filterProperties = new HashSet<String>();
while (st.hasMoreTokens()) {
filterProperties.add(st.nextToken());
}
ObjectMapper mapper = new ObjectMapper();
FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter",
SimpleBeanPropertyFilter.filterOutAllExcept(filterProperties));
try {
String json = mapper.filteredWriter(filters).writeValueAsString(user);
return json;
} catch (IOException e) {
e.printStackTrace();
return e.getMessage();
}
}
Мне нужно сделать больше тестов, но пока так хорошо.
Ответы
Ответ 1
Если вы используете Jackson (отличный JSON-тип стандарта для Java, я считаю), вы можете использовать аннотацию @View
для фильтрации того, что вы хотите в результирующем объекте.
Я понимаю, что вы хотите что-то динамичное, так что это немного сложнее. Вы найдете то, что вы ищете: http://www.cowtowncoder.com/blog/archives/2011/02/entry_443.html (см. 6. Полностью динамическая фильтрация: @JsonFilter
).
Мне было бы интересно найти решение, которое вы найдете.
Ответ 2
Создание экземпляра ObjectMapper внутри метода ресурса для каждого запроса может иметь значительные накладные расходы. В соответствии с наилучшими методами работы с производительностью в Jackson создатели объектов дороже создавать.
Вместо этого вы можете настроить создателя объекта JAX-RS провайдера Jackson внутри метода ресурсов, используя функцию Jackson 2.3 ObjectWriterModifier/ObjectReaderModifier.
Вот пример показывает, как зарегистрировать локальный объект потока объекта ObjectWriterModifier, который изменяет набор фильтров, применяемых для поставщика JAX-RS Jackson, который используется внутри метода ресурсов. Обратите внимание, что я не тестировал код против реализации JAX-RS.
public class JacksonObjectWriterModifier2 {
private static class FilterModifier extends ObjectWriterModifier {
private final FilterProvider provider;
private FilterModifier(FilterProvider provider) {
this.provider = provider;
}
@Override
public ObjectWriter modify(EndpointConfigBase<?> endpoint, MultivaluedMap<String, Object> responseHeaders,
Object valueToWrite, ObjectWriter w, JsonGenerator g) throws IOException {
return w.with(provider);
}
}
@JsonFilter("filter1")
public static class Bean {
public final String field1;
public final String field2;
public Bean(String field1, String field2) {
this.field1 = field1;
this.field2 = field2;
}
}
public static void main(String[] args) throws IOException {
Bean b = new Bean("a", "b");
JacksonJsonProvider provider = new JacksonJsonProvider();
ObjectWriterInjector.set(new FilterModifier(new SimpleFilterProvider().addFilter("filter1",
SimpleBeanPropertyFilter.filterOutAllExcept("field1"))));
provider.writeTo(b, Bean.class, null, null, MediaType.APPLICATION_JSON_TYPE, null, System.out);
}
}
Вывод:
{"field1":"a"}
Ответ 3
Библиотека jersey-entity-filtering Может сделать это:
https://github.com/jersey/jersey/tree/2.22.2/examples/entity-filtering-selectable
https://jersey.java.net/documentation/latest/entity-filtering.html
Пример:
Мой объект
public class Address {
private String streetAddress;
private String region;
private PhoneNumber phoneNumber;
}
URL
люди/1234? Выберите = StreetAddress, область
RETURN
{
"streetAddress": "2 square Tyson",
"region": "Texas"
}
Добавить в Maven
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-entity-filtering</artifactId>
<version>2.22.2</version>
</dependency>