1. 概述
本快速教程将说明如何使用 Jackson 2 通过 自定义Deserializer 对 JSON 进行反序列化。
要深入了解 更多关于Jackson的高级用法,请前往 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. 使用自定义反序列化器
在前面的示例中,JSON 表示与 Java 实体完美匹配。
接下来,我们将简化 JSON:
{
"id": 1,
"itemName": "theItem",
"createdBy": 2
}
由于JSON结构发生了变化,再次反序列化会报错:
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
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() {
// 默认构造函数
}
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 进行反序列化。
6. 总结
本文展示了如何利用Jackson 2处理非标准的JSON输入,以及如何完全控制映射,将这些输入映射到任何Java实体图。
所有这些示例和代码片段的实现可