Jackson使用Java 8将elasticsearch反序列化为LocalDateTime

wn9m85ua  于 2023-04-19  发布在  Java
关注(0)|答案(3)|浏览(206)

我们在elasticsearch索引中使用long填充了一个日期字段。
字段Map为:

@Field(type = FieldType.Date)
@JsonFormat(shape = JsonFormat.Shape.NUMBER_INT)
private LocalDateTime created;

我使用JacksonJavaTimeModuleJdk8Module,配置如下:

@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);
        }
}

但是当我试图解析索引中的实体时,字段如下:

"created" : 1563448935000

我得到一个错误:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (VALUE_NUMBER_INT), expected VALUE_STRING: Expected array or string.

我认为,可以将long反序列化为日期,但我不知道我错过了什么。
如果我把它Map到Long,它当然可以工作,如果值存储为String,我们在@JsonFormat中正确地塑造和格式化它,它也可以工作。

8fq7wneg

8fq7wneg1#

要从1970-01-01T00:00:00Z的历元开始以毫秒构建LocalDateTime,我们需要一个时区。在2.9.9版本中,当毫秒出现时,它会抛出异常:
原始时间戳(1563448935000)不允许用于java.time.LocalDateTime:需要额外的信息,如偏移量或时区(请参见Javadocs类)
但是我们可以实现我们的反序列化器,它将尝试使用默认时区来执行此操作。示例实现如下所示:

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。在您的情况下,您可以提供您的或使用系统默认值。示例用法:

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将覆盖它。删除该调用将使整个场景正常工作。如果以上不适用于您的版本,您需要调试默认实现并找到原因。

l7mqbcuq

l7mqbcuq2#

使用Instant作为日期的Jackson字段类型。这简化了一切!您只需要注册模块:https://github.com/FasterXML/jackson-modules-java8

gz5pxeao

gz5pxeao3#

我想将一个时间戳反序列化为一个Long到一个Instant,并这样做:

@JsonDeserialize(using = InstantWrapperDeserializer.class)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssZ", timezone = "Europe/Stockholm")
protected Instant timestamp;

public class InstantWrapperDeserializer extends JsonDeserializer<Instant> {
@Override
public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
    long millis = p.getValueAsLong(); 
    return Instant.ofEpochMilli(millis); 
}
}

class InstantWrapperDeserializerTest {
private InstantWrapperDeserializer deserializer;

private JsonParser parser;
private DeserializationContext ctxt;

@BeforeEach
public void setup() {
    deserializer = new InstantWrapperDeserializer();
    parser = mock(JsonParser.class);
    ctxt = mock(DeserializationContext.class);
}

@Test
void testDeserialize() throws IOException {
    // Given
    long millis = 1681758192323L;
    when(parser.getValueAsLong()).thenReturn(millis);

    // When
    Instant result = deserializer.deserialize(parser, ctxt);

    // Then
    Instant expected = Instant.ofEpochMilli(millis);
    Assertions.assertThat(result).isEqualTo(expected);
}
}

相关问题