热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

javamapper.readtree_7.Jackson用树模型处理JSON是必备技能,不信你看

每棵大树,都曾只是一粒种子。本文已被https:www.yourbatman.cn收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小

每棵大树,都曾只是一粒种子。本文已被 https://www.yourbatman.cn 收录,里面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以免费学习。

AAffA0nNPuCLAAAAAElFTkSuQmCC

✍前言

你好,我是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的继承图谱如下(部分):

AAffA0nNPuCLAAAAAElFTkSuQmCC

一目了然了吧,基本上每个数据类型都会有一个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结构,根节点 就是一个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}}

"大黄"

ObjectMapper中的树模型

树模型其实是底层流式API所提出和支持的,典型API便是com.fasterxml.jackson.core.TreeNode。但通过前面文章的示例讲解可以知道:底层流式API仅定义了接口而并未提供任何实现,甚至半成品都算不上。所以说要使用Jackson的树模型还得看ObjectMapper,它提供了TreeNode等API的完整实现。

不乏很多小伙伴对ObjectMapper的树模型是一知半解的,甚至从来都没有用过,其实它是非常灵活和强大的。有了上面的基础示例做支撑,再来了解它的实现就得心应手多了。

ObjectMapper中提供了树模型(tree model) API 来生成和解析 json 字符串。如果你不想为你的 json 结构单独建类与之对应的话,则可以选择该 API,如下图所示:

AAffA0nNPuCLAAAAAElFTkSuQmCC

ObjectMapper在读取JSON后提供指向树的根节点的指针, 根节点可用于遍历完整的树。 同样的,我们可从读(反序列化)、写(序列化)两个方面来展开。

写(序列化)

将Object写为JsonNode,ObjectMapper给我们提供了三个实用API俩操作它:

AAffA0nNPuCLAAAAAElFTkSuQmCC

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 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树模型。

AAffA0nNPuCLAAAAAElFTkSuQmCC

这是典型的方法重载设计,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一样~

✍总结

树模型(tree model) API比Jackson 流式(Streaming) API 简单了很多,不管是生成 json字符串还是解析json字符串。但是相对于自动化的数据绑定而言还是比较复杂的。

树模型(tree model) API在只需要取出一个大json串中的几个值时比较方便。如果json中每个(大部分)值都需要获得,那么这种方式便显得比较繁琐了。因此在实际应用中具体问题具体分析,但是,Jackson的树模型你必须得掌握。

♥关注A哥♥



推荐阅读
  • 深入解析Gradle中的Project核心组件
    在Gradle构建系统中,`Project` 是一个核心组件,扮演着至关重要的角色。通过使用 `./gradlew projects` 命令,可以清晰地列出当前项目结构中包含的所有子项目,这有助于开发者更好地理解和管理复杂的多模块项目。此外,`Project` 对象还提供了丰富的配置选项和生命周期管理功能,使得构建过程更加灵活高效。 ... [详细]
  • 如何使用 net.sf.extjwnl.data.Word 类及其代码示例详解 ... [详细]
  • 本文介绍了Android动画的基本概念及其主要类型。Android动画主要包括三种形式:视图动画(也称为补间动画或Tween动画),主要通过改变视图的属性来实现动态效果;帧动画,通过顺序播放一系列预定义的图像来模拟动画效果;以及属性动画,通过对对象的属性进行平滑过渡来创建更加复杂的动画效果。每种类型的动画都有其独特的应用场景和实现方式,开发者可以根据具体需求选择合适的动画类型。 ... [详细]
  • 本文介绍了如何通过掌握 IScroll 技巧来实现流畅的上拉加载和下拉刷新功能。首先,需要按正确的顺序引入相关文件:1. Zepto;2. iScroll.js;3. scroll-probe.js。此外,还提供了完整的代码示例,可在 GitHub 仓库中查看。通过这些步骤,开发者可以轻松实现高效、流畅的滚动效果,提升用户体验。 ... [详细]
  • 深入解析Tomcat:开发者的实用指南
    深入解析Tomcat:开发者的实用指南 ... [详细]
  • Node.js 教程第五讲:深入解析 EventEmitter(事件监听与发射机制)
    本文将深入探讨 Node.js 中的 EventEmitter 模块,详细介绍其在事件监听与发射机制中的应用。内容涵盖事件驱动的基本概念、如何在 Node.js 中注册和触发自定义事件,以及 EventEmitter 的核心 API 和使用方法。通过本教程,读者将能够全面理解并熟练运用 EventEmitter 进行高效的事件处理。 ... [详细]
  • 如何在Spark数据排序过程中有效避免内存溢出(OOM)问题
    本文深入探讨了在使用Spark进行数据排序时如何有效预防内存溢出(OOM)问题。通过具体的代码示例,详细阐述了优化策略和技术手段,为读者在实际工作中遇到类似问题提供了宝贵的参考和指导。 ... [详细]
  • jQuery Flot 数据可视化插件:高效绘制图表的专业工具
    jQuery Flot 是一款高效的数据可视化插件,专为绘制各种图表而设计。该工具支持丰富的图表类型和自定义选项,适用于多种应用场景。用户可以通过其官方网站获取示例代码和下载资源,以便快速上手和使用。 ... [详细]
  • 基于Node.js的高性能实时消息推送系统通过集成Socket.IO和Express框架,实现了高效的高并发消息转发功能。该系统能够支持大量用户同时在线,并确保消息的实时性和可靠性,适用于需要即时通信的应用场景。 ... [详细]
  • 在进行网络编程时,准确获取本地主机的IP地址是一项基本但重要的任务。Winsock作为20世纪90年代初由Microsoft与多家公司共同制定的Windows平台网络编程接口,为开发者提供了一套高效且易用的工具。通过Winsock,开发者可以轻松实现网络通信功能,并准确获取本地主机的IP地址,从而确保应用程序在网络环境中的稳定运行。此外,了解Winsock的工作原理及其API函数的使用方法,有助于提高开发效率和代码质量。 ... [详细]
  • 本文探讨了如何在 Google Sheets 中通过自定义函数实现 AJAX 调用。具体介绍了编写脚本的方法,以便在电子表格中发起 AJAX 请求,从而实现数据的动态获取与更新。这种方法不仅简化了数据处理流程,还提高了工作效率。 ... [详细]
  • 使用cpphttplib构建HTTP服务器以处理带有查询参数的URL请求 ... [详细]
  • Spring Boot 实战(一):基础的CRUD操作详解
    在《Spring Boot 实战(一)》中,详细介绍了基础的CRUD操作,涵盖创建、读取、更新和删除等核心功能,适合初学者快速掌握Spring Boot框架的应用开发技巧。 ... [详细]
  • Spring框架入门指南:专为新手打造的详细学习笔记
    Spring框架是Java Web开发中广泛应用的轻量级应用框架,以其卓越的功能和出色的性能赢得了广大开发者的青睐。本文为初学者提供了详尽的学习指南,涵盖基础概念、核心组件及实际应用案例,帮助新手快速掌握Spring框架的核心技术与实践技巧。 ... [详细]
  • C#编程指南:实现列表与WPF数据网格的高效绑定方法 ... [详细]
author-avatar
熊熊粉丝6888
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有