web-dev-qa-db-fra.com

Enregistrement de JacksonJsonProvider avec ObjectMapper + JavaTimeModule sur le client Jersey 2

J'essaie de rassembler une réponse contenant un horodatage au format ISO comme celui-ci:

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

dans le champ ZonedDateTime de mon objet de modèle de domaine. Finalement, ça marche si j'utilise une solution commentée dans l'extrait suivant. Il y a beaucoup de questions similaires sur SO mais je voudrais obtenir une réponse spécifique Qu'est-ce qui ne va pas avec une autre approche qui utilise JacksonJsonProvider avec 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();

Erreur je reçois:

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

Les dépendances du projet sont:

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'

modifier

La désérialisation se passe ici:

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

Finalement, cela fonctionne si j'utilise une solution qui est commentée dans l'extrait suivant.

Tout d’abord, il vous manque une dépendance dans votre liste, que vous avez également, ce qui est le problème.

jersey-media-json-jackson

Ce module dépend du module Jackson natif qui a la JacksonJsonProvider. Lorsque vous enregistrez la JacksonFeature (fournie avec jersey-media-json-jackson), elle enregistre sa propre JacksonJaxbJsonProvider, qui semble avoir la priorité sur toutes celles que vous fournissez.

Lorsque vous utilisez la variable ContextResolver, la variable JacksonJsonProvider recherche réellement cette variable ContextResolver et l'utilise pour résoudre la variable ObjectMapper. C'est pourquoi ça marche. Que vous utilisiez la variable JacksonFeature ou que vous enregistriez votre propre variable JacksonJsonProvider (sans configurer une variable ObjectMapper pour celle-ci), la variable ContextResovler fonctionnerait. 

Une autre chose à propos du module jersey-media-json-jackson, c’est qu’il participe au mécanisme auto-découvrable de Jersey, qui enregistre sa JacksonFeature. Donc, même si vous ne l'enregistriez pas explicitement, il le serait quand même. Les seuls moyens d'éviter son enregistrement sont les suivants:

  1. Désactiver la découverte automatique (comme mentionné dans le lien précédent )
  2. N'utilisez pas le jersey-media-json-jackson. Utilisez simplement le module natif de Jackson jackson-jaxrs-json-provider. La chose à ce sujet est que, le jersey-media-json-jackson ajoute quelques fonctionnalités au-dessus du module natif, de sorte que vous les perdriez.
  3. N'a pas été testé, mais il semble que si vous utilisez JacksonJaxbJsonProvider au lieu de JacksonJsonProvider, cela pourrait fonctionner. Si vous regardez la source pour la JacksonFeature , vous verrez qu’elle recherche une JacksonJaxbJsonProvider déjà enregistrée. S'il en existe un, il n'enregistrera pas le sien. 

    La seule chose dont je ne suis pas sûr, c'est l'auto-découvrable. L'ordre dans lequel il est enregistré, si cela aura une incidence sur le fait qu'il capture ou non votre inscrit JacksonJaxbJsonProvider. Quelque chose que vous pouvez tester.

4
Paul Samsotha

De mon projet animal de compagnie:

<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;
    }
}
1
Mircea Stanciu

La configuration de Jackson a l'air bien, j'ai essayé ce qui suit et j'ai pu désérialiser la valeur:

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;
    }
}

Je peux le reproduire en commentant mapper.registerModule(new JavaTimeModule());. Donc, il semble que le client jersey n'utilise pas l'instance personnalisée mapper. Pourriez-vous essayer de le configurer comme décrit ici .

1
Darshan Mehta