Jackson 使用 Java 8 将 elasticsearch 反序列化为 LocalDateTime [英] Jackson deserialize elasticsearch long as LocalDateTime with Java 8
问题描述
我们在 elasticsearch 索引中有一个用 long
填充的日期字段.
We have a date field being populated with a long
in elasticsearch index.
字段映射为:
@Field(type = FieldType.Date)
@JsonFormat(shape = JsonFormat.Shape.NUMBER_INT)
private LocalDateTime created;
我使用 Jackson
JavaTimeModule
和 Jdk8Module
和这个配置:
And I use Jackson
JavaTimeModule
and Jdk8Module
with this configuration:
@Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchRestTemplate(client(), new CustomEntityMapper());
}
public static class CustomEntityMapper implements EntityMapper {
private final ObjectMapper objectMapper;
public CustomEntityMapper() {
//we use this so that Elasticsearch understands LocalDate and LocalDateTime objects
objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false)
//MUST be registered BEFORE calling findAndRegisterModules
.registerModule(new JavaTimeModule())
.registerModule(new Jdk8Module());
//only autodetect fields and ignore getters and setters for nonexistent fields when serializing/deserializing
objectMapper.setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
//load the other available modules as well
objectMapper.findAndRegisterModules();
}
@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);
}
}
但是当我尝试使用以下字段解析索引中的实体时:
But when I try to parse an entity in the index with a field such as:
"created" : 1563448935000
我收到一个错误:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (VALUE_NUMBER_INT), expected VALUE_STRING: Expected array or string.
我认为,可以将 long
反序列化为日期,但我看不出我遗漏了什么.
I think, it is possible to deserialize a long
to a date, but I don't see what I am missing.
如果我将它映射到 Long
它当然可以工作,如果值存储为 String
并且我们在 @JsonFormat<中正确地对其进行整形和格式化代码>.但是是否也可以使用
long->LocalDateTime
?
If I map it to Long
it works of course and same if the value is stored as String
and we shape it and format properly in @JsonFormat
. But is it possible to have long->LocalDateTime
as well?
推荐答案
要从 1970-01-01T00:00:00Z
纪元开始,以毫秒为单位构建 LocalDateTime
我们需要一个时区.在版本 2.9.9 中,当毫秒出现:
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:
原始时间戳 (1563448935000) 不允许java.time.LocalDateTime
:需要额外的信息,例如偏移量或时区(参见 Javadocs 类)
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
.在您的情况下,您可以提供您的或使用系统默认值.示例用法:
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
}
以上代码打印:
Created{created=2019-07-18T11:22:15}
使用 Jackson 2.9.0
,因为 this 问题提供的代码将不会被调用,因为在注册自定义模块后调用的 findAndRegisterModules
将覆盖它.删除该调用将使整个场景工作.如果以上对您的版本不起作用,您需要调试默认实现并找到原因.
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.
这篇关于Jackson 使用 Java 8 将 elasticsearch 反序列化为 LocalDateTime的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!