开始使用 Jackson 中的自定义反序列化
上次更新:2024年9月3日
1. 概述
本快速教程将说明如何使用 Jackson 2 通过一个自定义反序列化器 (Deserializer) 来反序列化 JSON。
要深入了解我们可以使用 Jackson 2 进行的其他很酷的操作,请前往主要的 Jackson 教程。
更多阅读
2. 标准反序列化
让我们首先定义两个实体,看看 Jackson 如何将 JSON 表示反序列化到这些实体,而无需任何自定义
public class User {
public int id;
public String name;
}
public class Item {
public int id;
public String itemName;
public User owner;
}
现在让我们定义我们想要反序列化的 JSON 表示形式
{
"id": 1,
"itemName": "theItem",
"owner": {
"id": 2,
"name": "theUser"
}
}
最后,让我们将此 JSON 转换为 Java 实体
Item itemWithOwner = new ObjectMapper().readValue(json, Item.class);
3. 在 ObjectMapper 上的自定义反序列化器
在前面的示例中,JSON 表示形式与 Java 实体完全匹配。
接下来,我们将简化 JSON
{
"id": 1,
"itemName": "theItem",
"createdBy": 2
}
当将其反序列化为完全相同的实体时,默认情况下,这当然会失败
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "createdBy" (class org.baeldung.jackson.dtos.Item),
not marked as ignorable (3 known properties: "id", "owner", "itemName"])
at [Source: java.io.StringReader@53c7a917; line: 1, column: 43]
(through reference chain: org.baeldung.jackson.dtos.Item["createdBy"])
我们将通过使用一个自定义反序列化器来进行我们自己的反序列化来解决这个问题
public class ItemDeserializer extends StdDeserializer<Item> {
public ItemDeserializer() {
this(null);
}
public ItemDeserializer(Class<?> vc) {
super(vc);
}
@Override
public Item deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("id")).numberValue();
String itemName = node.get("itemName").asText();
int userId = (Integer) ((IntNode) node.get("createdBy")).numberValue();
return new Item(id, itemName, new User(userId, null));
}
}
正如我们所见,反序列化器正在使用 Jackson 的标准 JSON 表示形式——JsonNode。 一旦输入 JSON 被表示为 JsonNode,我们现在就可以从其中提取相关信息并构建我们自己的 Item 实体。
简单来说,我们需要注册这个自定义反序列化器并正常反序列化 JSON
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Item.class, new ItemDeserializer());
mapper.registerModule(module);
Item readValue = mapper.readValue(json, Item.class);
4. 在类上的自定义反序列化器
或者,我们也可以直接在类上注册反序列化器
@JsonDeserialize(using = ItemDeserializer.class)
public class Item {
...
}
在类级别定义反序列化器后,无需在 ObjectMapper 上注册它——默认的映射器就可以正常工作
Item itemWithOwner = new ObjectMapper().readValue(json, Item.class);
这种每类配置非常适用于我们可能无法直接访问原始 ObjectMapper 进行配置的情况。
5. 泛型类型的自定义反序列化器
现在让我们创建一个Wrapper类,其中包含一个泛型类型T的唯一参数
public class Wrapper<T> {
T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
我们的Item的User属性现在将是Wrapper<User>类型
public class Item {
public int id;
public String itemName;
public Wrapper<User> owner;
}
让我们为这种情况实现一个自定义反序列化器。
首先,我们需要实现ContextualDeserializer接口,以获取Wrapper内部的实体类型。 我们将通过重写createContextual()方法来做到这一点。 当调用此方法时,将解析上下文,并且可以通过BeanProperty参数检索Wrapper的实际内容。
我们还必须扩展JsonDeserializer。 因此,我们可以在deserialize()中设置Wrapper的值的具体类型
public class WrapperDeserializer extends JsonDeserializer<Wrapper<?>> implements ContextualDeserializer {
private JavaType type;
public WrapperDeserializer() {
// Default constructor
}
private WrapperDeserializer(JavaType type) {
this.type = type;
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
JavaType wrapperType = property.getType().containedType(0);
return new WrapperDeserializer(wrapperType);
}
@Override
public Wrapper<?> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
Wrapper<?> wrapper = new Wrapper<>();
wrapper.setValue(deserializationContext.readValue(jsonParser, type));
return wrapper;
}
}
这个JsonDeserializer还可以处理同一类中具有不同类型的多个Wrapper属性。 假设我们有一个包含多个类型包装器的类
public class ItemWithMultipleWrappers {
private int id;
private String itemName;
private Wrapper<User> owner;
private Wrapper<Integer> count;
}
在这种情况下,反序列化仍然有效,因为createContextual()方法为每个属性创建一个新的WrapperDeserializer实例并返回具有正确的类型,而不是直接设置类型字段。
最后,我们需要注册我们的自定义反序列化器才能反序列化 JSON
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Wrapper.class, new WrapperDeserializer());
mapper.registerModule(module);
Item readValue = mapper.readValue(json, Item.class);
6. 结论
本文展示了如何利用 Jackson 2 读取非标准 JSON 输入,以及如何将该输入映射到任何 Java 实体图,并完全控制映射。
支持本文的代码可在 GitHub 上获取。 一旦你以 Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。















