jackson

简介

jackson支持java和Kotlin、Scala等语言,支持JSON及JSON以外的多种格式转换。

是Spring家族默认的JSON、XML解析器。

语法特点:纵观整个Jackson,它更多的是使用抽象类而非接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.11.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.11.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.3</version>
</dependency>

三大核心模块

  • jackson-core: Streaming流处理模块,定义底层处理流API:JsonPaser和JsonGenerator等,并包含特定于json的实现。
  • jackson-annotations:Annotations标准注解模块,包含标准的Jackson注解。
  • jackson-databind:Databind数据绑定模块,在streaming包上实现数据绑定(和对象序列化)支持;它依赖于上面的两个模块,也是Jackson的高层API(如ObjectMapper)所在的模块。实际开发中,只用到Databind数据绑定模块

扩展模块

通过扩展模块,让Jackson databind包(ObjectMapper / ObjectReader / ObjectWriter)能够顺利读写/转换扩展的类型。

  1. 利用Jackson插件模块(ObjectMapper.registerModule()),
  2. 添加序列化器和反序列化器
  3. 最终支持各种常用Java库数据类型。
数据类型模块datatype
  1. 官方维护的扩展模块

    * groupId为:`<groupId>com.fasterxml.jackson.datatype</groupId>`
    
  • version和官方主版本号相对应
  1. 非官方直接维护的扩展模块

    * groupId不确定,版本号与官方主版本无关。比如:
    
    • jackson-datatype-commons-lang3:支持Apache Commons Lang v3里面的一些类型
    • jackson-datatype-money:支持javax.money
    • jackson-datatype-json-lib:对久远的json-lib这个库的支持
数据格式模块dataformat

支持JSON之外的数据格式,大多数只是实现streaming API抽象,以便数据绑定组件可以按原样使用。

  1. 官方维护的扩展模块
    • groupId为:<groupId>com.fasterxml.jackson.dataformat</groupId>
    • version和官方主版本号相对应。
    • Avro/CBOR/Ion/Protobuf/Smile(binary JSON) :二进制格式,artifactId为:<artifactId>jackson-dataformat-[FORMAT]</artifactId>
    • CSV/Properties/XML/YAML等文本格式。
  2. 非官方直接维护的扩展模块。

支持Kotlin和Scala的依赖

  • groupId为:<groupId>com.fasterxml.jackson.module</groupId>
  • version和官方主版本号相对应。
    • jackson-module-kotlin:处理kotlin源生类型
    • jackson-module-scala_[scala版本号]:处理scala源生类型

支持移动端的依赖

jackson.jr,一个轻量级的更简单、更小的库。

仅依赖jackson-core,并不依赖于annotation和databind。

  1. Jackson三大核心模块大小:343KB(core)+ 66KB(annotation)+ 1.4M(databind)。
  2. Jackson jr大小:96KB(jr) + 343KB(core)
1
2
3
4
5
6
7
8
9
10
11
12
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-objects -->
<dependency>
<groupId>com.fasterxml.jackson.jr</groupId>
<artifactId>jackson-jr-objects</artifactId>
<version>2.11.3</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.11.3</version>
</dependency>

JSON的三种处理方式

方式 描述 隶属包
流式API 读/写性能最好、开销最低、 速度最快; 其它2者都基于它实现。 jackson-core
树模型 类似于XML的DOM解析,层层嵌套。是最灵活的方式 jackson-core
数据绑定 JSON和POJO相互转换,是使用最方便的方式。使用最多。 jackson-databind
  1. 流式API,读取和写入JSON内容作为离散事件
    • 读数据:org.codehaus.jackson.JsonParser
    • 写数据:org.codehaus.jackson.JsonGenerator
    • 流式,指的是IO流,因此具有最低的开销。
    • 使用流式API读写JSON的方式,使用的均是增量模式(每个部分一个一个地往上增加,类似于垒砖)
    • 流式API里很重要的一个抽象概念JsonToken:每一部分都是一个独立的Token(有不同类型的Token),最终被“拼凑”起来就是一个JSON。
  2. 树模型,JSON文件在内存里以树形式表示
    • org.codehaus.jackson.map.ObjectMapper 生成树
    • 树组成JsonNode 节点集
  3. 数据绑定,JSON和POJO相互转换
    • 简单数据绑定,是指从Java Map、List、String、Numbers、Boolean和空值进行转换
    • 完整数据绑定,是指从任何Java bean类型 (及上文所述的”简单”类型) 进行转换
    • 使用最方便的方式

流式API

