Search code examples
restjacksonjax-rswildflyfasterxml

com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDateTime] from String


I have Java8 LocalDateTime in my Jax-RS REST APIs. My webapp is deployed in wildfly10. When i make a POST call(which includes LocalDateTime as parameter) i get following exception;

Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDateTime] from String value ('2016-06-02T00:08:25.605Z'); no single-String constructor/factory method
 at [Source: io.undertow.servlet.spec.ServletInputStreamImpl@396d1714; line: 2, column: 3] (through reference chain: com.leightonobrien.core.model.base.Company["created"])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
    at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:843)
    at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:277)
    at com.fasterxml.jackson.databind.deser.std.StdValueInstantiator.createFromString(StdValueInstantiator.java:284)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1150)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:153)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:144)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:523)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:101)
    at com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap.findDeserializeAndSet(BeanPropertyMap.java:285)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:248)

Based on following guides ;

Wildfly throws "Unable to find a constructor that takes a String param or a valueOf() or fromString() method for javax.ws.rs.QueryParam" error

and

jaxrs could not find my custom (de)serializers for joda.money type

and

https://github.com/FasterXML/jackson-datatype-jsr310

I have written my provider and registered in the application path;

package com.test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.Provider;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;

@Provider
public class LocalDateTimeConverterProvider extends JacksonJsonProvider implements ParamConverterProvider {

    private final LocalDateTimeConverter converter = new LocalDateTimeConverter();

    @Override
    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
        if (!rawType.equals(LocalDateTime.class))
            return null;
        return (ParamConverter<T>) converter;
    }

    public class LocalDateTimeConverter implements ParamConverter<LocalDateTime> {

        @Override
        public LocalDateTime fromString(String value) {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
            LocalDateTime dateTime = LocalDateTime.parse(value, formatter);
            return dateTime;
        }

        @Override
        public String toString(LocalDateTime value) {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
            String formattedDateTime = value.format(formatter);
            return formattedDateTime;
        }

    }

    public LocalDateTimeConverterProvider() {

        ObjectMapper mapper = new ObjectMapper();

        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        mapper.registerModule(new JavaTimeModule());

        setMapper(mapper);
    }

}



import javax.ws.rs.ApplicationPath;

@ApplicationPath("/rest")
public class RestApplication extends Application {
@Override
    public Set<Class<?>> getClasses() {
        HashSet<Class<?>> set = new HashSet<Class<?>>();
        set.add(com.test.JsonMoneyProvider.class);
        set.add(com.test.DurtaionConverterProvider.class);
        set.add(com.test.LocalDateTimeConverterProvider.class);
        set.add(com.test.MoneyConverterProvider.class);
...

I make POST call like;

 curl -X POST --header 'Content-Type: application/json' -d '{
  "created": "2016-06-02T00:08:25.605Z",
  "updated": "2016-06-02T00:08:25.605Z",
  "id": 0,
  "code": "string",
  "name": "string",
  "abn": "string",
  "addresses": [
    {
      "id": 0,
      "address1": "string",
      "address2": "string",
      "city": "string",
      "state": "string",
      "postcode": "string",
      "country": "string",
      "gps": {
        "latitude": {
          "latitude": 0,
          "value": 0
        },
        "longitude": {
          "longitude": 0,
          "value": 0
}' 'http://localhost:8080/test2dbwar/rest/Companys'

How can I overcome above issue? Now I'm clueless..I tried to put all stuff (try to avoid wildfly's jax-rs complex support issue, jackson serialization issue) and sort out the issue..

Any help?


Solution

  • The first question you linked to involves conversion of @XxxParam annotations. This is what the ParamConverterProvider is for. This is a completely different (de)serailization process from entity body (de)serialization.

    For entity (de)serialization, MessageBodyReader/MessageBodyWriters are used. Jackson provides one such implementation, in its JacksonJsonProvider/JacksonJaxbJsonProvider, which you are currently using, whether you know it or not. So the configuration for the LocalDataTime needs to somehow be configured with that provider.

    The only way to configure Jackson support for the LocalDateTime serialization, is through it's ObjectMapper. You can create a custom Json(De)Serializer, as mentioned in this post (Option two), or you can use the JSR310Module (that already has custom Json(De)Serializer for LocalDateTime), mentioned in this post.

    To actually configure the JacksonJsonProvider/JacksonJaxbJsonProvider to use your configured ObjectMapper, you can use a ContextResolver, as mentioned in both of the previous links, or you can construct the JacksonJsonProvider with the ObjectMapper, and register that provider

    ObjectMapper mapper = new ObjectMapper();
    mapper..configurModuleOrSerializer
    JacksonJsonProvider provider = new JacksonJsonProvider(mapper);
    registerProvier(provider)
    

    Personally I would just go with the ContextResolver, as mentioned in the links. The difference is that with the above code, the ObjectMapper is provided to the provider explicitly, while with the ContextResolver, during runtime, the provider will search the JAX-RS registry for a ContextResolver of type ObjectMapper, and then obtain it that way.