1. 概述

本快速教程将演示如何使用Jackson 2 进行自定义反序列化,将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. 为泛型类型创建自定义反序列化器

现在,我们将创建一个只包含泛型类型T的单一参数的Wrapper类:

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;

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
        this.type = property.getType().containedType(0);
        return this;
    }

    @Override
    public Wrapper<?> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        Wrapper<?> wrapper = new Wrapper<>();
        wrapper.setValue(deserializationContext.readValue(jsonParser, type));
        return wrapper;
    }
}

同样,我们需要注册我们的自定义反序列化器,才能反序列化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项目中找到。