实际是i/o流读写。隶属于jackson-core包。

写操作:JsonGenerator

运行案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// ----------基本实现------
public void testJsonGenerator() throws IOException {
JsonFactory factory = new JsonFactory();
// 将生成的json放到控制台,也可以放到文件(OutputStream)、网络(HttpOutputMessage)等
JsonGenerator jsonGenerator = factory.createGenerator(System.out, JsonEncoding.UTF8);
try {
// 体现了增量模式(每个部分一个一个地往上增加)
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", "want");
jsonGenerator.writeNumberField("age", 23);

// 只能出现一个独立key
// jsonGenerator.writeFieldName("school");
// jsonGenerator.writeFieldName("345");
jsonGenerator.writeFieldId(123);

jsonGenerator.writeEndObject();
} finally {
jsonGenerator.close();
}
} // {"name":"want","age":23,"123"}

// ----------try-with-resources实现(JsonGenerator实现了AutoCloseable接口)----
@Test
public void testJsonGeneratorAutoCloseable() throws IOException {
OutputStream outputStream = new FileOutputStream("./jsonGenertor.txt");
try (JsonGenerator jsonGenerator = factory.createGenerator(outputStream, JsonEncoding.UTF8)) {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", "want");
jsonGenerator.writeNumberField("age", 23);
jsonGenerator.writeEndObject();
}
}

// ----------------实现了AutoCloseable接口-----------
public interface Closeable extends AutoCloseable {void close() throws Exception;}
public abstract class JsonGenerator implements Closeable, Flushable, Versioned {
public abstract void close() throws IOException;
}
public void close() throws IOException {
super.close();
// 其他代码
}

try-with-resources:jdk1.7引入,一种语法糖,主要是替代了 try-catch-finally/try-finaly中的finally ,但是在编译的时候,还是会转回 try-catch-finally/try-finaly。

要求:在 try-with-resources 声明中定义的变量,要实现AutoCloseable 接口。

这样在系统可以自动调用它们的close方法,从而替代了finally中关闭资源的功能。

注意点:try-with-resources之后,又进行追加的catch 和 finally 中的代码,会在try-with-resources自动资源关闭之后才运行。

