Jackson 快速入门 - 捏造的信仰

SegmentFault 思否 · · 43 次点击 · · 开始浏览    

Jackson 快速入门

本文是对 Jackson 的快速入门介绍,主要分为四部分:

  1. 基本使用
  2. 基础配置
  3. 自定义序列化/反序列化
  4. 对泛型的处理

上面这几个话题足以覆盖日常开发的场景了。限于篇幅所限,本文力求读者读完后能掌握 Jackson 在日常使用中的绝大部分场景,以及了解如何着手探索 Jackson 的深层定制。

基本使用

引入 Jackson

本文假设读者熟悉 Maven 的使用,那么只需要在项目中添加下面的依赖关系就可以了:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>[VERSION]</version>
</dependency>

最新的版本号请在这里查看。

ObjectMapper

Jackson 提供了 com.fasterxml.jackson.databind.ObjectMapper 类作为 JSON 操作的统一接口。这个类有两个特点:

  1. 实例化代价很高;
  2. 线程安全。

因此我们应该避免频繁大量的创建 ObjectMapper 实例,而应该应用单例模式,比如:

  1. ObjectMapper 对象放在静态成员中;
  2. ObjectMapper 对象放在 IoC 容器中。这样做的好处是可以参与自动组装,坏处是容器外访问稍微有点麻烦。

我们知道 JSON 文档是一个树形数据结构。对于任何一个 JSON 框架,必然会在内部实现一套树节点,以方便 JSON 文档的解析和生成。因此一个 JSON 框架的基本功能就是实现下面三点:

  1. JSON 字符串和 POJO 对象之间的转换;
  2. JSON 字符串和树节点之间的转换;
  3. 树节点和 POJO 对象之间的转换。

Jackson 实现的树节点类叫做 com.fasterxml.jackson.databind.JsonNode。虽然树结构是框架内部的东西,但如果我们要对序列化/反序列化进行自定义,还是要了解树节点的操作。

基本功能

ObjectMapper 提供了简单好用的接口来实现上面三个功能。下面是使用示例:

// 假设我们有一个叫做 Book 的类,里面只有一个 name 属性
String json = "{\"name\":\"<<Design Patterns>>\"}";
ObjectMapper objectMapper = new ObjectMapper();

Book book = objectMapper.readValue(json, Book.class); // JSON -> OBJECT
String json2 = objectMapper.writeValueAsString(book); // OBJECT -> JSON

JsonNode node = objectMapper.readTree(json);          // JSON -> NODE
String json3 = objectMapper.writeValueAsString(node); // NODE -> JSON

Book book2 = objectMapper.treeToValue(node, Book.class);  // NODE -> OBJECT
JsonNode node2 = objectMapper.valueToTree(book);          // OBJECT -> NODE

以上就是 Jackson 的基本使用方法。

基础配置

ObjectMapper 提供非常多的配置,本文篇幅有限无法逐个介绍,只能挑几个重点。并且在具体介绍之前,最好先带读者了解 ObjectMapper 的整体配置是如何规划和访问的,以便读者进一步探索 ObjectMapper。

ObjectMapper 的配置大致分为特性开关、配置属性、功能替换和扩展模块四种类别。

特性开关

特性开关通过 ObjectMapper 的 configure() 方法来调用,下面是一个例子:

// 以多行缩进格式化的格式输出 JSON
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
// 反序列化若遇到无法识别的属性,不抛出异常
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

configure() 方法的第一个参数就是特性枚举值。所有的特性都在下面五个枚举类中定义:

  • com.fasterxml.jackson.core.JsonGenerator.Feature
  • com.fasterxml.jackson.core.JsonParser.Feature
  • com.fasterxml.jackson.databind.MapperFeature
  • com.fasterxml.jackson.databind.SerializationFeature
  • com.fasterxml.jackson.databind.DeserializationFeature

建议读者有时间的话,浏览一遍这些枚举值(源码中有详尽的注释),了解 ObjectMapper 提供哪些特性开关。

配置属性

当然有些配置是不能简单地开关的,所以 ObjectMapper 提供 setter 方法来设置这些配置属性。下面是一个例子:

// 当输出 JSON 时不包含 null 属性
objectMapper.setDefaultPropertyInclusion(Include.NON_NULL);
// 自定义序列化和反序列化的日期格式
objectMapper.setDateFormat(new SimpleDateFormat("yyyyMMddHHmmssSSS"));
// 设置属性命名风格
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);

功能替换

ObjectMapper 允许你用自己的实现来替换它的某些功能,比如 setNodeFactory(JsonNodeFactory)setSerializerFactory(SerializerFactory)setTypeFactory(TypeFactory) 等等,这些方法看名字就知道是用于深度定制的,平常我们用不到。

