每棵大树,都曾只是一粒种子。本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的 专栏供以避免费学习。关注公众号【 BAT的乌托邦】逐个击破,深刻掌握,拒绝浅尝辄止。
你好,我是YourBatman。javascript
上篇文章 体验了一把ObjectMapper在数据绑定方面的应用,用起来仍是蛮方便的有木有,为啥很多人说它难用呢,着实费解。我群里问了问,主要缘由是它不是静态方法调用,而且方法名取得不那么见名之意......java
虽然ObjectMapper
在数据绑定上既能够处理简单类型(如Integer、List、Map等),也能处理彻底类型(如POJO),看似无所不能。可是,如有以下场景它依旧不太好实现:node
为了解决这些问题,Jackson提供了强大的树模型 API供以使用,这也就是本文的主要的内容。python
小贴士:树模型虽然是jackson-core模块里定义的,可是是由jackson-databind高级模块提供的实现
2.11.0
5.2.6.RELEASE
2.3.0.RELEASE
树模型可能比数据绑定更方便,更灵活。特别是在结构高度动态或者不能很好地映射到Java类的状况下,它就显得更有价值了。git
树模型是JSON数据内存树的表示形式,这是最灵活的方法,它就相似于XML的DOM解析器。Jackson提供了树模型API来生成和解析 JSON串,主要用到以下三个核心类:github
JsonNodeFactory
:顾名思义,用来构造各类JsonNode节点的工厂。例如对象节点ObjectNode、数组节点ArrayNode等等JsonNode
:表示json节点。能够往里面塞值,从而最终构造出一颗json树ObjectMapper
:实现JsonNode和JSON字符串的互转这里有个萌新的概念:JsonNode。它贯穿于整个树模型中,因此有必要先来认识它。json
JSON节点,可类比XML的DOM树节点结构来辅助理解。JsonNode是全部JSON节点的基类,它是一个抽象类,它有一个较大的特色:绝大多数的get方法均放在了此抽象类里(即便它没有实现),目的是:在不进行类型强制转换的状况下遍历结构。可是,大多数的修改方法都必须经过特定的子类类型去调用,这实际上是合理的。由于在构建/修改某个Node节点时,类型类型信息通常是明确的,而在读取Node节点时大多数时候并不 太关心节点类型。segmentfault
多个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
工厂来作。
构建JsonNode工厂类。话很少说,用几个例子跑一跑。
此类节点均为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
的子类,特色是:本节点表明一个容器,里面能够装任何其它节点。
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}} "大黄"
树模型实际上是底层流式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俩操做它:
该方法属相对较为经常使用:将任意对象(包括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的使用几乎一毛同样?
顾名思义:将一个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)) { // 一、获得一个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}
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哥根据经验,举两个树模型的实际使用示例供你参考。
这种场景其实还蛮常见的,好比有个很经典的场景即是在MQ消费中:生产者通常会巴不得把它能吐出来的属性尽量都扔出来,但对于不一样的消费者而言它们的所需每每是不同的:
譬如,生产者生产的消息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属性相对应时,树模型的所见即所得特性就很好解决了这个问题。
当数据结构高度动态化(随时可能新增、删除节点)时,使用树模型去处理是一个较好的方案(稳定以后再转为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的树模型你必须得掌握。
Author | A哥(YourBatman) |
---|---|
我的站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活跃平台 |
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |