news 2026/8/9 6:18:20

Jackson:Spring默认的JSON解析器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Jackson:Spring默认的JSON解析器

前言

JSON 是现在很热门的数据传输格式。但是,在数据传输格式上面,并不是只有 JSON 这一种选择。

  • Java 的序列化 IO 流:可以实现 Java 对象和字节数组的相互转换,从而在不同机器上传输 Java 对象。但是数据的转换依赖于 Java 的序列化 IO 流,如果其他系统不是 Java 编写的,如:python、node.js 等,它们可没有 Java 的 API,可以接收,但是无法解析数据。
  • XML 文档:基于自定义标签来保存并解释数据,可读性好不好要具体情况具体分析,而且全平台通用。但是 XML 文档占用的空间比较大,因为标签需要闭合,所以标签的结构是:<xxx></xxx>,这两个 xxx 是完全相同的,冗余比较严重。

与此相比,JSON 更加轻量化,一样是全平台通用,可读性一样要具体问题具体分析,在 js 或者 python 中已经提供了内置方法可以处理 JSON。

那现在问题来了,Java 这么热门,活跃了这么多年,一定也有内置的 API 可以处理 JSON 吧。

很可惜,在 Java SE 中并没有内置的 API 可以处理 JSON,Java EE 倒是有。

但是 Java EE 不是直接安装 JDK 就可以用的,Java EE 其实就是在 Java SE 的基础上封装了一些依赖,最基础的 Java SE 是不支持处理 JSON 的。

所以,如果我们要在 Java 中处理 JSON 的话,有两种选择:

  • 自己编写处理 JSON 的代码
  • 使用别人已经写好的处理 JSON 的代码

OK啊,那这里我也是直接忽略第一个选择了,直接介绍第二个选择吧,自己的编程技术还是囊中羞涩啊。

Jackson 就是一个可以处理 JSON 的库函数,并且 Spring 默认的 JSON 解析器就是 Jackson,也就是在 Spring 项目中可以直接用 Jackson,不需要引入其他处理 JSON 的库函数了。

准备工作

首先当然是引入依赖,正如前文所说,Spring 自带了 Jackson,只要引入了 spring-boot-starter-web 或者 spring-boot-starter-webflux 就可以直接使用 Jackson 了。

如果是非 Spring 项目,需要引入以下依赖:

<dependency> <groupId>tools.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>3.2.0</version> </dependency>

这个版本比较新,跟老版本的 API 有些是不一样的,可以去 maven 仓库:Maven Repository: Search/Browse/Explore 找一下其他的版本。

当然 Spring Boot 的版本不同,用到的依赖版本也不同。

对象 -> JSON

