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.
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>
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);
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");
}
}
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);
}
}
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.