JSON多层嵌套复杂结构数据扁平化处理转为行列数据
背景
公司的中台产品,需要对外部API接口返回的JSON数据进行采集入湖,有时候外部API接口返回的JSON数据层级嵌套比较深,举个栗子:

上述的JSON数据中,最外层为请求返回对象,data里面包含返回的业务数据,业务数据按照学校 / 班级 / 学生进行嵌套
在数据入湖时,需要按照最内层的学生视角将数据拆分为行列数据,最终的拆分结果如下:

由于对接的外部API接口返回的JSON数据结构不是统一的、固定的,所以需要通过一种算法对每一层对象、数组进行遍历和钻取,实现JSON数据的扁平化
网上找了一些JSON扁平化的中间件,例如:Json2Flat在扁平化处理过程不太完美,不支持跨层级的数组嵌套结构
所以决定自己实现扁平化处理
关键代码如下:
public class LinkedNode {
private LinkedNode parent;
private String parentName;
private Map<String, Object> data;
public LinkedNode(LinkedNode parent, String parentName, Map<String, Object> data) {
this.parent = parent;
this.parentName = parentName;
this.data = data;
}
}
public class JSONFlatProcessor {
private LinkedList<LinkedNode> nodes;
private LinkedList<String> column;
private List<Object[]> data;
public void find(LinkedNode parent, String parentName, Map<String, Object> data) {
LinkedNode node = new LinkedNode(parent, parentName, data);
if (!hasObjectOrArray(data)) {
nodes.add(node);
} else {
for (Map.Entry entry : data.entrySet()) {
if (entry.getValue() instanceof Map) {
find(node, String.valueOf(entry.getKey()), (Map<String, Object>) entry.getValue());
} else if (isObjectArray(entry.getValue())) {
find(node, String.valueOf(entry.getKey()), (List<Map<String, Object>>) entry.getValue());
}
}
}
}
public void find(LinkedNode parent, String parentName, List<Map<String, Object>> data) {
for (Map<String, Object> item : data) {
find(parent, parentName, item);
}
}
protected Boolean hasObjectOrArray(Map<String, Object> item) {
Object field;
for (Map.Entry entry : item.entrySet()) {
field = entry.getValue();
if (field instanceof Map || isObjectArray(field)) {
return Boolean.TRUE;
}
}
return Boolean.FALSE;
}
protected Boolean isObjectArray(Object object) {
return object instanceof List
&& !CollectionUtils.isEmpty((List) object)
&& ((List) object).get(0) instanceof Map;
}
public JSONFlatProcessor process(List<Map<String, Object>> data) {
nodes = new LinkedList<>();
find(null, null, data);
return this;
}
public JSONFlatProcessor process(Map<String, Object> data) {
nodes = new LinkedList<>();
find(null, null, data);
return this;
}
public LinkedList<LinkedNode> getNodes() {
return nodes;
}
public List<String> getColumn() {
if (CollectionUtils.isEmpty(nodes)) {
return Collections.emptyList();
}
column = new LinkedList<>();
collectColumn(nodes.getFirst());
return column;
}
protected void collectColumn(LinkedNode node) {
List<String> innerColumn = new ArrayList<>(node.getData().size());
String columnBuilder;
for (Map.Entry entry : node.getData().entrySet()) {
if (!(entry.getValue() instanceof Map || isObjectArray(entry.getValue()))) {
columnBuilder = null == node.getParentName()? String.valueOf(entry.getKey()) : String.format("%s.%s", node.getParentName(), entry.getKey());
innerColumn.add(columnBuilder);
}
}
column.addAll(0, innerColumn);
if (null != node.getParent()) {
collectColumn(node.getParent());
}
}
public List<Object[]> getData() {
if (CollectionUtils.isEmpty(nodes)) {
return Collections.emptyList();
}
data = new ArrayList<>(nodes.size());
LinkedList<Object> container;
for (LinkedNode node : nodes) {
container = new LinkedList<>();
collectData(node, container);
data.add(container.toArray());
}
return data;
}
protected void collectData(LinkedNode node, LinkedList<Object> container) {
List<Object> innerData = new ArrayList<>(node.getData().size());
for (Map.Entry entry : node.getData().entrySet()) {
if (!(entry.getValue() instanceof Map || isObjectArray(entry.getValue()))) {
innerData.add(entry.getValue());
}
}
container.addAll(0, innerData);
if (null != node.getParent()) {
collectData(node.getParent(), container);
}
}
protected static class CollectionUtils {
public static boolean isEmpty(Collection<?> collection) {
return (collection == null || collection.isEmpty());
}
}
}
public class MainTests {
public static void main(String[] args) throws Exception {
String jsonStr = "{\"code\":200,\"requestId\":\"1680177848458\",\"data\":[{\"school\":\"xxx市第一实验小学\",\"no\":\"1001\",\"class\":[{\"name\":\"一(1)班\",\"teacher\":\"吴老师\",\"student\":[{\"name\":\"张同学\",\"age\":6},{\"name\":\"王同学\",\"age\":7}]}]},{\"school\":\"xxx市第二实验小学\",\"no\":\"1002\",\"class\":[{\"name\":\"一(2)班\",\"teacher\":\"陈老师\",\"student\":[{\"name\":\"欧阳同学\",\"age\":6}]}]}]}";
ObjectMapper jsonMapper = new ObjectMapper();
// List<Map<String, Object>> map = jsonMapper.readValue(jsonStr, List.class);
Map<String, Object> map = jsonMapper.readValue(jsonStr, Map.class);
JSONFlatProcessor processor = new JSONFlatProcessor().process(map);
System.out.println("数据条数: " + processor.getNodes().size());
System.out.println("字段名: " + processor.getColumn());
System.out.println("首行数据: " + new ObjectMapper().writeValueAsString(processor.getData().get(0)));
}
}
数据条数: 3
字段名: [code, requestId, data.school, data.no, class.name, class.teacher, student.name, student.age]
首行数据: [200,"1680177848458","xxx市第一实验小学","1001","一(1)班","吴老师","张同学",6]
JSON多层嵌套复杂结构数据扁平化处理转为行列数据的更多相关文章
- 【SpringBoot】 Java中如何封装Http请求,以及JSON多层嵌套解析
前言 本文中的内容其实严格来说不算springboot里面的特性,属于JAVA基础,只是我在项目中遇到了,特归纳总结一下. HTTP请求封装 目前JAVA对于HTTP封装主要有三种方式: 1. JAV ...
- Json多层嵌套,要怎么提取?
一直用Jmeter的Json Extactor,对于多层的Json嵌套,很好用,自己写代码的时候,总是遇到各种Exception 看了网上的资料,整理一下 1. 最简单的JSON提取,只有一层的时候 ...
- mybatis 注解写法 多层嵌套foreach,调用存储过程,批量插入数据
@Select("<script>" + "DECLARE @edi_Invoice_Details edi_Invoice_Details;" + ...
- 【JS简洁之道小技巧】第一期 扁平化数组
介绍两种方法,一是ES6的flat,简单粗暴.二是递归,也不麻烦. flat ES6自带了flat方法,用于使一个嵌套的数组扁平化,默认展开一个嵌套层.flat方法接收一个数字类型参数,参数值即嵌套层 ...
- ASP.NET提取多层嵌套json数据的方法
本文实例讲述了ASP.NET利用第三方类库Newtonsoft.Json提取多层嵌套json数据的方法,具体例子如下. 假设需要提取的json字符串如下: {"name":&quo ...
- .net(c#)提取多层嵌套的JSON
Newtonsoft.Json.Net20.dll 下载请访问http://files.cnblogs.com/hualei/Newtonsoft.Json.Net20.rar 在.net 2.0中提 ...
- [转]easyui tree 模仿ztree 使用扁平化加载json
原文地址:http://my.oschina.net/acitiviti/blog/349377 参考文章:http://www.jeasyuicn.com/demo/treeloadfilter.h ...
- c#多层嵌套Json
Newtonsoft.Json.Net20.dll 下载请访问http://files.cnblogs.com/hualei/Newtonsoft.Json.Net20.rar 在.net 2.0中提 ...
- 提取多层嵌套Json数据
在.net 2.0中提取这样的json {"name":"lily","age":23,"addr":{"ci ...
- 多层嵌套的json数据
很多时候我们见到的json数据都是多层嵌套的,就像下面这般: {"name":"桔子桑", "sex":"男", , & ...
随机推荐
- arrch架构部署redis,报错: ignore-warnings ARM64-COW-BUG
arrch架构服务器redis部署完成后,启动报错.做个记录. arrch架构的redis安装包 下载链接:https://pan.baidu.com/s/1TMXNpMvMDWRFD1f5km7Mw ...
- 零代码,使用 Dify 和 Laf 两分钟接入企业微信 AI 机器人
Dify 允许创建 AI 应用,并提供二次开发的能力.这里我将演示创建一个法律问答助手的 AI 应用,称作"知法".在本篇教程中,我将指导你为"知法"接入企业微 ...
- 揭秘ChatGPT,如何打造自己的自定义指令
一.ChatGPT-0720更新 又在深夜,正要打开ChatGPT官网测试下pdf对话功能,发现ChatGPT又有更新.本次更新总结有2点: 1.对于Plus用户,GPT-4的使用限额从25条/3h提 ...
- Jenkins持续集成入门到精通(入门篇)
1. 什么是持续集成 持续集成(Continuous integration,简称CI)指的是频繁将代码集成到主干.它的目的,就是让产品可以快速迭代,同时保持高质量.核心措施,代码集成到主干之前,必须 ...
- .NET Core 实现Excel的导入导出
目录 前言 NPOI简介 一.安装相对应的程序包 1.1.在 "管理NuGet程序包" 中的浏览搜索:"NPOI" 二.新建Excel帮助类 三.调用 3.1. ...
- 前端三件套系例之JS——JavaScript基础、JavaScript基本数据类型、JavaScript函数
文章目录 1 JavaScript基础 1.JavaScript是什么 2.JavaScript介绍 2-1 ECMAScript和JavaScript的关系 2-2 ECMAScript的历史 3. ...
- code2uml使用教程
通常的开发顺序是 先设计uml然后进行code编程. 但是很多时候我们是先看到code源码,却不知道uml关系如何. 特别是写论文的时候也是很需要的. 这个code2uml,就是辛苦搜寻到的结果,可以 ...
- CCF CSP认证注册、报名、查询成绩、做模拟题等答疑
CCF CSP认证注册.报名.查询成绩.做模拟题等答疑 CCF CSP认证中心将考生在注册,或报名,或查询成绩,或历次真题练习时遇到的问题进行汇总,并给出解决方法,具体如下: 1.注册时,姓名可否随意 ...
- Go接口 - 构建可扩展Go应用
本文深入探讨了Go语言中接口的概念和实际应用场景.从基础知识如接口的定义和实现,到更复杂的实战应用如解耦与抽象.多态.错误处理.插件架构以及资源管理,文章通过丰富的代码示例和详细的解释,展示了Go接口 ...
- 我们又组织了一次欧洲最大开源社区活动,Hugging Face 博客欢迎社区成员发帖、Hugging Chat 功能更新!
每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...