每棵大树,都曾只是一粒种子。本文已被 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
的子类,特点是:一个节点示意一个值。
@Testpublic 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:1true:nulltrue_true:Person(name=YourBatman, age=18)---true---
容器类型节点(ContainerNode)
此类节点均为ContainerNode
的子类,特点是:本节点代表一个容器,外面能够装任何其它节点。
Java中容器有两种:Map和Collection。对应的Jackson也提供了两种容器节点用于表述此类数据结构:
ObjectNode
:类比Map,采纳K-V构造存储。比方一个JSON构造,根节点 就是一个ObjectNodeArrayNode
:类比Collection、数组。外面能够搁置任何节点
上面用示例感受一下它们的应用:
@Testpublic 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()
办法的
@Testpublic 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))YourBatman18狗的属性:::"旺财"3---------------------------------------"旺财"3
对于JsonNode在这里补充一个要点:读取其属性,你既能够用迭代器遍历,也能够依据key(属性)间接获取,是不是和Map的应用简直一毛一样?
2、writeTree(JsonGenerator, JsonNode)
顾名思义:将一个JsonNode应用JsonGenerator写到输入流里,此办法间接应用到了JsonGenerator这个API,灵便度杠杠的,但绝对偏底层,本处仍旧给个示例玩玩吧(底层API更多详解,请参见本系列后面几篇文章):
@Testpublic 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字符串为例,其它的触类旁通即可。
@Testpublic 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"]}
这时候,我仅关怀狗的色彩,肿么办呢?置信你曾经想到了:树模型
@Testpublic 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即可)。这次要是利用了树模型它具备动静可扩大的个性,满足咱们日益变动的构造:
@Testpublic 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的乌托邦 |
每日文章举荐 | 每日文章举荐 |