Ohhnews

分类导航

$ cd ..
Baeldung原文

如何修复Jackson JSON解析错误:无法构造java.time.LocalDate实例

#jackson#java#json#localdate#日期解析

1. 概述

在使用 Java 处理 JSON 时,Jackson 通常无需过多配置就能处理 Java 8 的日期和时间类型。然而,我们仍然会遇到一些问题,例如常见的反序列化解析错误之一:

JSON parse error: Can not construct instance of java.time.LocalDate:
no String-argument constructor/factory method to deserialize from String value

在较新的 Jackson 版本中,错误文本会以另一种形式出现:

Cannot deserialize value of type java.time.LocalDate from String

尽管措辞略有不同,但两种错误都表明 Jackson 无法将 JSON 值转换为 LocalDateLocalDateTime

在本教程中,我们将探讨这个 JSON 反序列化异常及其解决方法。首先,我们用一个示例来演示问题。随后,我们会逐一分析导致该问题的不同原因以及如何处理它们。

2. 理解问题

我们编写一个基本的 User 类:

$ java
public class User {
    private String name;
    private LocalDate dob;
    // getters and setters
}

现在,假设我们收到一段匹配的 JSON:

$ cat
{
  "name": "x",
  "dob": "2006-06-06"
}

Jackson 并没有成功反序列化这段 JSON,而是抛出类似下面的异常:

Can not construct instance of java.time.LocalDate:
no String-argument constructor/factory method to deserialize from String value

值得注意的是,JSON 数据看起来是有效的,因为日期已经遵循 LocalDate 所期望的 ISO-8601 格式。问题通常不在 JSON 本身,而在于 Jackson 的配置。事实上,根本原因可能是缺少 Java Time 模块、日期格式不受支持,或者为传入的 JSON 使用了错误的 Java 日期时间类型。

3. 验证 Jackson 版本

Jackson 各版本对 Java Time API 的支持发生了显著变化。在深入探讨具体技术问题和代码变更之前,我们不妨检查一下所使用的版本

在 Jackson 2.x 中,Java Time 支持由单独的 jackson-datatype-jsr310 模块提供。在 Jackson 3.x 中,该支持已内置于 jackson-databind,因此不需要单独注册模块

如果项目使用的是过时的 Jackson 版本,升级 Jackson 可能会在没有代码改动的情况下解决反序列化问题。对于 Jackson 2.x,我们还应该确认 Java Time 模块是否存在并已正确配置。

4. 确保 Java Time 支持

导致这个 JSON 错误的最常见原因是 Jackson 配置不支持 Java 8 日期和时间 API。

java.util.Date 等较旧的类型不同,LocalDateLocalDateTimeInstant 等类是通过 Jackson Java Time 模块提供的。

如果缺少该模块,Jackson 会尝试像实例化普通 Java 对象一样实例化 LocalDate。由于 LocalDate 没有默认构造函数或字符串构造函数,反序列化就会失败。

因此,假设我们决定使用 Jackson 2.x,让我们引入 Java Time 模块依赖:

$ xml
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.22.1</version>
</dependency>

如果手动创建 ObjectMapper,则应注册该模块:

$ java
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());

不过,Spring Boot 会在类路径中存在该模块时自动完成注册。

5. 匹配 JSON 日期格式

导致 JSON 反序列化错误的另一个常见原因是确切的日期格式。如果传入的 JSON 没有使用 LocalDateLocalDateTime 默认期望的 ISO-8601 格式,我们可能会看到错误。

例如,让我们看一段无法自动解析的 JSON 字符串:

$ cat
{
  "time": "10/20/2020 10:00:10 AM"
}

注册了 Java Time 模块后,Jackson 默认期望 LocalDateTime 字段使用 ISO-8601 日期时间格式:

2020-10-20T10:00:10

在这种情况下,当 JSON 数据使用其他格式时,字段需要指定期望的格式模式:

$ java
public class ImportTrans {
    @JsonFormat(pattern = "M/d/yyyy h:mm:ss a")
    private LocalDateTime time;
}

不过,这个例子还包含了另一个提示。JSON 数据实际上可以同时包含日期和时间,而 Java 的 LocalDate 字段只存储日期。

6. 使用正确的 Java 时间类型

LocalDate 只表示日历日期。特别是,它不包含任何一天中的时间信息。

如果 JSON 数据包含小时、分钟或秒,LocalDate 就不再是合适的目标类型

$ java
private LocalDate time;

相反,我们可能应该使用 LocalDateTime

$ java
private LocalDateTime time;

此外,可以通过 @JsonFormat 声明匹配的格式:

$ java
@JsonFormat(pattern = "M/d/yyyy h:mm:ss a")
private LocalDateTime time;

选择能够准确表示 JSON 载荷的 Java 类型往往是最简单的解决方案。

7. LocalDateTime 也有类似要求

使用 LocalDateTime 时,我们经常会遇到相同类型的异常:

Can not deserialize value of type
java.time.LocalDateTime from String

让我们考虑下面的 JSON:

$ cat
{
  "creationTime": "2016-06-16 06:56:00"
}

和之前一样,由于该格式不同于默认的 ISO-8601 表示,Jackson 需要显式指定格式模式:

$ java
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime creationTime;

ObjectMapper 还必须注册 Java Time 模块,但对于这种非 ISO 格式,仍然需要显式给出格式模式。

8. 对比

现在我们已经了解了使用 Jackson 解析日期时间 JSON 时出现错误的不同原因,下面是我们发现内容的概览:

问题典型错误消息可能原因
缺少模块Java 8 date/time type java.time.LocalDate not supported by default使用 Jackson 2.x 时未注册 Java Time 模块
格式错误Cannot deserialize value of type java.time.LocalDate from String输入字符串不符合期望的日期格式
JSON 类型错误Cannot deserialize value of type java.time.LocalDate from Object valueJSON 值的类型不兼容,而不是期望的字符串

因此,每当我们遇到意外错误时,都可以查阅这个参考表。

9. 结论

在本文中,我们探讨了 Jackson 无法构造 LocalDateLocalDateTime 实例的错误。

具体来说,我们确认该错误通常表明 Jackson 无法确定如何将传入的 JSON 转换为 Java 日期时间对象。

总而言之,一旦 Java 类型、JSON 载荷格式和 Jackson 配置保持一致,这类反序列化错误就会消失,Java Time 类型也应该能被透明地处理。