7. Jackson用树模型处理JSON是必备技能,不信你看
每棵大树,都曾只是一粒种子。本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以免费学习。关注公众号【BAT的乌托邦】逐个击破,深入掌握,拒绝浅尝辄止。

✍前言
你好,我是YourBatman。
上篇文章 体验了一把ObjectMapper在数据绑定方面的应用,用起来还是蛮方便的有木有,为啥不少人说它难用呢,着实费解。我群里问了问,主要原因是它不是静态方法调用,并且方法名取得不那么见名之意......
虽然ObjectMapper在数据绑定上既可以处理简单类型(如Integer、List、Map等),也能处理完全类型(如POJO),看似无所不能。但是,若有如下场景它依旧不太好实现:
- 硕大的JSON串中我只想要某一个(某几个)属性的值而已
- 临时使用,我并不想创建一个POJO与之对应,只想直接使用值即可(类型转换什么的我自己来就好)
- 数据结构高度动态化
为了解决这些问题,Jackson提供了强大的树模型 API供以使用,这也就是本文的主要的内容。
小贴士:树模型虽然是jackson-core模块里定义的,但是是由jackson-databind高级模块提供的实现
版本约定
- Jackson版本:
2.11.0 - Spring Framework版本:
5.2.6.RELEASE - Spring Boot版本:
2.3.0.RELEASE
✍正文
树模型可能比数据绑定更方便,更灵活。特别是在结构高度动态或者不能很好地映射到Java类的情况下,它就显得更有价值了。
树模型
树模型是JSON数据内存树的表示形式,这是最灵活的方法,它就类似于XML的DOM解析器。Jackson提供了树模型API来生成和解析 JSON串,主要用到如下三个核心类:
JsonNodeFactory:顾名思义,用来构造各种JsonNode节点的工厂。例如对象节点ObjectNode、数组节点ArrayNode等等JsonNode:表示json节点。可以往里面塞值,从而最终构造出一颗json树ObjectMapper:实现JsonNode和JSON字符串的互转
这里有个萌新的概念:JsonNode。它贯穿于整个树模型中,所以有必要先来认识它。
JsonNode
JSON节点,可类比XML的DOM树节点结构来辅助理解。JsonNode是所有JSON节点的基类,它是一个抽象类,它有一个较大的特点:绝大多数的get方法均放在了此抽象类里(即使它没有实现),目的是:在不进行类型强制转换的情况下遍历结构。但是,大多数的修改方法都必须通过特定的子类类型去调用,这其实是合理的。因为在构建/修改某个Node节点时,类型类型信息一般是明确的,而在读取Node节点时大多数时候并不 太关心节点类型。
多个JsonNode节点构成Jackson实现的JSON树模型的基础,它是流式API中com.fasterxml.jackson.core.TreeNode接口的实现,同时它还实现了Iterable迭代器接口。
public abstract class JsonNode extends JsonSerializable.Base
implements TreeNode, Iterable<JsonNode> {
...
}
JsonNode的继承图谱如下(部分):

