解决Jackson异常:无法从对象值反序列化(缺少委托或基于属性的创建器)
[LOADING...]
1. 引言
Jackson 是一个 Java 库,通常用于在 Java 对象与 JSON 之间进行转换。在 JSON 反序列化期间,当 Jackson 无法确定如何创建目标类的实例时,可能会抛出 InvalidDefinitionException。 即使 JSON 结构与类字段匹配,也可能发生这种情况。
在本教程中,我们将使用 Jackson 2.17.2 复现该异常,并探讨几种解决它的方法。
2. 复现异常
让我们从一个包含带参构造函数的不可变 Notification 类开始:
接下来,让我们尝试将 JSON 对象反序列化为 Notification 类:
尽管 JSON 属性与 Notification 中的字段匹配,Jackson 仍无法确定它们如何映射到构造函数参数。该构造函数没有指定每个参数应使用哪个 JSON 属性。
结果,反序列化失败并抛出 InvalidDefinitionException:
该异常表明 Jackson 需要一种合适的机制来创建 Notification 实例。 我们可以通过几种方式解决这个问题。
3. 添加默认构造函数
解决该异常的一种方式是提供一个无参构造函数:
默认情况下,Jackson 依赖无参构造函数来实例化目标类。然后它可以在反序列化期间填充对象的属性:
这种方法适用于可变类,即 Jackson 可以在创建实例后设置属性。
4. 定义基于属性的创建器
对于不可变类,我们可以显式定义 Jackson 应如何使用带参构造函数:
在这种情况下,@JsonCreator 注解会告诉 Jackson 使用被注解的构造函数来创建对象。这些 @JsonProperty 注解指定了每个构造函数参数对应哪个 JSON 属性。
将构造函数配置为创建器后,我们就可以在保持类不可变的同时反序列化 JSON:
此外,我们应该确保从 com.fasterxml.jackson.annotation 包导入这些注解,Jackson 3.x 也使用该包。Jackson 不会识别从旧版 org.codehaus.jackson 包导入的创建器注解。
4.1. 将工厂方法作为创建器
工厂方法也可以作为创建器。如果 Jackson 没有将我们的工厂方法识别为创建器,它将抛出同样的 InvalidDefinitionException。
要解决该问题,我们以与带参构造函数相同的方式注解该方法及其参数:
现在,这个方法也可以充当创建器。
5. 发现构造函数参数名
最后,除了向目标类添加 Jackson 注解之外,我们还可以配置 Jackson 来发现构造函数参数名。
5.1. 使用 ParameterNamesModule
在 Jackson 2.x 中,我们可以使用 ParameterNamesModule 来发现构造函数参数名,而无需添加 @JsonProperty 注解:
我们需要使用 Java 的 -parameters 选项编译该类,以便参数名在运行时可用。 **在 Jackson 3.x 中,此功能已内置于 jackson-databind 并默认启用,**因此我们不需要注册单独的模块。
5.2. 使用 ParanamerModule
Jackson 2.x 中的另一种选择是使用 ParanamerModule。首先,我们需要将 jackson-module-paranamer 依赖项添加到项目中:
接下来,我们将 ParanamerModule 注册到 ObjectMapper:
注册后,ParanamerModule 会向 Jackson 提供构造函数参数名,因此它可以将 message 和 priority 匹配到对应的 JSON 属性。
然而,Paranamer 已停止维护,并且仅支持 Jackson 2.x,这限制了该方法在新项目中的使用。
6. 结论
在本文中,我们复现了一个 Jackson 反序列化异常,该异常发生在 Jackson 无法确定如何创建目标对象时。随后,我们探讨了几种解决它的方法。
这些方法包括添加默认构造函数、使用 @JsonCreator 和 @JsonProperty 定义基于属性的创建器,以及让 Jackson 发现构造函数参数名。静态工厂方法也可以作为创建器。
文章 解决异常:无法从对象值反序列化(没有基于委托或基于属性的创建器) 首次出现在 Baeldung。