Как вернуть частичный ответ JSON, используя Java? - PullRequest
41 голосов
/ 16 февраля 2012

Я создаю RESTful API и хочу предоставить разработчикам возможность выбирать, какие поля возвращать в ответе JSON. В этом блоге показаны примеры того, как несколько API (Google, Facebook, LinkedIn) позволяют разработчикам настраивать ответ.Это называется частичным ответом.

Пример может выглядеть следующим образом:

/users/123?fields=userId,fullname,title

В приведенном выше примере API должен возвращать поля userId, fullName и title для пользователя «123».

Я ищу идеи о том, как реализовать это в моем веб-сервисе RESTful.В настоящее время я использую CXF (edit: и Джексон), но хочу попробовать другую реализацию JAX-RS.

Вот что у меня сейчас есть.Возвращает полный объект User.Как я могу вернуть только те поля, которые хочет вызывающий API во время выполнения, основываясь на параметре «fields»?Я не хочу, чтобы другие поля были пустыми.Я просто не хочу их возвращать.

@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's where i would like to select only the fields i want to return

}
return user;
}

ОБНОВЛЕНИЕ:

Я перешел по ссылке 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();
    }
}

Мне нужно больше тестировать, но пока все хорошо.

Ответы [ 3 ]

18 голосов
/ 16 февраля 2012

Если вы используете Джексона (отличный JSON-библиотека - своего рода стандарт для Java, я считаю), вы можете использовать аннотацию @View для фильтрации того, что вы хотите в результирующем объекте.

Я понимаю, что вы хотите что-то динамичное, так что это немного сложнее.Здесь вы найдете то, что ищете: http://www.cowtowncoder.com/blog/archives/2011/02/entry_443.html (см. 6. Полностью динамическая фильтрация: @JsonFilter).

Мне было бы интересно найти решение, которое вы найдете.

5 голосов
/ 23 мая 2014

Создание экземпляра ObjectMapper внутри метода ресурса для каждого запроса может значительно снизить производительность. Согласно рекомендациям Джексона по производительности объектные картографы дороги в создании.

Вместо этого вы можете настроить средство записи объектов Jackson провайдера JAX-RS внутри метода ресурса, используя функцию ObjectWriterModifier / ObjectReaderModifier Jackson 2.3 .

Вот пример, показывающий, как зарегистрировать локальный объект потока 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"}
4 голосов
/ 18 марта 2016

Библиотека джерси-сущности-фильтрация может сделать это:

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, область

ВОЗВРАТ

{
   "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>
...