1 概述:Jackson
Jackson 库的介绍
Jackson
是由 FasterXML 开发的、用来序列化和反序列化json
的Java
的开源框架。
- 其支持多种数据格式(如 JSON、XML、YAML 等)。
Spring MVC
/Spring Boot
框架的默认json
解析器便是Jackson
。- 其通过流式 API 提供高性能的序列化和反序列化,适合企业级复杂场景
Jackson库的版本沿革
- Jackson 的
1.x
版本的包名是org.codehaus.jackson
,当升级到2.x
版本时,包名变为com.fasterxml.jackson
本文讨论的内容是基于最新的 Jackson 的 2.9.1 版本。
Jackson的组成
- Jackson 的核心模块由三部分组成:
jackson-core
,核心包,提供基于"流模式"解析的相关 API,它包括JsonPaser
和JsonGenerator
。Jackson 内部实现正是通过高性能的流模式 API 的 JsonGenerator 和 JsonParser 来生成和解析 json。
jackson-annotations
,注解包,提供标准注解功能;
jackson-databind
,数据绑定包提供基于"对象绑定" 解析的相关 API (
ObjectMapper
) 和"树模型" 解析的相关 API (JsonNode
);
基于"对象绑定" 解析的 API 和"树模型"解析的 API 依赖基于"流模式"解析的 API。
Jackson
最常用的 API 就是基于"对象绑定" 的ObjectMapper
。
JSON序列化库的对比
- 与其他 Java 的 json 的框架 Gson / Fastjson 等相比:
Gson
:Gson 是 Google 开发的轻量级 JSON 库,零外部依赖,适合 Android 开发。它通过 TypeToken 解决泛型擦除问题,支持复杂对象图的转换,但性能相对较慢。
Fatsjson
/Fastjson2
Fastjson2 是阿里巴巴开发的高性能 JSON 库,作为 Fastjson 1.x 的重构版本,修复了安全漏洞并提升了性能,适合高吞吐量场景。
[JSON] Fastjson 之版本对比:Fastjson vs Fastjson2 - 博客园/千千寰宇
- 序列与反序列化的性能: Jackson 解析大的 json 文件速度比较快;
性能测试结果显示,Fastjson2 的序列化速度领先 20%-30%,而 Jackson 的反序列化性能与其接近,Gson 在两方面都稍逊一筹。
- 资源消耗: Jackson 运行时占用内存比较低,性能比较好;
- 可扩展性: Jackson 有灵活的 API,可以很容易进行扩展和定制。(相比 Fastjson)
- 安全性: Jackson 和 Fastjson2 的安全性较高,尤其是 Fastjson2 修复了 1.x 的历史漏洞(如反序列化 RCE)。
Gson 的安全性也较好,但在处理复杂场景时可能需要额外配置。
Fastjson2 对 Fastjson 1.x 的部分 API 提供兼容支持,迁移成本较低。
- 稳定性:Jackson 和 Gson 的 API 稳定性较高,适合长期维护的项目。
- json 序列化库选型的最佳实践
- 企业级应用:推荐 Jackson,因其安全性高、生态完善且支持多格式。
- 性能优先场景:推荐 Fastjson2,其序列化性能优于其他库。
- 轻量级开发:推荐 Gson,因其简单易用、零配置。
Maven 依赖坐标
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${jackson.version}</version>
</dependency>
jackson.version
= 2.13.3 / 2.19.2 / ...
- 依赖说明
jackson-databind
依赖jackson-core
和jackson-annotations
故:当添加
jackson-databind
之后,jackson-core
和jackson-annotations
也随之添加到 Java 项目工程中。
在添加相关依赖包之后,就可以使用 Jackson。
2 实践案例
样例DTO
public class Person {// 正常caseprivate String name;// 空对象caseprivate Integer age;// 日期转换caseprivate Date date;// 默认值caseprivate int height;
}
样例数据
- 样例数据1
{"cmsMajorVersionNumber": 1,"cmsMinorVersionNumber": null,"cmsPackCnt": 2,"cmsPackInfo": [{"id": "cms123456","chargeEndNo": 1,"cmsCellCnt": 96,"cmsModuleXCellBlock": [3.72,3.71,3.7,3.72],"cmsCellThremistorCnt": 4,"cmsModuleXTempSensor": [28.5,29.0,28.8,29.2],"cmsCellVoltMax": 3.72,"cmsCellVoltMin": 3.7,"cmsCellVoltAvg": 3.71,"cmsCellVoltMaxIndex": 2,"cmsCellVoltMinIndex": 5,"cmsPackTempMin": 28.5,"cmsPackTempMax": 29.2,"cmsPackTempMinIdx": 1,"cmsPackTempMaxIdx": 3,"cmsPackTempAvg": 28.9,"cmsPPack": -3200.5,"cmsSoh": 95.2,"cmsChgAmount": 12450.6,"cmsIsolationResistance": 5000,"cmsVPackBmu": 355.6,"cmsBalanceSwCellBlock": [0,1,0,1],"cmsCycleCnt": 215,"ratedCurrent": 200.0,"peakCurrent": 350.0,"ratedVolt": 360.0,"ratedCapacity": 120.0,"currentCapacity": 75.3,"cmsSoc": 62.5}]
}
CASE 基本的序列化与反序列化
@Test
public void test1() throws IOException {ObjectMapper mapper = new ObjectMapper();// 造数据Person person = new Person();person.setName("Tom");person.setAge(40);person.setDate(new Date());//序列化String jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(person);System.out.println(jsonString);//反序列化Person deserializedPerson = mapper.readValue(jsonString, Person.class);System.out.println(deserializedPerson);
}
out
序列化
{
"name" : "Tom",
"age" : 40,
"date" : 1594634846647,
"man" : null,
"height" : 0
}
反序列化
JackSonTest.Person(name=Tom, age=40, date=Mon Jul 13 18:07:26 CST 2020, man=null, height=0)
- 解释
ObjectMapper
通过writeValue
系列方法将 java 对象序列化为 json,并将 json 存储成不同的格式 :String
(writeValueAsString
),Byte Array
(writeValueAsString),Writer
,File
,OutStream
和DataOutput
。
ObjectMapper
通过readValue
系列方法从不同的数据源像 String , Byte Array, Reader,File,URL, InputStream 将 json 反序列化为 java 对象。
CASE ObjectMapper
对象 的统一配置
- 在调用 writeValue 或调用 readValue 方法之前,往往需要设置 ObjectMapper 的相关配置信息。这些配置信息应用 java 对象的所有属性上。
//在反序列化时忽略在 json 中存在但 Java 对象不存在的属性
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);//在序列化时日期格式默认为 yyyy-MM-dd'T'HH:mm:ss.SSSZ
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);//在序列化时自定义时间日期格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));//在序列化时忽略值为 null 的属性
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);//在序列化时忽略值为默认值的属性
mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);
更多配置信息可以查看 Jackson 的
DeserializationFeature
,SerializationFeature
和Include
。
CASE 使用注解
- Jackson 根据它的默认方式序列化和反序列化 java 对象,若根据实际需要,灵活的调整它的默认方式,可以使用
Jackson
的注解。常用的注解及用法如下。
注解 | 用法 |
---|---|
@JsonProperty | 用于属性,把属性的名称序列化时转换为另外一个名称。示例:@JsonProperty("birth_date") private Date birthDate |
@JsonIgnore | 可用于字段、getter/setter、构造函数参数上,作用相同,都会对相应的字段产生影响。使相应字段不参与序列化和反序列化。 |
@JsonIgnoreProperties | 该注解是类注解。该注解在Java类和JSON不完全匹配的时候使用。 |
@JsonFormat | 用于属性或者方法,把属性的格式序列化时转换成指定的格式。示例:@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm") public Date getBirthDate() |
@JsonPropertyOrder | 用于类, 和 @JsonProperty 的index属性类似,指定属性在序列化时 json 中的顺序 , 示例:@JsonPropertyOrder({ "birth_Date", "name" }) public class Person |
@JsonCreator | 用于构造方法,和 @JsonProperty 配合使用,适用有参数的构造方法。示例:@JsonCreator public Person(@JsonProperty("name")String name) |
@JsonAnySetter | 用于属性或者方法,设置未反序列化的属性名和值作为键值存储到 map 中 @JsonAnySetter public void set(String key, Object value) |
@JsonAnyGetter | 用于方法 ,获取所有未序列化的属性 public Map<String, Object> any() |
@JsonNaming | 类注解。序列化的时候该注解可将驼峰命名的字段名转换为下划线分隔的小写字母命名方式。反序列化的时候可以将下划线分隔的小写字母转换为驼峰命名的字段名。示例:@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) |
@JsonRootName | 类注解。需开启mapper.enable(SerializationFeature.WRAP_ROOT_VALUE),用于序列化时输出带有根属性名称的 JSON 串,形式如 {"root_name":{"id":1,"name":"zhangsan"}}。但不支持该 JSON 串反序列化。 |
- 使用示例
// 用于类,指定属性在序列化时 json 中的顺序
@JsonPropertyOrder({"date", "user_name"})
// 批量忽略属性,不进行序列化
@JsonIgnoreProperties(value = {"other"})
// 用于序列化与反序列化时的驼峰命名与小写字母命名转换
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public static class User {@JsonIgnoreprivate Map<String, Object> other = new HashMap<>();// 正常case@JsonProperty("user_name")private String userName;// 空对象caseprivate Integer age;@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")// 日期转换caseprivate Date date;// 默认值caseprivate int height;public User() {}// 反序列化执行构造方法@JsonCreatorpublic User(@JsonProperty("user_name") String userName) {System.out.println("@JsonCreator 注解使得反序列化自动执行该构造方法 " + userName);// 反序列化需要手动赋值this.userName = userName;}@JsonAnySetterpublic void set(String key, Object value) {other.put(key, value);}@JsonAnyGetterpublic Map<String, Object> any() {return other;}// 本文默认省略getter、setter方法
}
单元测试:
@Test
public void test3() throws IOException {ObjectMapper mapper = new ObjectMapper();// 造数据Map<String, Object> map = new HashMap<>();map.put("user_name", "Tom");map.put("date", "2020-07-26 19:28:44");map.put("age", 100);map.put("demoKey", "demoValue");String jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(map);System.out.println(jsonString);System.out.println("反序列化");User user = mapper.readValue(jsonString, User.class);System.out.println(user);System.out.println("序列化");jsonString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);System.out.println(jsonString);
}
out
{"date" : "2020-07-26 19:28:44","demoKey" : "demoValue","user_name" : "Tom","age" : 100
}
反序列化
@JsonCreator 注解使得反序列化自动执行该构造方法Tom
JackSonTest.User(other={demoKey=demoValue}, userName=Tom, age=100, date=Sun Jul 26 19:28:44 CST 2020, height=0)
序列化
{"date" : "2020-07-26 19:28:44","user_name" : "Tom","age" : 100,"height" : 0,"demoKey" : "demoValue"
}
CASE 将强类型的 dto java 对象转换为 JsonNode
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.valueToTree( myDto );
CASE 将 json / dto 对象转换为平铺的嵌套层次的K-V Map
- 需求描述
将 【样例数据1】 的 json / dto 转为平铺的嵌套层次的K-V Map。
其Map的内容,例如:
"cmsMajorVersionNumber" : 1
"cmsMinorVersionNumber": null
"cmsPackCnt": 2
"cmsPackInfo[0].id" : "cms123456"
"cmsPackInfo[0].chargeEndNo": 1
"cmsPackInfo[0].cmsCellCnt": 96
...
- 代码实现
- JacksonUtils#flatten (本文档)
Z 最佳实践
JacksonUtils
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.annotation.Nullable;
import java.util.LinkedHashMap;
import java.util.Map;/*** 基于 jackson 的反序列化工具* @updateTime 2025-09-10 23:51*/
//@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JacksonUtils {private static final Logger log = LoggerFactory.getLogger(JacksonUtils.class);private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();public static String NESTED_LEVEL_SEPARATOR_VALUE = System.getProperty("json.nestedLevelSeparator", ".");public static final String NESTED_LEVEL_SEPARATOR_PARAM = "json.nestedLevelSeparator";public static String ARRAY_ELEMENT_LEFT_FLAG_VALUE = "[";public static final String ARRAY_ELEMENT_LEFT_FLAG_PARAM = "json.arrayElement.leftFlag";public static String ARRAY_ELEMENT_RIGHT_FLAG_VALUE = "]";public static final String ARRAY_ELEMENT_RIGHT_FLAG_PARAM = "json.arrayElement.rightFlag";static {OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);OBJECT_MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);//flatten 方法的相关配置log.info("{} : {}", "json.nestedLevelSeparator", NESTED_LEVEL_SEPARATOR_VALUE);ARRAY_ELEMENT_LEFT_FLAG_VALUE = System.getProperty("json.arrayElement.leftFlag", "[");log.info("{} : {}", "json.arrayElement.leftFlag", ARRAY_ELEMENT_LEFT_FLAG_VALUE);ARRAY_ELEMENT_RIGHT_FLAG_VALUE = System.getProperty("json.arrayElement.rightFlag", "]");log.info("{} : {}", "json.arrayElement.rightFlag", ARRAY_ELEMENT_RIGHT_FLAG_VALUE);}@SneakyThrowspublic static String writeValueAsString(Object o) {return OBJECT_MAPPER.writeValueAsString(o);}@SneakyThrowspublic static byte[] writeValueAsBytes(Object o) {return OBJECT_MAPPER.writeValueAsBytes(o);}@SneakyThrowspublic static <T> T readValue(byte[] bytes, TypeReference<T> typeReference) {return OBJECT_MAPPER.readValue(bytes, typeReference);}@SneakyThrowspublic static <T> T readValue(byte[] bytes, Class<T> tClass) {return OBJECT_MAPPER.readValue(bytes, tClass);}@SneakyThrowspublic static <T> T readValue(String json, Class<T> tClass) {return OBJECT_MAPPER.readValue(json, tClass);}public static Map<String, Object> flatten(String json, @Nullable String prefix, Boolean handleArray) throws Exception {JsonNode root = OBJECT_MAPPER.readTree(json);Map<String, Object> result = new LinkedHashMap();flatten(prefix == null ? "" : prefix, root, result, handleArray, false);return result;}public static Map<String, Object> flatten(String json) throws Exception {return flatten(json, "", true);}/*** 打平* @param prefix* @param node* @param map* @param handleArray* @param filterNullValue 是否过滤掉 null 值*/public static void flatten(String prefix, JsonNode node, Map<String, Object> map, Boolean handleArray, Boolean filterNullValue) {int nestedLevelSeparatorValueLength = NESTED_LEVEL_SEPARATOR_VALUE != null ? NESTED_LEVEL_SEPARATOR_VALUE.length() : 0;if (node.isObject()) {String finalPrefix = prefix;node.fields().forEachRemaining((e) -> {JsonNode currentNode = (JsonNode)e.getValue();flatten(finalPrefix + (String)e.getKey() + NESTED_LEVEL_SEPARATOR_VALUE, currentNode, map, handleArray, filterNullValue);});} else if (node.isArray()) {prefix = prefix.substring(0, prefix.length() - nestedLevelSeparatorValueLength);if (!handleArray) {map.put(prefix, node);} else {for(int i = 0; i < node.size(); ++i) {flatten(prefix + ARRAY_ELEMENT_LEFT_FLAG_VALUE + i + ARRAY_ELEMENT_RIGHT_FLAG_VALUE + NESTED_LEVEL_SEPARATOR_VALUE, node.get(i), map, handleArray, filterNullValue);}}} else {prefix = prefix.substring(0, prefix.length() - nestedLevelSeparatorValueLength);if(node.isNull() && filterNullValue) {// 过滤值为 null 的节点//do nothing} else {Object value = asSimpleValue(node);map.put(prefix, value);}}}private static Object asSimpleValue(JsonNode node) {if(node.isNull()){//检查当前节点是否为 null (isNull: 判断节点是否为 null, 而非 isEmpty2) , eg: nullreturn null;} else if (node.isTextual()) {return node.textValue();} else if (node.isNumber()) {return node.numberValue();} else {return node.isBoolean() ? node.booleanValue() : node.asText();}}
}
Y 推荐文献
- Jackson
- https://github.com/FasterXML/jackson
-
[JSON] Fastjson 之版本对比:Fastjson vs Fastjson2 - 博客园/千千寰宇
-
第三方文档
- 史上最全的Jackson框架使用教程 - CSDN //TODO
- Jackson教程 - baeldung-cn.com //todo
- Java之Jackson使用详解 - Zhihu//todo