一目了然了吧,基本上每个数据类型都会有一个JsonNode的实现类型对应。譬如数组节点ArrayNode、数字节点NumericNode等等。
一般情况下,我们并不需要通过new关键字去构建一个JsonNode实例,而是借助JsonNodeFactory工厂来做。
JsonNodeFactory
构建JsonNode工厂类。话不多说,用几个例子跑一跑。
值类型节点(ValueNode)
此类节点均为ValueNode的子类,特点是:一个节点表示一个值。
@Test
public void test1() {
JsonNodeFactory factory = JsonNodeFactory.instance;
System.out.println("------ValueNode值节点示例------");
// 数字节点
JsonNode node = factory.numberNode(1);
System.out.println(node.isNumber() + ":" + node.intValue());
// null节点
node = factory.nullNode();
System.out.println(node.isNull() + ":" + node.asText());
// missing节点
node = factory.missingNode();
System.out.println(node.isMissingNode() + "_" + node.asText());
// POJONode节点
node = factory.pojoNode(new Person("YourBatman", 18));
System.out.println(node.isPojo() + ":" + node.asText());
System.out.println("---" + node.isValueNode() + "---");
}
运行程序,输出:
------ValueNode值节点示例------
true:1
true:null
true_
true:Person(name=YourBatman, age=18)
---true---
容器类型节点(ContainerNode)
此类节点均为ContainerNode的子类,特点是:本节点代表一个容器,里面可以装任何其它节点。
Java中容器有两种:Map和Collection。对应的Jackson也提供了两种容器节点用于表述此类数据结构:
ObjectNode:类比Map,采用K-V结构存储。比如一个JSON结构,根节点 就是一个ObjectNodeArrayNode:类比Collection、数组。里面可以放置任何节点
下面用示例感受一下它们的使用:
@Test
public void test2() {
JsonNodeFactory factory = JsonNodeFactory.instance;
System.out.println("------构建一个JSON结构数据------");
ObjectNode rootNode = factory.objectNode();
// 添加普通值节点
rootNode.put("zhName", "A哥"); // 效果完全同:rootNode.set("zhName", factory.textNode("A哥"))
rootNode.put("enName", "YourBatman");
rootNode.put("age", 18);
// 添加数组容器节点
ArrayNode arrayNode = factory.arrayNode();
arrayNode.add("java")
.add("javascript")
.add("python");
rootNode.set("languages", arrayNode);
// 添加对象节点
ObjectNode dogNode = factory.objectNode();
dogNode.put("name", "大黄")
.put("age", 3);
rootNode.set("dog", dogNode);
System.out.println(rootNode);
System.out.println(rootNode.get("dog").get("name"));
}
运行程序,输出:
------构建一个JSON结构数据------
{"zhName":"A哥","enName":"YourBatman","age":18,"languages":["java","javascript","python"],"dog":{"name":"大黄","age":3}}
"大黄"
ObjectMapper中的树模型
树模型其实是底层流式API所提出和支持的,典型API便是com.fasterxml.jackson.core.TreeNode。但通过前面文章的示例讲解可以知道:底层流式API仅定义了接口而并未提供任何实现,甚至半成品都算不上。所以说要使用Jackson的树模型还得看ObjectMapper,它提供了TreeNode等API的完整实现。
不乏很多小伙伴对ObjectMapper的树模型是一知半解的,甚至从来都没有用过,其实它是非常灵活和强大的。有了上面的基础示例做支撑,再来了解它的实现就得心应手多了。
ObjectMapper中提供了树模型(tree model) API 来生成和解析 json 字符串。如果你不想为你的 json 结构单独建类与之对应的话,则可以选择该 API,如下图所示:

ObjectMapper在读取JSON后提供指向树的根节点的指针, 根节点可用于遍历完整的树。 同样的,我们可从读(反序列化)、写(序列化)两个方面来展开。
写(序列化)
将Object写为JsonNode,ObjectMapper给我们提供了三个实用API俩操作它:

1、valueToTree(Object)
该方法属相对较为常用:将任意对象(包括null)写为一个JsonNode树模型。功能上类似于先将Object序列化为JSON串,再读为JsonNode,但很明显这样一步到位更加高效。
小贴士:高效不代表性能高,因为其内部实现好还是调用了
readTree()方法的
@Test
public void test1() {
ObjectMapper mapper = new ObjectMapper();
Person person = new Person();
person.setName("YourBatman");
person.setAge(18);
person.setDog(new Person.Dog("旺财", 3));
JsonNode node = mapper.valueToTree(person);
System.out.println(person);
// 遍历打印所有属性
Iterator<JsonNode> it = node.iterator();
while (it.hasNext()) {
JsonNode nextNode = it.next();
if (nextNode.isContainerNode()) {
if (nextNode.isObject()) {
System.out.println("狗的属性:::");
System.out.println(nextNode.get("name"));
System.out.println(nextNode.get("age"));
}
} else {
System.out.println(nextNode.asText());
}
}
// 直接获取
System.out.println("---------------------------------------");
System.out.println(node.get("dog").get("name"));
System.out.println(node.get("dog").get("age"));
}
运行程序,控制台输出:
Person(name=YourBatman, age=18, dog=Person.Dog(name=旺财, age=3))
YourBatman
18
狗的属性:::
"旺财"
3
---------------------------------------
"旺财"
3
对于JsonNode在这里补充一个要点:读取其属性,你既可以用迭代器遍历,也可以根据key(属性)直接获取,是不是和Map的使用几乎一毛一样?
2、writeTree(JsonGenerator, JsonNode)
顾名思义:将一个JsonNode使用JsonGenerator写到输出流里,此方法直接使用到了JsonGenerator这个API,灵活度杠杠的,但相对偏底层,本处仍旧给个示例玩玩吧(底层API更多详解,请参见本系列前面几篇文章):
@Test
public void test2() throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonFactory factory = new JsonFactory();
try (JsonGenerator jsonGenerator = factory.createGenerator(System.err, JsonEncoding.UTF8)) {
// 1、得到一个jsonNode(为了方便我直接用上面API生成了哈)
Person person = new Person();
person.setName("YourBatman");
person.setAge(18);
JsonNode jsonNode = mapper.valueToTree(person);
// 使用JsonGenerator写到输出流
mapper.writeTree(jsonGenerator, jsonNode);
}
}
运行程序,控制台输出:
{"name":"YourBatman","age":18,"dog":null}
3、writeTree(JsonGenerator,TreeNode)
JsonNode是TreeNode的实现类,上面方法已经给出了使用示例,所以本方法不在赘述你应该不会有意见了吧。
读(反序列化)
将一个资源(如字符串)读取为一个JsonNode树模型。

