Jackson JSON解析库
Json作为一种数据交换格式,最常用到的地方就是传输数据,比如前后端Ajax或Websocket通信,以及微服务间的通信。Jackson是Java中一个流行的Json解析库,SpringMVC处理和Json相关的数据时,默认就需要使用Jackson库。本篇笔记记录Jackson库的简单使用。
引入Maven依赖
这里要注意,序列化通常都是一个安全漏洞频发的组件,Jackson库更新比较积极,但兼容性还是非常好的,我们也需要时常检查更新。到目前为止,Jackson库的最新版本是2.13.3。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.3</version>
</dependency>
Jackson主要包含这三个模块:
jackson-core核心模块,提供底层的streaming APIjackson-annotations提供了注解支持jackson-databind实现了数据绑定和对象序列化API,依赖于core和annotations模块
我们可以根据需要使用相应的模块。
处理json的三种方式
Jackson库提供了三种处理Json的方式:
- 流式解析 streaming API
- 文档树解析 Tree Model
- 对象数据绑定 Data Binding
这几种解析方式和XML的SAX、DOM、对象映射意思差不多。流式解析比较常用于嵌入式设备或是巨大Json报文,在更为常见的场景中,文档树解析和对象绑定方式更为常用。
对象绑定解析
对象绑定解析是Jackson库处理Json最简单也是最常用的方式,我们这里通过例子演示Json字符串和Java对象互相转换的写法。
注:这里我们为了节省篇幅,JavaBean的定义使用了Lombok插件。
解析Json字符串
下面例子中我们定义Java实体类,并解析JSON字符串为实体类的对象。
User.java
package com.gacfox.demo;
import lombok.Data;
@Data
class User {
private String username;
private String password;
}
user.json
{
"username":"tom",
"password":"123"
}
Main.java
package com.gacfox.demo;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
public class Main {
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("user.json");
User user = null;
try {
user = objectMapper.readValue(inputStream, User.class);
System.out.println(user);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
ObjectMapper是Jackson库提供的用于将Json字符串映射到对象的工具类。它是线程安全的,我们可以在多线程环境中使用一个全局的ObjectMapper对象。
readValue()方法有很多重载形式,上面我们传入的第一个参数是输入流,另一个常用的方式是直接传入Json字符串,其返回的结果就是填充好的Java对象。如果解析过程或者映射过程出错,都会抛出异常。除此之外,readValue()第二个参数接收了一个类型,我们也可以用TypeReference对象代替Class对象,写法如下:
user = objectMapper.readValue(inputStream, new TypeReference<User>() {});
TypeReference的优点是支持泛型类型(毕竟Java中没有List<User>.class这种写法)。
映射的过程中,实际上我们没有指定哪个Json字段映射到哪个Java类的属性,但是Json默认会将同名的字段和属性进行映射。实际上最佳实践其实也不需要手动指定,我们约定Json字段和Java类属性名保持一致即可,但如果不能满足,则需要通过注解指定。
序列化对象为Json
有了前面解析Json的知识,实际上序列化Java对象到Json也是类似的。
String jsonStr = objectMapper.writeValueAsString(user);
objectMapper.writeValueAsString()可以将一个对象序列化为Json。
使用Jackson注解
上面例子中,我们的Json数据和Java类字段都是完全对应的,这种情况不需要任何注解就可以自动映射。然而,实际开发时情况通常都比较复杂,有时并不能满足Json和Java类的对应。此时就需要使用Jackson注解标注特殊字段的处理,这里我们介绍最常用的一些注解。
@JsonIngore:该注解标注在类属性上,指示序列化、反序列化处理时忽略该字段。
@JsonProperty:该注解标注处理Json时的字段名,如果不标注该注解默认为Java类的属性名。
@JsonInclude(JsonInclude.Include.NON_NULL):@JsonInclude标注在类上,指定哪些情况下将类属性输出到Json,JsonInclude.Include.NON_NULL是该注解比较常见的参数,即只有字段非null时才会处理。
文档树解析
文档树解析相对来说就比较麻烦了,一般只有在极特殊情况,Json格式无法确定,必须动态解析时才会使用。Jackson库封装了JsonNode对象代表Json中的一个节点,包括对象、数组、字段,其具体内容提供了一系列as方法进行转换。下面是一个例子。
package com.gacfox.demo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
public class Main {
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("users.json");
try {
JsonNode jsonNode = objectMapper.readTree(inputStream);
JsonNode usersNode = jsonNode.get("users");
for (JsonNode userNode : usersNode) {
JsonNode usernameNode = userNode.get("username");
if (usernameNode != null) {
String username = usernameNode.asText();
System.out.println(username);
}
JsonNode passwordNode = userNode.get("password");
if (passwordNode != null) {
String password = passwordNode.asText();
System.out.println(password);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
users.json
{
"users": [
{
"username": "tom",
"password": "123"
},
{
"username": "lucy",
"password": "abc"
}
]
}
日期处理
日期类型也经常出现在实体类中,Jackson库默认能解析时间戳(一个很大的整数),其结果是java.util.Date类型。序列化Java对象时,我们可以对ObjectMapper指定DateFormat,输出一个代表时间的字符串。
time.json
{
"name":"timejson",
"createTime":1363917730000
}
TimeObject.java
package com.gacfox.demo;
import lombok.Data;
import java.util.Date;
@Data
public class TimeObject {
private String name;
private Date createTime;
}
Main.java
package com.gacfox.demo;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
public class Main {
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("time.json");
TimeObject timeObject = null;
try {
timeObject = objectMapper.readValue(inputStream, TimeObject.class);
System.out.println(timeObject);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
下面代码中,序列化为Json字符串时,我们传入了DateFormat参数。
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
String jsonStr = objectMapper.writeValueAsString(timeObject);
System.out.println(jsonStr);
输出结果如下。
{"name":"timejson","createTime":"2013-03-22 10:02:10"}
Json字段规约转换
Json是一种比较通用的格式,然而不同编程语言、不同框架中对字段的命名有不同的规约,比如Java喜欢统一用CamelCase;Python/PHP中很多Web接口喜欢用SnakeCase;C++中则有的项目喜欢PascalCase,有的喜欢CamelCase,有的喜欢LowerSnakeCase,有的喜欢UpperSnakeCase。
如果Json报文是SnakeCase,我们的Java对象是CamelCase,那么显然两者是无法对应的,难道我们要为此使用@JsonProperty标注全部字段么?实际上,Jackson库考虑到这一点,PropertyNamingStrategies中实现了大部分常见的规约写法转换。
下面例子中,我们建立了UpperSnakeCase和Java对象之间的互相转换。
package com.gacfox.demo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import java.io.IOException;
import java.io.InputStream;
public class Main {
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_SNAKE_CASE);
InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("user_upper_snake.json");
User user = null;
try {
// 解析Json
user = objectMapper.readValue(inputStream, User.class);
System.out.println(user);
// 序列化Json
String jsonStr = objectMapper.writeValueAsString(user);
System.out.println(jsonStr);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
代码中使用的user_upper_snake.json如下。
{
"USERNAME":"tom",
"PASSWORD":"123",
"USER_STATUS": 1
}