参考:https://zhuanlan.zhihu.com/p/163106465

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// try-finaly
InputStream inputStream = null;
try {
inputStream = new FileInputStream("/my/file");
// ...
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

// try-with-resources
try (InputStream inputStream = new FileInputStream("/my/file")) {
// ...
}

// 也可以支持创建多个资源,先创建,后关闭
try (InputStream inputStream = new FileInputStream("/my/file");
OutputStream outputStream = new FileOutputStream("./jsonGenertor.txt")) {
// ...
}
相关类
  • WriterBasedJsonGenerator:基于:java.io.Writer处理字符编码(使用Writer输出JSON)
    • 因为UTF-8编码基本标准化了,因此Jackson内部也提供了SegmentedStringWriter/UTF8Writer来简化操作
  • UTF8JsonGenerator:基于OutputStream + UTF-8处理字符编码(默认指定使用UTF-8编码把字节变为字符)

1606270805638

json实现

key,可以独立存在(无需value),但value不能独立存在{"name":"want","age":23,"school"}。生成独立key方法如下:

  1. jsonGenerator.writeFieldName(“school”);
  2. jsonGenerator.writeFieldId(123);只能是数字,且本质还是用writeFieldName来实现。
1
2
3
4
// 独立存在的key,放到最后,因为碰到独立key,json就终止了
// jsonGenerator.writeFieldName("school");
// jsonGenerator.writeFieldName("345");
jsonGenerator.writeFieldId(123); // {"school"}

value,JSON中取值类型只能如下6种,但是Java中数据类型更丰富,需要JsonGenerator进行映射。

graph LR
A(java数据类型) --JsonGenerator--> B(JSON数据格式)
字符串
1
2
3
4
5
6
// key-value分开写
jsonGenerator.writeFieldName("height");
jsonGenerator.writeString("170cm");

// key-value合并写法[推荐]
jsonGenerator.writeStringField("name", "want");

Base64编码

1
2
3
4
5
jsonGenerator.writeFieldName("base64");
jsonGenerator.writeBinary("hello,world!jackson!".getBytes()); // "base64":"aGVsbG8sd29ybGQhamFja3NvbiE="

// 合并写法
jsonGenerator.writeBinaryField("base64", "hello,world!jackson!".getBytes())

文本原样输出

1
2
3
4
5
6
7
8
9
10
jsonGenerator.writeFieldName("love1");
jsonGenerator.writeString("{'love': 'batman'}");//"love1":"{'love': 'batman'}"

// 不做任何转义,也不添加分隔符
jsonGenerator.writeFieldName("love2");
jsonGenerator.writeRaw("{'love': 'batman'}");//"love2"{'love': 'batman'}

// 不做任何转义,但添加分隔符
jsonGenerator.writeFieldName("love3");
jsonGenerator.writeRawValue("{'love': 'batman'}");//"love3":{'love': 'batman'}
数字
1
2
3
4
5
jsonGenerator.writeFieldName("age");
jsonGenerator.writeNumber(10);

// 合并写法
jsonGenerator.writeNumberField("age", 23);
布尔
1
2
3
4
5
jsonGenerator.writeFieldName("isMan");
jsonGenerator.writeBoolean(false)

//合并写法
jsonGenerator.writeBooleanField("isMan", true);
数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ------key-value分开写法------
jsonGenerator.writeFieldName("friends");
jsonGenerator.writeStartArray();
// 数组元素-string
jsonGenerator.writeString("tom");

// 数组元素-对象
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", "zhou");
jsonGenerator.writeNumberField("age", 53);
jsonGenerator.writeEndObject();

// 数组元素-数字
jsonGenerator.writeNumber(10);
jsonGenerator.writeEndArray();

// ------key-value合并写法------
// writeArrayFieldStart = writeFieldName + writeStartArray
jsonGenerator.writeArrayFieldStart("cakes");
jsonGenerator.writeString("price");
jsonGenerator.writeEndArray();

// ------快捷写入数组(从第index = 0位开始,取5个[0,0+5))------
jsonGenerator.writeFieldName("classmates");
jsonGenerator.writeArray(new int[]{1, 2, 3, 4, 5, 6}, 0, 5);
对象
1
2
3
4
5
6
7
8
9
10
11
12
13
// ------key-value分开写法------
jsonGenerator.writeFieldName("father");
// JSON的顺序,和write的顺序保持一致
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name", "zhou");
jsonGenerator.writeNumberField("age", 53);
jsonGenerator.writeEndObject();

// ------key-value合并写法------
// writeObjectFieldStart = writeFieldName + writeStartObject
jsonGenerator.writeObjectFieldStart("money");
jsonGenerator.writeStringField("bank", "12345678901");
jsonGenerator.writeEndObject();

POJO实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// User实体类转json
class User {
private String name = "loveName";
private Integer age = 23;
// 其他
}

/**
* 自定义解码器
* 用于把User写为JSON
*/
class UserObjectCodec extends ObjectCodec {
@Override
public void writeValue(JsonGenerator gen, Object value) throws IOException {
User user = (User) value;
gen.writeStartObject();
gen.writeStringField("name", user.getName());
gen.writeNumberField("age", user.getAge());
gen.writeEndObject();
}
// 其他
}

// --------执行JSON转换-----------
// 必须指定一个ObjectCodec解码器,才能转换实体类为JSON
// data-bind常见的ObjectMapper就是一个ObjectCodec解码器,这是ObjectMapper的原理雏形
jsonGenerator.setCodec(new UserObjectCodec());
jsonGenerator.writeObject(new User());

嵌套实体类,其实是个树结构转JSON,但core模块没有提供树模型TreeNode的实现

实现方式:

  1. writeTree()
  2. setCodec()解码器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class UserTreeNode implements TreeNode {
private User user;

public UserTreeNode(User user) {
this.user = user;
}
}
class User {
private String name = "loveName";
private Integer age = 23;
}

class UserTreeCodec extends ObjectCodec {
@Override
public void writeValue(JsonGenerator gen, Object value) throws IOException {
User user = null;
if (value instanceof User) {
user = User.class.cast(value); // 类型强制转换等价于 (User) value
} else if (value instanceof TreeNode) {
user = UserTreeNode.class.cast(value).getUser();
}

gen.writeStartObject();
gen.writeStringField("name", user.getName());
gen.writeNumberField("age", user.getAge());
gen.writeEndObject();
}
}
public void testJsonGeneratorTree() throws IOException {
JsonFactory jsonFactory = new JsonFactory();
try (JsonGenerator jsonGenerator = jsonFactory.createGenerator(System.out, JsonEncoding.UTF8)) {
jsonGenerator.setCodec(new UserTreeCodec());
jsonGenerator.writeTree(new UserTreeNode(new User()));
}
}
null
1
jsonGenerator.writeNullField("weight");

树模型

writeTree()

-------------Keep It Simple Stupid-------------
0%