这是典型的方法重载设计,API更加友好,所有方法底层均为_readTreeAndClose()这个protected方法,可谓“万剑归宗”。
下面以最为常见的:读取JSON字符串为例,其它的举一反三即可。
@Test
public void test3() throws IOException {
ObjectMapper mapper = new ObjectMapper();
String jsonStr = "{\"name\":\"YourBatman\",\"age\":18,\"dog\":null}";
// 直接映射为一个实体对象
// mapper.readValue(jsonStr, Person.class);
// 读取为一个树模型
JsonNode node = mapper.readTree(jsonStr);
// ... 略
}
至于底层_readTreeAndClose(JsonParser)方法的具体实现,就有得捞了。不过鉴于它过于枯燥和稍有些烧脑,后面撰有专文详解,有兴趣可持续关注。
场景演练
理论和示例讲完了,光说不练假把式,下面A哥根据经验,举两个树模型的实际使用示例供你参考。
1、偌大JSON串中仅需1个值
这种场景其实还蛮常见的,比如有个很经典的场景便是在MQ消费中:生产者一般会恨不得把它能吐出来的属性尽可能都扔出来,但对于不同的消费者而言它们的所需往往是不一样的:
- 需要较多的属性值,这时候用完全数据绑定转换成POJO来操作更为方便和合理
- 需要1个(较少)的属性值,这时候“杀鸡岂能用牛刀”呢,这种case使用树模型来做就显得更为优雅和高效了
譬如,生产者生产的消息JSON串如下(模拟数据,总之你就当做它属性很多、嵌套很深就对了):
{"name":"YourBatman","age":18,"dog":{"name":"旺财","color":"WHITE"},"hobbies":["篮球","football"]}
这时候,我仅关心狗的颜色,肿么办呢?相信你已经想到了:树模型
@Test
public void test4() throws IOException {
ObjectMapper mapper = new ObjectMapper();
String jsonStr = "{\"name\":\"YourBatman\",\"age\":18,\"dog\":{\"name\":\"旺财\",\"color\":\"WHITE\"},\"hobbies\":[\"篮球\",\"football\"]}";
JsonNode node = mapper.readTree(jsonStr);
System.out.println(node.get("dog").get("color").asText());
}
运行程序,控制台输出:WHITE,目标达成。值得注意的是:如果node.get("dog")没有这个节点(或者值为null),是会抛出NPE异常的,因此请你自己保证代码的健壮性。
当你不想创建一个Java Bean与JSON属性相对应时,树模型的所见即所得特性就很好解决了这个问题。
2、数据结构高度动态化
当数据结构高度动态化(随时可能新增、删除节点)时,使用树模型去处理是一个较好的方案(稳定之后再转为Java Bean即可)。这主要是利用了树模型它具有动态可扩展的特性,满足我们日益变化的结构:
@Test
public void test5() throws JsonProcessingException {
String jsonStr = "{\"name\":\"YourBatman\",\"age\":18}";
JsonNode node = new ObjectMapper().readTree(jsonStr);
System.out.println("-------------向结构里动态添加节点------------");
// 动态添加一个myDiy节点,并且该节点还是ObjectNode节点
((ObjectNode) node).with("myDiy").put("contry", "China");
System.out.println(node);
}
运行程序,控制台输出:
-------------向结构里动态添加节点------------
{"name":"YourBatman","age":18,"myDiy":{"contry":"China"}}
说白了,也没啥特殊的。拿到一个JsonNode后你可以任意的造它,就像Map<Object,Object>一样~
✍总结
树模型(tree model) API比Jackson 流式(Streaming) API 简单了很多,不管是生成 json字符串还是解析json字符串。但是相对于自动化的数据绑定而言还是比较复杂的。
树模型(tree model) API在只需要取出一个大json串中的几个值时比较方便。如果json中每个(大部分)值都需要获得,那么这种方式便显得比较繁琐了。因此在实际应用中具体问题具体分析,但是,Jackson的树模型你必须得掌握。
推荐阅读:
- Fastjson到了说再见的时候了
- 1. 初识Jackson -- 世界上最好的JSON库
- 2. 妈呀,Jackson原来是这样写JSON的
- 3. 懂了这些,方敢在简历上说会用Jackson写JSON
- 4. JSON字符串是如何被解析的?JsonParser了解一下
- 5. JsonFactory工厂而已,还蛮有料,这是我没想到的
- 6. 二十不惑,ObjectMapper使用也不再迷惑
7. Jackson用树模型处理JSON是必备技能,不信你看的更多相关文章
- 使用 Jackson 树模型(tree model) API 处理 JSON
http://blog.csdn.net/gao1440156051/article/details/54091702 http://blog.csdn.net/u010003835/article/ ...
- 2. 妈呀,Jackson原来是这样写JSON的
没有人永远18岁,但永远有人18岁.本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈.MyBatis.JVM.中间件等小而美的专栏供以免费学习.关注公众 ...
- 使用Jackson在Java中处理JSON
在工作中实际使用到Java处理JSON的情况,且有很大部分都使用的是开源工具Jackson实现的. 一.入门 Jackson中有个ObjectMapper类很是实用,用于Java对象与JSON的互换. ...
- Jackson 框架,轻易转换JSON
Jackson 框架,轻易转换JSON Jackson可以轻松的将Java对象转换成json对象和xml文档,同样也可以将json.xml转换成Java对象. 前面有介绍过json-lib这个框架,在 ...
- Java json设置时间格式,Jackson设置时间格式,json设置单引号
Java json设置时间格式,Jackson设置时间格式,json设置单引号 >>>>>>>>>>>>>>> ...
- Jackson 框架,轻易转换JSON【转】
Jackson 框架,轻易转换JSON Jackson可以轻松的将Java对象转换成json对象和xml文档,同样也可以将json.xml转换成Java对象. 前面有介绍过json-lib这个框架,在 ...
- 文档模型(JSON)使用介绍
一.背景 E.F.Codd在1970年首次提出了数据库系统的关系模型,从此开创了数据库关系方法和关系数据理论的研究,为数据库技术奠定了理论基础,数据库技术也开始蓬勃发展.而随着几大数据库厂商陆续发布的 ...
- sklearn中树模型可视化的方法
在机器学习的过程中,我们常常会用到树模型的方式来解决我们的问题.在工业界,我们不仅要针对某个问题利用机器学习的方法来解决问题,而且还需要能力解释其中的原理或原因.今天主要在这里记录一下树模型是怎么做可 ...
- 特征选择:方差选择法、卡方检验、互信息法、递归特征消除、L1范数、树模型
转载:https://www.cnblogs.com/jasonfreak/p/5448385.html 特征选择主要从两个方面入手: 特征是否发散:特征发散说明特征的方差大,能够根据取值的差异化度量 ...
随机推荐
- 比较两个等长的字符串,若相同,则输出Match!,若不同,则输出No Match!
文章目录 问题 代码 运行结果 问题 比较两个等长的字符串,若相同,则输出Match!,若不同,则输出No Match! 代码 data segment str1 db 'ASDFGHJKL';字符串 ...
- 初学编程丨从零开始学习编程的基本路线,BAT程序员亲手总结!
编程并不是说代码怎么写,框架怎么用,业务怎么转换为代码逻辑,这些都不是编程的要素(但却是工作的刚需......).我认为按照下面这个路线来学习编程,会使自己在学习的路途上少去很多问题(比如为啥会有多线 ...
- Python自动化运维 技术与最佳实践PDF高清完整版免费下载|百度云盘|Python基础教程免费电子书
点击获取提取码:7bl4 一.内容简介 <python自动化运维:技术与最佳实践>一书在中国运维领域将有"划时代"的重要意义:一方面,这是国内第一本从纵.深和实践角度探 ...
- java开发-flyway
数据库版本管理工具 什么是数据库版本管理? 做过开发的小伙伴们都知道,实现一个需求时,一般情况下都需要设计到数据库表结构的修改.那么我们怎么能保证项目多人开发时,多个数据库环境(测试,生产环境)能够保 ...
- day12. 闭包
一.概念 """ 如果内函数使用了外函数的局部变量, 并且外函数把内函数返回出来的过程,叫做闭包 里面的内函数是闭包函数 """ 二.基本语 ...
- 【NOIP2016】换教室 题解(期望DP)
前言:状态贼鸡儿多,眼睛快瞎了. ----------------------- 题目链接 题目大意:给定$n(课程数),m(可换次数),v(教室数),e(无向边数)$,同时给定原定教室$c[i]$和 ...
- (恐怕是)写得最通俗易懂的一篇关于HashMap的文章——xx大佬这样说
先看再点赞,给自己一点思考的时间,微信搜索[沉默王二]关注这个有颜值却假装靠才华苟且的程序员. 本文 GitHub github.com/itwanger 已收录,里面还有一线大厂整理的面试题,以及我 ...
- Python稳居编程语言榜首,看完这篇总结,你就明白为什么要学它了
最近,网上流传一组<人工智能实验教材>的图片,照片火起来的原因是教材是为幼儿园的小朋友们设计的! Python被列入小学.初高中教材已不是新鲜事,现在又成功“入侵”了幼儿园,对此有网友调侃 ...
- sockjs.js?9be2:1606 GET http://192.168.1.101:8080/sockjs-node/info?t=1583642185049 net::ERR_CONNECTION_TIMED_OUT错误连接方式解决方法
在使用vue-cli脚手架创建项目的时候,在cnpm create app命令后,项目创建成功后通过npm run serve命令运行以后,控制台报错,sockjs.js?9be2:1606 GET ...
- 总结笔记 | 深度学习之Pytorch入门教程
笔记作者:王博Kings 目录 一.整体学习的建议 1.1 如何成为Pytorch大神? 1.2 如何读Github代码? 1.3 代码能力太弱怎么办? 二.Pytorch与TensorFlow概述 ...