Использование Jackson ObjectMapper с Джерси

Я использую Jersey 2.4 для создания простого интерфейса REST, который обслуживает объект JSON. Моя проблема в том, что я пытаюсь использовать комментарии quickxml Jackson для контроля вывода, и это не работает для меня. Я поместил аннотации в мой класс bean, но они игнорируются.

Когда я явно создаю ObjectMapper и использую его для создания Java bean, я получаю вывод, который я хочу, который уважает аннотации Джексона. Тем не менее, я бы предпочел, чтобы мне не приходилось делать этот шаг, чтобы мой класс ресурсов мог просто вернуть bean, а структура "Джерси" позаботится о его подделке.

Я попытался решить эту проблему с помощью ответа Custom ObjectMapper с версией Jersey 2.2 и Jackson 2.1, однако это не работает для меня. Я вижу, что ContextResolver создан, но он никогда не вызывается.

Я также потратил много часов, пытаясь решить эту, по-видимому, простую проблему. Я разделил это на очень простой тестовый пример, который показан ниже. Я был бы признателен за любую помощь в решении этого вопроса.

Класс ресурса Java:

@Path("resource")
public class MainResource {

    public static class Foobar {
        @JsonIgnore
        private String foo = "foo";
        private String baa = "baa";
        private Map<String, List<? extends Number>> map = new HashMap<>();

        public Foobar() {
            map.put("even", Arrays.asList(new Integer[] { 2, 4, 6, 8, 10 }));
            map.put("odd", Arrays.asList(new Integer[] { 1, 3, 5, 7, 9 }));
            map.put("float", Arrays.asList(new Float[] { 1.1F, 2.2F, 3.3F }));
        }

        public String getFoo() {
            return foo;
        }

        public void setFoo(String foo) {
            this.foo = foo;
        }

        public String getBaa() {
            return baa;
        }

        public void setBaa(String baa) {
            this.baa = baa;
        }

        @JsonAnyGetter
        public Map<String, List<? extends Number>> getMap() {
            return map;
        }

        public void setMap(Map<String, List<? extends Number>> map) {
            this.map = map;
        }
    }

    private ObjectMapper om = new ObjectMapper();

    @GET
    @Path("get-object")
    @Produces(MediaType.APPLICATION_JSON)
    public Foobar getObject() {
        // In this method, I simply return the bean object but the WRONG JSON syntax is generated.
        return new Foobar();
    }

    @GET
    @Path("get-string")
    @Produces(MediaType.APPLICATION_JSON)
    public String getString() throws JsonProcessingException {
        // This method returns the RIGHT JSON syntax but I don't want to have to explicitly use the ObjectMapper.
        Foobar foobar = new Foobar();
        return om.writeValueAsString(foobar);
    }
}

web.xml:

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

    <module-name>sample</module-name>

    <servlet>
        <servlet-name>Jersey Web Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>ie.cit.nimbus.sample</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>Jersey Web Application</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

Зависимости POM:

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-json-provider</artifactId>
        <version>2.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.ext</groupId>
        <artifactId>jersey-spring3</artifactId>
        <version>2.4.1</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>2.4.1</version>
    </dependency>
</dependencies>

Ответ 1

EDIT. Не используйте старый подход ниже, поскольку он создает ошибки (по крайней мере, с устройством Android, см. EDIT2 для получения более подробной информации). По моим испытаниям, Джерси v2.6, похоже, устраняет проблему с помощью @Provide, который не работает. Мне удалось заставить его работать с этим простым провайдером:

@Provider
public class JerseyMapperProvider implements ContextResolver<ObjectMapper> {
    private static ObjectMapper apiMapper = ObjectMapperManager.createMapperForApi();
    @Override
    public ObjectMapper getContext(Class<?> type)
    {
        return apiMapper;
    }
}

Поэтому, пожалуйста, не используйте мой хак внизу.


СТАРЫЙ ПОДХОД

Используя

@Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper>

не работал у меня (Jersey 2.4 и Jackson 2.3), и, возможно, это связано с сообщением провайдера jackson о том, что код ContextResolver должен быть зарегистрирован в JacksonJsonProvider.java (2.3rc1):

 @Override
protected ObjectMapper _locateMapperViaProvider(Class<?> type, MediaType mediaType)
{
    if (_providers != null) {
        ContextResolver<ObjectMapper> resolver = _providers.getContextResolver(ObjectMapper.class, mediaType);
        /* Above should work as is, but due to this bug
         *   [https://jersey.dev.java.net/issues/show_bug.cgi?id=288]
         * in Jersey, it doesn't. But this works until resolution of
         * the issue:
         */
        if (resolver == null) {
            resolver = _providers.getContextResolver(ObjectMapper.class, null);
        }
        if (resolver != null) {
            return resolver.getContext(type);
        }
    }
    return null;
}

