【问题标题】:Jackson deserialize elasticsearch long as LocalDateTime with Java 8Jackson 使用 Java 8 反序列化 elasticsearch long as LocalDateTime
【发布时间】:2019-11-27 15:23:26
【问题描述】:

我们在 elasticsearch 索引中有一个日期字段填充有 long

字段映射为:

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

我使用 Jackson JavaTimeModuleJdk8Module 进行此配置:

@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 反序列化为日期,但我看不出我缺少什么。

如果我将它映射到Long,它当然可以工作,如果值存储为String 并且我们在@JsonFormat 中正确地对其进行整形和格式化,那么它当然可以工作。但是long-&gt;LocalDateTime也可以吗?

【问题讨论】:

    标签: java spring-boot elasticsearch jackson jackson-databind


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      要从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 在注册自定义模块后调用将覆盖它。删除该调用将使整个场景工作。如果上述方法不适用于您的版本,则需要调试默认实现并查找原因。

      【讨论】:

      • 你好米哈尔,谢谢你的回答。是的,我想知道是否可以避免编写自己的反序列化器。我们在我们的应用程序中的任何地方都使用 UTC,并且根据docs 的 Elasticsearch 在内部使用 UTC,我想知道我是否可以将其他参数传递给 Jackson 本身,也许在未指定时使其使用 UTC 或使用自定义反序列化器的唯一方法?考虑我们在 Elasticsearch 上将该字段声明为 Date,因此可以在其中放入任何内容
      • @grog,要回答这个问题,您需要查看您使用的Jackson 版本的源代码。当我开始写答案时,我正在考虑以某种方式对其进行配置,但最后我不得不看看实现以及什么是可能的。 JavaTimeModule deserialiser 的最新版本告诉我们我们只能覆盖它并在新类中实现一些自定义。待续……
      • @grog,没有选项可以设置时区并从此反序列化器中使用它。您当然可以实现这一点并创建一个具有此类配置可能性的新JavaTimeModule,但这需要时间。如果您认为它对您来说是值得的,因为您将在许多项目中使用它,我认为值得尝试。
      • 谢谢 Michal,我现在明白了。然后我将不得不使用我自己的反序列化器或更改我们存储数据的方式。祝你有美好的一天
      • 顺便说一句,我编辑了你的答案:这个答案是正确的并且完全可以独立工作,我只是指出在所描述的特定场景中(使用 Jackson 2.9.0),因为this 提供的代码将不会被调用,因为在注册自定义模块后调用的findAndRegisterModules 将覆盖它。删除该调用将使整个场景工作。但它需要经过同行评审。如果您可以请自己更新以反映这一点,那就太好了。再次感谢和欢呼
      猜你喜欢
      • 1970-01-01
      • 2021-01-19
      • 2017-03-12
      • 2023-04-04
      • 1970-01-01
      • 2015-06-16
      • 1970-01-01
      • 2018-08-14
      • 1970-01-01
      相关资源
      最近更新 更多