每棵大树,都曾只是一粒种子。本文已被 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 应用也不再蛊惑
♥关注 A 哥♥
Author | A 哥(YourBatman) |
---|---|
集体站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
沉闷平台 |
|
公众号 | BAT 的乌托邦(ID:BAT-utopia) |
常识星球 | BAT 的乌托邦 |
每日文章举荐 | 每日文章举荐 |