jsonproperty - ¿Cómo deserializar Joda DateTime usando Jackson con el cliente Jersey 2 en Spring MVC?
jsonformat java (5)
He estado golpeando mi cabeza con esta prueba de concepto por un tiempo. Quiero consumir un punto final REST que devuelve la carga JSON con una marca de tiempo UTC ISO8601:
{ ...
"timestamp" : "2014-08-20T11:51:31.233Z"
}
y quiero consumirlo utilizando un cliente Java de línea de comandos escrito como un cliente Jersey 2 con Jackson / Spring Boot. El orden de clasificación se define de la siguiente manera:
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_NULL)
public class GreetingResource
{
@JsonProperty("timestamp")
private DateTime date;
...
}
Después de las siguientes recomendaciones se muestran en:
https://jersey.java.net/documentation/latest/user-guide.html#json.jackson
y utilizando las siguientes dependencias de Gradle:
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")
}
Siempre me sale este error:
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)
Me he rendido. ¿Qué estoy haciendo mal?
El cliente se configura de la siguiente manera:
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");
}
...
}
El proveedor registrado se define como (en el mismo paquete que el cliente):
@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;
}
}
He intentado con / sin registrar el proveedor y también anotar el campo para usar DateTimeDeserializer (a través de @JsonDeserialize), solo para obtener un error porque "no hay un constructor predeterminado sin argumentos disponible".
Si utilizo el estándar java.util.Date en lugar de
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd''T''HH:mm:ss.SSS''Z''")
funciona como una brisa
¿Alguna pista? Gracias por tu ayuda.
A continuación la prueba funciona 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 +
''}'';
}
}
Configuración de 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>
Me he encontrado con el mismo problema. Lo que tienes que hacer es registrar ObjectMapper personalizado en Jersey:
@Component
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
provider.setMapper(new JodaMapper());
register(provider);
packages("endpoints.package.names");
}
}
Para cualquier otra persona que tenga problemas, ninguna de las otras respuestas fue realmente completa, así que es esto
WebTarget target = ClientBuilder.newClient().target(host);
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
provider.setMapper(new JodaMapper());
target.register(provider);
Piotor Glazar trabaja de una manera pero no de la otra. (Escribirá la fecha como una marca de tiempo, en lugar de ISO8601 UTC).
El único cambio de su solución es deshabilitar SerializationFeature.WRITE_DATES_AS_TIMESTAMPS.
Aquí está la solución que funcionó para mí.
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 desea lograr lo mismo en el proyecto J2EE para el cliente Jersy, consulte el código a continuación.
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);
Y el Date DeSerialiser está abajo,
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;
}
}
Espero que esto te ayudará.