Comment désérialiser Joda DateTime en utilisant Jackson avec le client Jersey 2 en MVC printemps?

je me suis cogné la tête avec cette preuve de concept pendant un certain temps. Je veux consommer un point de terminaison REST que revient la charge utile JSON avec un ISO8601 UTC timestamp:

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

et je veux l'exploiter à l'aide d'une ligne de commande client Java écrit un Maillot 2 client avec Jackson/le Printemps de Démarrage. La gare de POJO est défini comme suit:

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

  ...
}

Après les recommandations suivantes indiqué dans:

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

et en utilisant la suite de Gradle dépendances:

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.access1(ScopedJaxrsResponse.java:56)
  at org.glassfish.jersey.client.ScopedJaxrsResponse.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é. 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 paquet 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 aussi Annoter le champ pour utiliser DateTimeDeserializer (via @jsondeserialize) - seulement pour obtenir une erreur car "il n'y a pas de constructeur no-arg disponible par défaut".

si j'utilise java standard.util.Date de la place avec

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

cela fonctionne comme une brise.

des indices? Merci pour votre aide.

18
demandé sur Reynaldo 2014-08-20 16:27:23

5 réponses

ci-Dessous le test 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 +
                '}';
    }
}

Maven de configuration:

<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>
11
répondu Michał Ziober 2014-08-20 20:17:08

pour toute autre personne ayant des problèmes, aucune des autres réponses n'était vraiment complète donc c'est cela

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

JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();

provider.setMapper(new JodaMapper());

target.register(provider);
6
répondu devshorts 2015-04-17 23:22:43

j'ai rencontré le même problème. Ce que vous devez faire est d'enregistrer custom ObjectMapper à Jersey:

@Component
public class JerseyConfig extends ResourceConfig {

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

        register(provider);
        packages("endpoints.package.names");
    }
}
3
répondu Piotr Glazar 2015-01-09 12:01:09

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

le seul changement par rapport à sa solution est de 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
répondu Bojan Petkovic 2015-10-23 19:32:06

si vous souhaitez obtenir le même résultat dans le projet J2EE pour le client Jersy, reportez-vous 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 la Date DeSerialiser 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 cela va vous aider.

0
répondu Lyju I Edwinson 2016-09-21 23:43:15