扩展模块

ObjectMapper 提供一个叫 registerModule(Module) 的方法。Module 是一个抽象类,目的是帮助用户将特定场景下对 ObjectMapper 的一整套定制过程集中在一个类里面。所以除非是大规模的定制,否则是没必要用它的。

自定义序列化/反序列化

但是在某些情况下,上面这些配置依旧不能满足我们的需要。下面是一个例子,假设我们有一套这样的 POJO 类:

// 假设读者知道如何使用 lombok,这里用 @Data 来代替 getter/setter 方法
// Person.java
@Data
public class Person {
    private Pet pet;
}
// Pet.java
@Data
public abstract class Pet {
    private String name;
}
// Dog.java
public class Dog extends Pet {
}
// Cat.java
public class Cat extends Pet {
}

以及这样一个 JSON 字符串 {"pet":{"name":"Matt"}},如果我想把它反序列化为 Person 对象,就会失败,因为 ObjectMapper 无法通过 JSON 内容来判断 pet 属性值应该是一个 Dog 对象还是 Cat 对象。这时候我们就要用到自定义反序列化了。下面是解决这个问题的过程:

1. 补完类型信息

反序列化失败归根结底是因为 JSON 中缺少类型信息,所以要有一种方式来将它补全。我们添加一个新的属性 type,如果属性值存在,则根据它来确定类型;如果不存在,则使用一个默认的类型(比如 Cat)。当属性存在时,JSON 字符串是这样的:

{"pet":{"name":"Matt","type":"Dog"}}

同时 Pet 对象生成的 JSON 也要包含 type 属性。Pet 类代码改动如下:

// Pet.java
@Data
public abstract class Pet {
    private String name;
    public String getType() {
        return this.getClass().getSimpleName();
    }
}

2. 自定义反序列化

首先我们通过注解告诉 ObjectMapper,pet 属性要用自定义的反序列化类。对 Person 类的代码改动如下:

// Person.java
@Data
public class Person {
    @JsonDeserialize(using = PetDeserializer.class)
    private Pet pet;
}

PetDeserializer 这个类尚不存在,我们需要创建它:

// PetDeserializer.java
public class PetDeserializer extends JsonDeserializer<Pet> {

    @Override
    public Pet deserialize(
        JsonParser p, DeserializationContext ctxt
    ) throws IOException {

        // 1. 取 JSON 中的 type 属性,注意这里是通过树节点属性来取值
        JsonNode node = p.readValueAsTree();
        String petType = node.get("type").asText();

        // 2. 根据 type 的值来决定返回 Dog 对象还是 Cat 对象。
        //    注意这里是如何直接反序列化整个树节点的
        Pet pet;
        if (Cat.class.getSimpleName().equals(petType)) {
            pet = p.getCodec().treeToValue(node, Cat.class);
        } else if (Dog.class.getSimpleName().equals(petType)) {
            pet = p.getCodec().treeToValue(node, Dog.class);
        } else {
            pet = p.getCodec().treeToValue(node, Cat.class);
        }
        return pet;
    }
}

不过当 JSON 字符串包含 type 属性时,运行这段代码依旧会反序列化失败,因为 Pet 类并没有真正的 type 属性。没关系我们再给 getType() 方法加上一个 @JsonIgnore注解。对 Pet 类的改动如下:

// Pet.java
@Data
public abstract class Pet {
    private String name;
    @JsonIgnore
    public String getType() {
        return this.getClass().getSimpleName();
    }
}

这样 Person 类就能正常序列化和反序列化了。

自定义反序列化需要继承 JsonDeserializer 类,而自定义序列化则需要继承 JsonSerializer 类,这里不再举例了。

对泛型的处理

这里针对的是无法进行类型推断的泛型,介绍如何将 JSON 以指定的元素类型反序列化到一个集合。假设我们有一个这样的类:

@Data
public class Book {
    private String name;
}

以及这样一个 JSON 字符串:

[{"name":"book1"}]

想要将其序列化为一个 List<Book> 对象,需要采取下面的方式,不同的 jackson 版本有区别:

// jackson 2.11 以下版本
CollectionType typeRef =
    com.fasterxml.jackson.databind.type.TypeFactory
    .defaultInstance()
    .constructCollectionType(List.class, Book.class);
List<Book> bookList =
    objectMapper.readValue(json, typeRef);

// jackson 2.11 及以上版本
List<Book> bookList = objectMapper
    .readerForListOf(Book.class).readValue(json);

以上就是对 Jackson 的快速入门介绍,如有不正确之处,感谢指正!

阅读 356

本文来自:SegmentFault 思否

感谢作者:SegmentFault 思否

查看原文:Jackson 快速入门 - 捏造的信仰

43 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传