解决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. 定义基于属性的 Creator
对于不可变类,我们可以显式定义 Jackson 应如何使用带参构造函数:
在这种情况下,@JsonCreator 注解告诉 Jackson 使用被注解的构造函数创建对象。** @JsonProperty 注解则指定每个构造函数参数对应哪个 JSON 属性**。
将构造函数配置为 Creator 后,我们可以在保持类不可变的同时反序列化 JSON:
此外,应确保从 com.fasterxml.jackson.annotation 包导入注解,Jackson 3.x 也使用该包。Jackson 不会识别从旧版 org.codehaus.jackson 包导入的 Creator 注解。
4.1. 将工厂方法作为 Creator
工厂方法也可以作为 Creator。如果 Jackson 不将我们的工厂方法识别为 Creator,它将抛出相同的 InvalidDefinitionException。
要解决该问题,我们像处理带参构造函数一样,为该注解标注方法及其参数:
现在,这个方法也可以充当 Creator。
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 在无法确定如何创建目标对象时发生的反序列化异常。随后,我们探讨了几种解决方法。
这些方法包括添加默认构造函数、使用 @JsonCreator 和 @JsonProperty 定义基于属性的 Creator,以及让 Jackson 发现构造函数参数名。静态工厂方法也可以作为 Creator。
一如既往,完整源代码可在 GitHub 上获取。