Но по крайней мере я не могу получить доступ к https://jersey.dev.java.net/issues/show_bug.cgi?id=288, поэтому я не знаю, что это за ошибка.

Но я нашел обходное решение (взломать, если так будет). Просто растяните JacksonJsonProvider с помощью соответствующей аннотации и верните свой ObjectMapper следующим образом:

@Provider
@Consumes(MediaType.APPLICATION_JSON) // NOTE: required to support "non-standard" JSON variants
@Produces(MediaType.APPLICATION_JSON)
public class JacksonHackProvider extends JacksonJsonProvider {
    @Override
    protected ObjectMapper _locateMapperViaProvider(Class<?> type, MediaType mediaType) {
        return new MyCustomObjectMapper();
    }
}

Нет необходимости делать что-либо, чтобы он зарегистрировался (проверьте с помощью журнала, он зарегистрируется при первом доступе к службе json rest). Это сейчас работает для меня, а не изящно, но я сдался.

РЕДАКТИРОВАТЬ: используйте с осторожностью - Im, испытывающий ошибку, возможно, связанную с этим взломом: Android-залп не может отправить запрос POST/PUT с телом запроса, всегда получая 400 из фреймворка, я буду исследовать и сообщать о моих выводах.

EDIT2: этот хак действительно отвечал за общий 400, когда приложение Android с волейболом и клиентом OKHTTP пытались выполнить запрос POST или PUT, поэтому не используйте его - в моем тестовом джерси 2.6, похоже, исправлено это поэтому вы можете использовать подход @Provide

Ответ 2

К сожалению, каждый делает это намного сложнее, чем нужно. Команда Джерси в своей мудрости решила интегрировать Jackson 1.9, поэтому их материал не поможет вам.

Но это было довольно легко для меня. Просто сделайте следующее:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.3.0</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-json-provider</artifactId>
        <version>2.3.0</version>
    </dependency>

Теперь ПОЛУЧИТЕ ЭТО:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.4.1</version>
</dependency>

Затем в вашем web.xml измените эту строку:

<param-value>ie.cit.nimbus.sample</param-value>

Быть:

<param-value>ie.cit.nimbus.sample,com.fasterxml.jackson.jaxrs.json</param-value>

Это должно сделать это.

Ответ 3

Используя Jersey 2.13, вы можете заставить @Provider использовать тот же ObjectMapper, должен только создать один ObjectMapper:

package com.example.api.environment.configs;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

@Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {

  private final ObjectMapper objectMapper;

  public JacksonConfig() {
    objectMapper = new ObjectMapper()
          .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
          .registerModule(new JodaModule());
  };

  @Override
  public ObjectMapper getContext(Class<?> type) {
    return objectMapper;
  }
}

Я использую это для @Autowire в своем ObjectMapper для генерации json-schema документов на лету.

Ответ 4

Существует множество способов интеграции Jackson с реализацией Jax-rs Jersey.

Если вы посмотрите на учебник Mkyong: http://www.mkyong.com/webservices/jax-rs/json-example-with-jersey-jackson/ Кажется, что вы также должны передать "POJOMappingFeature" → true в параметрах init в web.xml. Я думаю, что это работает для Jersey 1.8

Если вы посмотрите на официальную документацию Джерси: https://jersey.java.net/nonav/documentation/latest/user-guide.html#json.jackson Кажется, что вы должны внедрить поставщика Jax-rs и добавить этого провайдера в свои ресурсы приложения.

@Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper>

Они предоставляют вам пример того, как это сделать https://github.com/jersey/jersey/blob/2.4.1/examples/json-jackson/src/main/java/org/glassfish/jersey/examples/jackson/MyObjectMapperProvider.java

Я использовал этот способ, и это решило мои проблемы, а аннотации Джексона правильно проверены поставщиком Jackson.


Отключить тему Я предлагаю вам использовать этот синтаксис в bean для инициализации карты:

import static java.util.Arrays.asList;

public static class Foobar {
        @JsonIgnore
        private String foo = "foo";
        private String baa = "baa";
        private Map<String, List<? extends Number>> map = new HashMap<>(){{
            put("even", asList(2, 4, 6, 8, 10));
            put("odd", asList(1, 3, 5, 7, 9));
            put("float", asList(1.1F, 2.2F, 3.3F));
        }};

        public Foobar() {
        }

        public String getFoo() {
            return foo;
        }

        public void setFoo(String foo) {
            this.foo = foo;
        }

        public String getBaa() {
            return baa;
        }

        public void setBaa(String baa) {
            this.baa = baa;
        }

        @JsonAnyGetter
        public Map<String, List<? extends Number>> getMap() {
            return map;
        }

        public void setMap(Map<String, List<? extends Number>> map) {
            this.map = map;
        }
    }

Ответ 5

добавить в каждый класс модели, как этот

@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Event {
  -----
  --
}

он работал у меня, но я использовал javascript javascript