杰克逊:将纪元反序列化为 LocalDate

Posted

技术标签:

【中文标题】杰克逊:将纪元反序列化为 LocalDate【英文标题】:Jackson: deserialize epoch to LocalDate 【发布时间】:2017-11-09 09:29:09 【问题描述】:

我有以下 JSON:


      "id" : "1",
      "birthday" : 401280850089

还有 POJO 类:

public class FbProfile 
    long id;
    @JsonDeserialize(using = LocalDateDeserializer.class)
    LocalDate birthday;

我正在使用 Jackson 进行反序列化:

public FbProfile loadFbProfile(File file) throws JsonParseException, JsonMappingException, IOException 
    ObjectMapper mapper = new ObjectMapper();
    FbProfile profile = mapper.readValue(file, FbProfile.class);
    return profile;

但它会抛出异常:

com.fasterxml.jackson.databind.JsonMappingException:意外的令牌 (VALUE_NUMBER_INT),预期 VALUE_STRING:预期数组或字符串。

如何将纪元反序列化为LocalDate?我想补充一点,如果我将数据类型从 LocalDate 更改为 java.util.Date 它工作得很好。所以也许最好反序列化为java.util.Date 并创建getter 和setter,它们将执行与LocalDate 之间的转换。

【问题讨论】:

请注意,将毫秒转换为日期取决于时区。例如,您示例中的数字等于 1982-09-19T10:54:10.089Z,而后者又等于 1982-09-18T23:54:10.089-11:00[Pacific/Midway]。 避免使用老式的Date 类。它给你的唯一一件事是现代课程没有给你带来麻烦。如果您想尝试LocalDate 以外的其他课程,Instant 是显而易见的选择。 您在询问之前是否进行了搜索?我想你可能会在this question: Java 8 LocalDate Jackson format 中找到一些灵感。 @Ole V.V.是的,我搜索过。我找到了注释@JsonDeserialize(using = LocalDateDeserializer.class),但它不适用于时代。不过还是谢谢你的提示。 【参考方案1】:

如果您有能力更改 POJO,我选择的另一个选择是将您的字段声明为 java.time.Instant

public class FbProfile 
    long id;
    Instant birthday;

这将从包括 epoch 在内的多种不同格式反序列化。然后,如果您需要将其用作 LocalDate 或业务逻辑中的其他内容,只需执行上述某些转换器正在执行的操作即可:

LocalDate asDate = birthday.atZone(ZoneId.systemDefault()).toLocalDate()

LocalDateTime asDateTime = birthday.atZone(ZoneId.systemDefault()).toLocalDateTime()

【讨论】:

【参考方案2】:

我找到了一种无需编写自定义反序列化器的方法,但需要进行一些修改。

首先,LocalDateDeserializer 接受自定义 DateTimeFormatter。因此,我们需要创建一个接受纪元毫秒的格式化程序。我通过加入INSTANT_SECONSMILLI_OF_SECOND 字段来做到这一点:

// formatter that accepts an epoch millis value
DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    // epoch seconds
    .appendValue(ChronoField.INSTANT_SECONDS, 1, 19, SignStyle.NEVER)
    // milliseconds
    .appendValue(ChronoField.MILLI_OF_SECOND, 3)
    // create formatter, using UTC as timezone
    .toFormatter().withZone(ZoneOffset.UTC);

我还将格式化程序设置为 UTC 时区,因此它不会受到时区和 DST 更改的影响。

然后,我创建了反序列化器并在我的ObjectMapper 中注册:

ObjectMapper mapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
// add the LocalDateDeserializer with the custom formatter
module.addDeserializer(LocalDate.class, new LocalDateDeserializer(formatter));
mapper.registerModule(module);

我还不得不从birthday 字段中删除注释(因为注释似乎覆盖了模块配置):

public class FbProfile 
    long id;

    // remove @JsonDeserialize annotation
    LocalDate birthday;

现在最大的问题是:DateTimeFormatter 只接受 String 作为输入,并且 JSON 在 birthday 字段中包含一个数字,我不得不更改 JSON:


  "id" : "1",
  "birthday" : "401280850089"

请注意,我将 birthday 更改为 String(将值放在引号之间)。

这样,LocalDate 可以正确地从 JSON 中读取:

FbProfile value = mapper.readValue(json, FbProfile.class);
System.out.println(value.getBirthday()); // 1982-09-19

注意事项:

我找不到将数字直接传递给格式化程序的方法(因为它只需要String 作为输入),所以我不得不将数字更改为String。如果您不想这样做,那么无论如何您都必须编写一个自定义转换器。 您可以将ZoneOffset.UTC 替换为您想要的任何时区(甚至是ZoneId.systemDefault()),这取决于您的应用程序需要什么。但正如@Ole V.V.'s comment 中所说,时区可能会导致日期发生变化。

【讨论】:

很遗憾,我无法修改 JSON 文件的结构。不过还是谢谢你的回答。 经过深思熟虑和研究。【参考方案3】:

我已经设法编写了自己的反序列化器(感谢@Ole V.V. 将我指向帖子Java 8 LocalDate Jackson format):

public class LocalDateTimeFromEpochDeserializer extends StdDeserializer<LocalDateTime> 

    private static final long serialVersionUID = 1L;

    protected LocalDateTimeFromEpochDeserializer() 
        super(LocalDate.class);
    

    @Override
    public LocalDateTime deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException 
        return Instant.ofEpochMilli(jp.readValueAs(Long.class)).atZone(ZoneId.systemDefault()).toLocalDateTime();
    


关于时区的通知也非常有用。谢谢!

仍然悬而未决的问题是是否可以在不编写自己的反序列化器的情况下完成?

【讨论】:

这是一个猜测:我的猜测是,如果您坚持在自 Epoch 以来的 LocalDate 和毫秒之间进行序列化,您将需要自己的反序列化器。如果您可以用格式化的日期字符串 替换长毫秒,那么您会接受得到Instant 而不是LocalDate,我会更乐观地使用内置的反序列化。说几乎没有触及杰克逊的表面。很高兴你找到了解决办法。

以上是关于杰克逊:将纪元反序列化为 LocalDate的主要内容,如果未能解决你的问题,请参考以下文章

杰克逊将单个项目反序列化为列表

杰克逊未能将字符串反序列化为 Joda-Time

Spring Boot LocalDate 字段序列化和反序列化

杰克逊:反序列化为每个值都具有正确类型的 Map<String, Object>

为日期反序列化设置杰克逊时区

Jackson 将日期字符串反序列化为 Long