Cannot Construct Instance of 'Java.Time.Localdate' - Spring Boot, Elasticseach, Jackson

Jackson deserialize elasticsearch long as LocalDateTime with Java 8

To build LocalDateTime from milliseconds from the epoch of 1970-01-01T00:00:00Z we need a time zone. In version 2.9.9 it throws exception when milliseconds appears:

raw timestamp (1563448935000) not allowed for
java.time.LocalDateTime: need additional information such as an
offset or time-zone (see class Javadocs)

But we can implement our deserialiser which will try to do this with default time zone. Example implementation could look like below:

class MillisOrLocalDateTimeDeserializer extends LocalDateTimeDeserializer {

public MillisOrLocalDateTimeDeserializer() {
super(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}

@Override
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException {
if (parser.hasToken(JsonToken.VALUE_NUMBER_INT)) {
long value = parser.getValueAsLong();
Instant instant = Instant.ofEpochMilli(value);

return LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
}

return super.deserialize(parser, context);
}

}

ZoneOffset.UTC is used. In your case you can provide yours or use system default. Example usage:

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;

import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

public class JsonApp {

public static void main(String[] args) throws Exception {
JavaTimeModule javaTimeModule = new JavaTimeModule();
// override default
javaTimeModule.addDeserializer(LocalDateTime.class, new MillisOrLocalDateTimeDeserializer());

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(javaTimeModule);

String json = "{\"created\":1563448935000}";
System.out.println(mapper.readValue(json, Created.class));

}
}

class Created {

private LocalDateTime created;

// getters, setters, toString
}

Above code prints:

Created{created=2019-07-18T11:22:15}

EDIT: Using Jackson 2.9.0, because of this issue the code provided will not be invoked since findAndRegisterModules which is called AFTER registering the customized module will override it. Removing that call will make the full scenario work. If above will not work for your version, you need to debug default implementation and find a reason.

Using Instant, LocalDateTime and ZonedDateTime with Spring Boot and ElasticSearch

Managed to get it to work with Spring Boot 2.1.4 and Spring Data Jest. Here is what I did:

  1. Example domain object:

    @Document(indexName = "datetest")
    public class DateTest {

    @Id
    private String id;

    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSSZZ", timezone = "UTC")
    private Instant instant = Instant.now();

    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSSZZ")
    private ZonedDateTime zonedDateTime = ZonedDateTime.now();

    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSS")
    private LocalDateTime localDateTime = LocalDateTime.now();

    // getters/setters
    }
  2. The ElasticSearch/JEST config:

    @Configuration
    public class ESConfig {

    @Bean
    public EntityMapper getEntityMapper() {
    return new CustomEntityMapper();
    }

    @Bean
    @Primary
    public ElasticsearchOperations elasticsearchTemplate(final JestClient jestClient,
    final ElasticsearchConverter elasticsearchConverter,
    final SimpleElasticsearchMappingContext simpleElasticsearchMappingContext, EntityMapper mapper) {
    return new JestElasticsearchTemplate(jestClient, elasticsearchConverter,
    new DefaultJestResultsMapper(simpleElasticsearchMappingContext, mapper));
    }

    public class CustomEntityMapper implements EntityMapper {

    private final ObjectMapper objectMapper;

    public CustomEntityMapper() {
    objectMapper = new ObjectMapper();
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
    objectMapper.registerModule(new CustomGeoModule());
    objectMapper.registerModule(new JavaTimeModule());
    }

    @Override
    public String mapToString(Object object) throws IOException {
    return objectMapper.writeValueAsString(object);
    }

    @Override
    public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
    return objectMapper.readValue(source, clazz);
    }

    }
    }
  3. The results in ElasticSearch:

    Screenshot of results in ElasticSearch

Hope this helps.

Springframework Data Elasticsearch unable to create object of class java.time.ZonedDateTime

Change your pattern to use uuuu instead of yyyy; this is documented here, the change in Elasticsearch responsible for this: https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats

BTW, you don't need

httpHeaders.add("content-type", "application/json");

The @Json... annotations are not needed for Spring Data Elasticsearch.



Related Topics



Leave a reply



Submit