public class Main { public static void main(String[] args) { ObjectMapper objectMapper = new ObjectMapper(); User user = new User("李华", "12345", 18, List.of(1, 2, 3, 4, 5), new User.School("广州大学", "广州")); String json = objectMapper.writeValueAsString(user); // {"age":18,"list":[1,2,3,4,5],"password":"12345","school":{"address":"广州","name":"广州大学"},"username":"李华"} System.out.println(json); } } @NoArgsConstructor @AllArgsConstructor @Data class User { private String username; private String password; private Integer age; private List<Integer> list; private School school; @NoArgsConstructor @AllArgsConstructor @Data static class School { private String name; private String address; } }

ObjectMapper 就是 Jackson 提供的入口类,可以通过 ObjectMapper 的对象调用方法处理 JSON。

在 ObjectMapper 中就有一个非常简单粗暴的方法:public String writeValueAsString(Object value),将任何传入的对象转成 JSON 字符串,并且可以自动处理对象之间的嵌套关系。

但是,这个方法也不是完美的,在处理 Map 类型的时候有一些问题。

public class Main { public static void main(String[] args) { ObjectMapper objectMapper = new ObjectMapper(); HashMap<User, Integer> map1 = new HashMap<>(); HashMap<Integer, User> map2 = new HashMap<>(); User user = new User("李华", "12345", 18); map1.put(user, 1); map2.put(1, user); // {"User(username=李华, password=12345, age=18)":1} System.out.println(objectMapper.writeValueAsString(map1)); // {"1":{"age":18,"password":"12345","username":"李华"}} System.out.println(objectMapper.writeValueAsString(map2)); } } @NoArgsConstructor @AllArgsConstructor @Data class User { private String username; private String password; private Integer age; }

因为 Map 和 JSON 都是键值对,所以如果用 User 作为 key,在转换的时候,会调用 User 的 toString 方法得到 User 对象的字符串表示形式作为 key,所以 key 是 User(username=李华, password=12345, age=18)。

如果 User 作为 value 就没有这种情况,可以被转换成 {"username":"李华","password":"12345","age":18}。

其实这里的 Map 没有问题啊,因为键值对的 key 都是 description,描述性信息,基本上是不会用对象作为 key 的。

如果不是我要写博客,考虑的情况多了一点,我也不会发现这个情况,说明在我之前的使用过程中根本没有碰到过用对象作为 key 的情况。

我这里就以抖音的一个接口返回的 JSON 为例,可以看到 key 都是描述性文字,都是用 value 来表示对象的。

JSON -> 对象

public class Main { public static void main(String[] args) { ObjectMapper objectMapper = new ObjectMapper(); String json = """ { "username":"李华", "password":"12345", "age":18 } """; User user = objectMapper.readValue(json, User.class); System.out.println(user.getUsername()); System.out.println(user.getPassword()); System.out.println(user.getAge()); HashMap<String, Object> map = objectMapper.readValue(json, new TypeReference<HashMap<String, Object>>() { }); // password:12345 // age:18 // username:李华 for (Map.Entry<String, Object> entry : map.entrySet()) { System.out.println(entry.getKey() + ":" + entry.getValue()); } } } @NoArgsConstructor @AllArgsConstructor @Data class User { private String username; private String password; private Integer age; }

ObjectMapper 提供了 readValue 方法将字符串转成对象。如果是非泛型对象,直接传入 对象.class 即可。

如果是泛型对象,比如这里的 HashMap,就必须通过匿名内部类的方式 new TypeReference<HashMap<String, Object>>() {}。

如果直接是 HashMap.class,因为 Java 在编译期会进行泛型擦除,所以返回的没有泛型的 HashMap 对象,如下所示:

HashMap map = objectMapper.readValue(json, HashMap.class); // 李华 System.out.println(map.get("username")); for (Object o : map.keySet()) { } for (Object o : map.entrySet()) { }

如果是通过 get 方法去拿,一样能拿到,因为 get 方法的参数就是 Object 类型。但是 key 和 value 都变成 Object 类型了,还要手动转换类型。

而 new TypeReference<HashMap<String, Object>>() {},是通过匿名内部类记录了父类的泛型信息,而类的定义中的泛型信息是不会被擦除的,所以父类的泛型信息可以保留下来。

也就是 new TypeReference<HashMap<String, Object>>() {} 记录了父类:TypeReference<HashMap<String, Object> 的泛型信息,可以自动进行转换。

直接解析 JSON

虽然可以将 JSON 转成对象,但是如果是深度嵌套的 JSON 呢?为一个深度嵌套的 JSON 编写一个同样层级的 Java 类也是非常麻烦的。

所以,直接祭出最无敌的打法:直接解析 JSON 结构。

ObjectMapper objectMapper = new ObjectMapper(); String json = """ { "username":"李华", "password":"12345", "age":18 } """; JsonNode jsonNode = objectMapper.readTree(json); String username = jsonNode.path("username").asString(); String password = jsonNode.path("password").asString(); int age = jsonNode.path("age").asInt();

通过 readTree 方法解析 JSON 结构,通过 path 方法传入 key 获取 value,并且可以通过 asXxx 方法对获得的值进行类型转换。

不过上面的示例只是基础用法,下面还有进阶用法。

readTree 其实就是把 JSON 结构解析成树状结构,Jackson 根据 JSON 的特点,将树节点分成三种类型:数组节点、对象节点、数值节点。

ObjectMapper objectMapper = new ObjectMapper(); String json = """ [ { "username":"李1华", "password":"12345", "age":18 }, { "username":"李2华", "password":"12345", "age":18 }, { "username":"李3华", "password":"12345", "age":18 } ] """; ArrayNode arrayNode = (ArrayNode) objectMapper.readTree(json); for (JsonNode node : arrayNode) { ObjectNode objectNode = (ObjectNode) node; System.out.println(objectNode.path("username").asString()); }

readTree 和 path 返回的都是 JsonNode,需要强转成 ArrayNode 和 ObjectNode。从这个关系就可以看出来,JsonNode 是 ArrayNode 和 ObjectNode 的父类。

ArrayNode 就是数组嘛,所以可以直接遍历里面的所有元素;而 ObjectNode 就是一个对象,里面有一堆键值对嘛,通过 path 传入键的名字就可以获得对应的值。

path 返回的可能还是对象,或者是叶子节点,也就是那几种基本数据类型:字符串、整数、小数、布尔值,如果是 path 下面没有其他嵌套的对象了那返回的就是数值节点。

JsonNode 是一个抽象类,它下面的子类有:

ArrayNode 表示数组、ObjectNode 表示对象、而 BooleanNode/NullNode 这些节点表示数值。

如果是 ArrayNode,就可以用 path(0) 选中其中第一个元素。

如果是 ObjectNode,就可以用 path("username") 获取对象中的属性。

当然如果已经了解 JSON 的结构就不需要这么麻烦。

{ "id": "81c65c60-e25c-4f6b-a848-af81f813d3e1", "object": "chat.completion", "created": 1784880697, "model": "deepseek-v4-flash", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "你好!我是DeepSeek,由深度求索公司创造的智能助手。我能够处理各种问题,无论是文本对话、文件上传,还是阅读链接内容,我都可以胜任。目前,我专注于文字处理,不支持多模态识别,但可以对上传的图片中的文字进行读取和理解。我的知识截止日期是2025年5月,并且我支持联网搜索功能(需要手动开启)。最重要的是,我完全免费使用!有什么可以帮你的吗?😊", "reasoning_content": "嗯,用户问了一个简单的自我介绍问题。这个问题很直接,就是想知道我的身份和功能。我需要清晰说明自己是什么、能做什么。\n\n我是DeepSeek,由深度求索公司创造的AI助手。应该简明扼要地介绍核心特点:纯文本对话、文件处理能力、免费使用、有知识截止日期和上下文长度限制。同时可以用友好的语气表达愿意提供帮助的态度。\n\n想到了用\"你好\"开头,然后说明身份和基本能力,最后以开放式的提问结束,这样既回答了问题又促进了进一步交流。" }, "logprobs": null, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 10, "completion_tokens": 211, "total_tokens": 221, "prompt_tokens_details": { "cached_tokens": 0 }, "completion_tokens_details": { "reasoning_tokens": 112 }, "prompt_cache_hit_tokens": 0, "prompt_cache_miss_tokens": 10 }, "system_fingerprint": "fp_8b330d02d0_prod0820_fp8_kvcache_20260402" }

从这个 JSON 中获取 content 属性:

JsonNode node = objectMapper.readTree(json); node.path("choices").path(0).path("message").path("content").asString();

还有一件事

属性缺失和属性溢出

public class Main { public static void main(String[] args) { ObjectMapper objectMapper = new ObjectMapper(); String json1 = """ {"username":"李华"} """; String json2 = """ {"username":"李华","school":"广州大学"} """; User user1 = objectMapper.readValue(json1, User.class); User user2 = objectMapper.readValue(json2, User.class); // User(username=李华, password=null, age=null) // User(username=李华, password=null, age=null) System.out.println(user1); System.out.println(user2); } } @NoArgsConstructor @AllArgsConstructor @Data class User { private String username; private String password; private Integer age; }

如果 JSON 的属性和类的属性没有对齐,还是可以转换的。

transient 关键字

Java 的序列化 IO 流可以通过 transient 关键字指定某个属性不被序列化,Jackson 可以吗?

Jackson 提供了 @JsonIgnore 和 @JsonIgnoreProperties 注解来指定属性不被序列化。

@NoArgsConstructor @AllArgsConstructor @Data class User { private String username; private String password; @JsonIgnore private Integer age; }

age 属性不会被序列化,得到的结果是:{"password":"12345","username":"李华"}。

@NoArgsConstructor @AllArgsConstructor @Data @JsonIgnoreProperties({"age", "password"}) class User { private String username; private String password; private Integer age; }

也可以通过 @JsonIgnoreProperties 把不需要序列化的属性一锅端,得到的结果是:{"username":"李华"}。

我要变美

ObjectMapper objectMapper = new ObjectMapper(); ArrayList<User> list = new ArrayList<>(); list.add(new User("李1华", "12345", 18)); list.add(new User("李2华", "12345", 18)); list.add(new User("李3华", "12345", 18)); String json = objectMapper.writeValueAsString(list); System.out.println(json);

集合转成 JSON 之后,打印出来的效果是这样的。

这些对象挤在一起,好难看啊。

爱美之心人皆有之,怎么才能让 JSON 变美呢?

JsonNode 有一个 toPrettyString 方法,可以让 JSON 以更美观的形式打印出来。

ObjectMapper objectMapper = new ObjectMapper(); ArrayList<User> list = new ArrayList<>(); list.add(new User("李1华", "12345", 18)); list.add(new User("李2华", "12345", 18)); list.add(new User("李3华", "12345", 18)); String json = objectMapper.writeValueAsString(list); System.out.println(objectMapper.readTree(json).toPrettyString());

空悲切

如果 JSON 没有这个属性,但是我就是 path 想拿到这个属性,会怎么样呢?

public static void main(String[] args) { ObjectMapper objectMapper = new ObjectMapper(); String json = """ [{"username":"李华","school":"广州大学"}] """; JsonNode node1 = objectMapper.readTree(json).path(1); String age = objectMapper.readTree(json).path(1).path("age").asString(); if (node1.isEmpty()) System.out.println(111); }

首先,JsonNode 有 isEmpty 方法可以用来判断是否为空。

那如果我很急,我没有写判断语句呢?就像 String age = objectMapper.readTree(json).path(1).path("age").asString() 这行代码一样,path(1) 是拿不到数据的,而且也没有 age 字段,那这个 age 的取值会是什么呢?

debug 可以看到,age = "",所以其实 Jackson 已经帮你做了善后,不然获取不存在的属性应该是会报错的。

但是我对这个结果并不满意,这也太不严谨了,如果这个键值对就是 "key": "" 呢,我怎么知道它是有这个属性但是 value = "" 还是没有这个属性呢?

asString 可以传入默认值,如果没有找到这个属性就使用默认值。

public static void main(String[] args) { ObjectMapper objectMapper = new ObjectMapper(); String json = """ [{"username":"李华","school":"广州大学"}] """; String age = objectMapper.readTree(json).path(1).path("age").asString(null); if (age == null) System.out.println("没有找到age"); }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/9 6:17:51

Python自动化临时文件管理系统设计与实现

1. 临时文件管理的痛点与自动化需求在软件开发、数据处理和日常办公中&#xff0c;临时文件就像空气一样无处不在却又容易被忽视。我见过太多项目因为临时文件管理不善导致的灾难&#xff1a;某次服务器磁盘被临时日志塞满导致生产环境崩溃&#xff1b;一个数据分析项目因为临时…

作者头像 李华
网站建设 2026/8/9 6:15:25

AI原生开发时代:代码当量的价值重塑与开发者角色升级

1. 一个被误读的“死亡宣告”最近在技术圈里&#xff0c;关于“代码当量已死”的讨论又热了起来。这个说法其实不新鲜&#xff0c;每隔几年&#xff0c;当有新的开发范式或工具出现时&#xff0c;类似的论调就会冒出来。从早期的代码生成器&#xff0c;到后来的低代码/无代码平…

作者头像 李华
网站建设 2026/8/9 6:13:54

模拟实现strncpy

代码展示&#xff1a; #include <stdio.h>#include <stddef.h>//模拟strncpy char* my_strncpy(char* dest, char* src, size_t n){char* start src;while(n > 0 && *src ! \0){*dest *src;dest;src;n--;}while(n > 0){*dest \0;dest;n--;}return…

作者头像 李华
网站建设 2026/8/9 6:13:23

企业IT资产退出管理:ThinkPad与百应平台操作指南

1. 项目概述&#xff1a;企业IT资产退出管理的重要性最近帮三家中小型企业处理了IT设备回收工作&#xff0c;发现很多企业对设备退出的标准化流程存在严重认知空白。特别是使用联想百应平台的企业用户&#xff0c;在设备交接、数据清理和系统解绑环节频频踩坑。ThinkPad作为商务…

作者头像 李华
网站建设 2026/8/9 6:12:17

ShaderGraph顶点颜色节点:从原理到实战的性能优化与创意应用

1. 项目概述&#xff1a;为什么顶点颜色节点是ShaderGraph的“隐藏宝藏”&#xff1f;在Unity ShaderGraph的世界里&#xff0c;我们经常把目光聚焦在那些能产生炫酷效果的节点上&#xff0c;比如各种噪声、扭曲或者复杂的数学运算。但今天我想聊一个看似简单&#xff0c;却蕴含…

作者头像 李华