web-dev-qa-db-fra.com

Comment désérialiser Joda DateTime avec Jackson avec Jersey 2 Client dans Spring MVC?

Je me cogne la tête avec cette preuve de concept depuis un moment. Je souhaite utiliser un noeud final REST qui renvoie la charge JSON avec un horodatage ISO8601 UTC:

{  ...
  "timestamp" : "2014-08-20T11:51:31.233Z" 
}

et je veux le consommer en utilisant un client Java en ligne de commande écrit en tant que client Jersey 2 avec Jackson/Spring Boot. Le POJO de marshalling est défini comme suit:

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_NULL)
public class GreetingResource
{
  @JsonProperty("timestamp")
  private DateTime date;

  ...
}

Après avoir suivi les recommandations indiquées dans: 

https://jersey.Java.net/documentation/latest/user-guide.html#json.jackson

et en utilisant les dépendances Gradle suivantes:

dependencies {
    compile("org.springframework.boot:spring-boot-starter")
    compile("org.springframework.boot:spring-boot-starter-logging")
    compile("joda-time:joda-time")
    compile("com.fasterxml.jackson.core:jackson-core")
    compile("com.fasterxml.jackson.core:jackson-annotations")
    compile("com.fasterxml.jackson.core:jackson-databind")
    compile("com.fasterxml.jackson.datatype:jackson-datatype-joda")
    compile("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.3.3")
    compile("org.Apache.commons:commons-lang3:3.3.2")
    compile("org.glassfish.jersey.core:jersey-client:2.2")
    compile("org.glassfish.jersey.media:jersey-media-json-jackson:2.2")
    testCompile("org.springframework.boot:spring-boot-starter-test")
}

J'ai toujours cette erreur:

Exception in thread "main" javax.ws.rs.ProcessingException: Error reading entity from input stream.
  at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.Java:849)
  at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.Java:768)
  at org.glassfish.jersey.client.InboundJaxrsResponse.readEntity(InboundJaxrsResponse.Java:96)
  at org.glassfish.jersey.client.ScopedJaxrsResponse.access$001(ScopedJaxrsResponse.Java:56)
  at org.glassfish.jersey.client.ScopedJaxrsResponse$1.call(ScopedJaxrsResponse.Java:77)
  at org.glassfish.jersey.internal.Errors.process(Errors.Java:315)
  at org.glassfish.jersey.internal.Errors.process(Errors.Java:297)
  at org.glassfish.jersey.internal.Errors.process(Errors.Java:228)
  at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.Java:397)
  at org.glassfish.jersey.client.ScopedJaxrsResponse.readEntity(ScopedJaxrsResponse.Java:74)
  at client.GreetingClient.processResponse(GreetingClient.Java:62)
  at client.GreetingClient.performGet(GreetingClient.Java:53)
  at client.GreetingService.internalLoadGreeting(GreetingService.Java:44)
  at client.GreetingService.LoadGreeting(GreetingService.Java:27)
  at client.Application.main(Application.Java:25)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class org.joda.time.DateTime] from String value ('2014-08-20T12:19:36.358Z'); no single-String constructor/factory method (through reference chain: client.GreetingResource["timestamp"])
  at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator._createFromStringFallbacks(StdValueInstantiator.Java:428)
  at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.Java:299)
  at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.Java:1150)
  at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.Java:139)
  at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.Java:126)
  at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.Java:525)
  at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.Java:99)
  at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.Java:242)
  at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.Java:118)
  at com.fasterxml.jackson.databind.ObjectReader._bind(ObjectReader.Java:1233)
  at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.Java:677)
  at com.fasterxml.jackson.jaxrs.base.ProviderBase.readFrom(ProviderBase.Java:777)
  at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.aroundReadFrom(ReaderInterceptorExecutor.Java:188)
  at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.Java:134)

J'ai abandonné. Qu'est-ce que je fais mal? 

Le client est configuré comme suit: 

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.client.WebTarget;
import org.glassfish.jersey.client.ClientConfig;

...

@Component
public class GreetingClient
{

  private final WebTarget serviceWebTarget;

  {
    ClientConfig config = new ClientConfig();
    config.register(MyObjectMapperProvider.class);
    config.register(JacksonFeatures.class);
    Client client = ClientBuilder.newClient(config);
    this.serviceWebTarget = client.target("http://myserver:8080");
  }

  ...  

}

Le fournisseur enregistré est défini comme (dans le même package que le client):

@Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper>
{
  private final static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");


  @Override
  public ObjectMapper getContext(Class<?> type)
  {
    final ObjectMapper result = new JodaMapper();
    result.setDateFormat(dateFormat);
    return result;
  }

}

J'ai essayé avec/sans enregistrer le fournisseur et annoter le champ pour utiliser DateTimeDeserializer (via @JsonDeserialize) - uniquement pour obtenir une erreur car "il n'y a pas de constructeur par défaut sans argument disponible". 

Si j’utilise plutôt Java.util.Date standard avec 

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")

cela fonctionne comme une brise.

Des indices? Merci de votre aide.

19
Reynaldo

Le test ci-dessous fonctionne bien:

import Java.io.IOException;

import org.joda.time.DateTime;
import org.junit.Test;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.joda.JodaModule;

public class JacksonTest {

    private static final String json = "{ \n" +
            "  \"timestamp\" : \"2014-08-20T11:51:31.233Z\" \n" +
            "}";

    @Test
    public void test() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JodaModule());

        System.out.println(mapper.readValue(json, GreetingResource.class));
    }
}

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_NULL)
class GreetingResource {
    @JsonProperty("timestamp")
    private DateTime date;

    public DateTime getDate() {
        return date;
    }

    public void setDate(DateTime date) {
        this.date = date;
    }

    @Override
    public String toString() {
        return "GreetingResource{" +
                "date=" + date +
                '}';
    }
}

Configuration Maven:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.4.1.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.4.1.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.4.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-joda</artifactId>
    <version>2.4.0</version>
</dependency>
<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.4</version>
</dependency>
12
Michał Ziober

Pour quiconque ayant des problèmes, aucune des autres réponses n’était vraiment complète, donc

WebTarget target = ClientBuilder.newClient().target(Host);

JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();

provider.setMapper(new JodaMapper());

target.register(provider);
6
devshorts

J'ai rencontré le même problème. Ce que vous devez faire est d’enregistrer un ObjectMapper personnalisé dans Jersey:

@Component
public class JerseyConfig extends ResourceConfig {

    public JerseyConfig() {
        JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
        provider.setMapper(new JodaMapper());

        register(provider);
        packages("endpoints.package.names");
    }
}
4
Piotr Glazar

Piotor Glazar fonctionne dans un sens mais pas dans l'autre. (Il écrira la date sous forme d'horodatage, plutôt qu'ISO8601 UTC.)

Le seul changement de sa solution consiste à désactiver SerializationFeature.WRITE_DATES_AS_TIMESTAMPS.

Voici la solution qui a fonctionné pour moi. 

public class JerseyConfig extends ResourceConfig {

    public JerseyConfig() {
        JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
        JodaMapper jodaMapper = new JodaMapper();
        jodaMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        provider.setMapper(jodaMapper);

        register(provider);
    }
}
1
Bojan Petkovic

Si vous souhaitez obtenir la même chose dans le projet J2EE pour le client Jersy, veuillez vous reporter au code ci-dessous.

    final Client client = ClientBuilder.newClient();
    final WebTarget target = client.target(URI);

    final JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
    final JodaMapper jodaMapper = new JodaMapper();
    jodaMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

    //while sending date to server.
    //Use a consistent format to send to server
    final DateFormat format = new SimpleDateFormat(DATE_TIME_FORMAT);
    jodaMapper.setDateFormat(format);

    //receiving date from server. [Deserialize]
    //expect any ISO format or any other format and make sure we handle it.
    final SimpleModule module = new SimpleModule();
    module.addDeserializer(Date.class, new CustumDateDeserializer());
    jodaMapper.registerModule(module);

    provider.setMapper(jodaMapper);

    target.register(provider);

Et le DeSerialiser Date est ci-dessous,

private class CustumDateDeserializer extends JsonDeserializer<Date> {

    @Override
    public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {

        final String stringDate = jp.getText();
        Date parsedDate = null;

        if(stringDate == null || stringDate.trim().isEmpty()) {
            return parsedDate;
        }

        try{
            final DateTimeFormatter parser = ISODateTimeFormat.dateTime();
            parsedDate = parser.parseDateTime(stringDate).toDate();
        }catch(IllegalArgumentException e) {
            //use the default parser if the given date is not iso-format.
            parsedDate = new DateDeserializers.DateDeserializer().deserialize(jp, ctxt);
        }

        return parsedDate;
    }
}

J'espère que ceci vous aidera.

0
Lyju I Edwinson