前言
你好,我是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() + "---"); }
容器类型节点(ContainerNode)
此类节点均为ContainerNode的子类,特点是:本节点代表一个容器,里面可以装任何其它节点。
Java中容器有两种:Map和Collection。对应的Jackson也提供了两种容器节点用于表述此类数据结构:
- ObjectNode:类比Map,采用K-V结构存储。比如一个JSON结构,根节点 就是一个ObjectNode
- ArrayNode:类比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}} "大黄"
运行程序,输出:
------ValueNode值节点示例------ true:1 true:null true_ true:Person(name=YourBatman, age=18) ---true---