Регистрация JacksonJsonProvider с ObjectMapper + JavaTimeModule для клиента Jersey 2

Я пытаюсь упорядочить ответ, содержащий метку времени в формате ISO, например:

{
...
    "time" : "2014-07-02T04:00:00.000000Z"
...
}

в ZonedDateTime поле в моей модели предметной области. В конце концов это работает, если я использую решение, которое прокомментировано в следующем фрагменте. Есть много похожих вопросов по SO, но я хотел бы получить конкретный ответ, что не так с другим подходом, который использует JacksonJsonProvider с ObjectMapper + JavaTimeModule?

        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        JacksonJsonProvider provider = new JacksonJsonProvider(mapper);
        Client client = ClientBuilder.newBuilder()
//                .register(new ObjectMapperContextResolver(){
//                    @Override
//                    public ObjectMapper getContext(Class<?> type) {
//                        ObjectMapper mapper = new ObjectMapper();
//                        mapper.registerModule(new JavaTimeModule());
//                        return mapper;
//                    }
//                })
                .register(provider)
                .register(JacksonFeature.class)
                .build();

Ошибка, которую я получаю:

javax.ws.rs.client.ResponseProcessingException: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.time.ZonedDateTime: no String-argument constructor/factory method to deserialize from String value ('2017-02-24T20:46:05.000000Z')
 at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream@53941c2f

Зависимости проекта:

compile 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.8.7'
compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.7'
compile 'com.fasterxml.jackson.core:jackson-core:2.8.7'
compile 'com.fasterxml.jackson.core:jackson-databind:2.8.7'
compile 'com.fasterxml.jackson.core:jackson-annotations:2.8.7'
compile 'org.glassfish.jersey.core:jersey-client:2.25.1'

редактировать

Десериализация происходит здесь:

CandlesResponse<BidAskCandle> candlesResponse = webTarget.request()
                .header(HttpHeaders.AUTHORIZATION,"Bearer "+token)
                .accept(MediaType.APPLICATION_JSON)
                .get(new GenericType<CandlesResponse<BidAskCandle>>(){});

3 ответа

Решение

В конце концов это работает, если я использую решение, которое прокомментировано в следующем фрагменте.

Прежде всего, вам не хватает зависимости в вашем списке, которая у вас также есть, что является проблемой.

jersey-media-json-jackson

Этот модуль зависит от собственного модуля Джексона, который имеет JacksonJsonProvider, Когда вы регистрируете JacksonFeature (что идет с jersey-media-json-jackson), он регистрирует свой собственный JacksonJaxbJsonProvider, который, кажется, имеет приоритет над любым, что вы предоставляете.

Когда вы используете ContextResolver, JacksonJsonProvider на самом деле выглядит, что ContextResolver и использует его для разрешения ObjectMapper, Вот почему это работает. Использовали ли вы JacksonFeature или зарегистрировал свой JacksonJsonProvider (без настройки ObjectMapper за это) ContextResovler должно сработать.

Еще одна вещь о jersey-media-json-jackson модуль, он участвует в механизме автоматического обнаружения Джерси, который регистрирует его JacksonFeature, Поэтому, даже если вы не зарегистрировали его явно, он все равно будет зарегистрирован. Единственные способы избежать его регистрации:

  1. Отключите авто-обнаружение (как упомянуто в предыдущей ссылке)
  2. Не используйте jersey-media-json-jackson, Просто используйте родной модуль Джексона jackson-jaxrs-json-provider, Дело в том, что это то, что jersey-media-json-jackson добавляет пару функций поверх нативного модуля, так что вы их потеряете.
  3. Не проверял, но кажется, что если вы используете JacksonJaxbJsonProvider вместо JacksonJsonProviderэто может сработать. Если вы посмотрите на источник дляJacksonFeature, вы увидите, что он проверяет уже зарегистрированный JacksonJaxbJsonProvider, Если он есть, он не будет регистрировать свой собственный.

    Единственное, в чем я не уверен, это автоматическое обнаружение. Порядок, в котором он зарегистрирован, если это повлияет на то, поймет ли он ваш зарегистрированный JacksonJaxbJsonProvider, То, что вы можете проверить.

Из моего любимого проекта:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>${jackson.version}</version>
</dependency>

public WebTarget getTarget(URI uri) {
    Client client = ClientBuilder
            .newClient()
            .register(JacksonConfig.class);
    return client.target(uri);
}

где

@Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {

    private final ObjectMapper objectMapper;

    public JacksonConfig() {
        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }

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

Конфигурация Джексона выглядит отлично, я попробовал следующее и смог десериализовать значение:

public class Test {

    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        Model model = mapper.readValue("{\"time\" : \"2014-07-02T04:00:00.000000Z\"}", Model.class);
        System.out.println(model.getTime());
    }

}

class Model{
    private ZonedDateTime time;

    public ZonedDateTime getTime() {
        return time;
    }
    public void setTime(ZonedDateTime time) {
        this.time = time;
    }
}

Я могу воспроизвести это, комментируя mapper.registerModule(new JavaTimeModule());, Итак, похоже, клиент-джерси не использует кастом mapper пример. Не могли бы вы попытаться настроить его, как описано здесь.

Другие вопросы по тегам