数据处理,实现JSON树形结构

文章讲述了如何从具有层级关系的数据库表中查询数据并使用递归思想将其转换成树形结构,以SpringBoot为例,创建Controller,定义数据实体TreeNode,并提供了单个父节点和多个父节点的处理模式,最后展示了测试结果。
摘要由CSDN通过智能技术生成

前言

所谓从数据库中查询的数据进行树形打印,主要因为一个表中设计了当前记录id和该记录的父级id的字段,使得该表的数据形成了级联的关系。表模拟:

字段类型注释
idint主键id信息
pidint父级id信息,一级设置为0
namevarchar名称属性

通常,我们都是将所有的记录查询出来,在通过业务处理,将数据封装成树形结构。

具体思路我们可以通过递归思想进行数据的处理和封装

具体实现

准备工作:创建一个SpringBoot工程,设计一个Controller组件模拟一个接口。

设计数据实体:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TreeNode {
    private Integer id;
    private String name;
    private Integer pid;
    // childrenNode属性用于保存当前记录的子节点信息
    // 注意:数据中对应的实体没有该字段,我们可以从数据库中查询所有记录后,再使用BeanUtils或MapStruct相关工具进行转换
    private List<TreeNode> childrenNode;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TreeNode treeNode = (TreeNode) o;
        return Objects.equals(id, treeNode.id) && Objects.equals(name, treeNode.name) && Objects.equals(pid, treeNode.pid) && Objects.equals(childrenNode, treeNode.childrenNode);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, pid, childrenNode);
    }
}

进行数据处理和封装:

@RestController
@RequestMapping("/treeNode")
@Slf4j
public class HelloController {
    /**
     * 单个父节点模式
     *
     * @return
     */
    @GetMapping("/oneFather")
    public TreeNode listOne() {
        List<TreeNode> nodes = new ArrayList<>();
        // 封装数据:模拟数据数据
        // 注意:从数据库中查询到的数据没有childrenNode属性,那我们可以使用BeanUtils和mapstruct进行对象转换
        Collections.addAll(nodes,
                TreeNode.builder().id(1).pid(0).name("江西省").build(),
                TreeNode.builder().id(2).pid(1).name("吉安市").build(),
                TreeNode.builder().id(3).pid(2).name("遂川县").build(),
                TreeNode.builder().id(5).pid(1).name("赣州市").build(),
                TreeNode.builder().id(6).pid(5).name("章贡").build()
        );
        // 获取树节点
        TreeNode root = nodes.stream().filter(node -> Objects.equals(node.getPid(), 0)).findFirst().orElse(null);
        if (root == null) {
            return null;
        }
        root.setChildrenNode(getChildren(root, nodes));
        return root;
    }

    /**
     * 多个父节点模式
     *
     * @return
     */
    @GetMapping("/manyFather")
    public Set<TreeNode> listMany() {
        List<TreeNode> nodes = new ArrayList<>();
        // 封装数据
        Collections.addAll(nodes,
                TreeNode.builder().id(1).pid(0).name("江西省").build(),
                TreeNode.builder().id(2).pid(1).name("吉安市").build(),
                TreeNode.builder().id(3).pid(2).name("遂川县").build(),
                TreeNode.builder().id(5).pid(1).name("赣州市").build(),
                TreeNode.builder().id(6).pid(5).name("章贡").build(),
                TreeNode.builder().id(7).pid(0).name("浙江省").build(),
                TreeNode.builder().id(8).pid(7).name("杭州").build()
        );
        // 封装树节点
        Set<TreeNode> root = nodes.stream().filter(node -> Objects.equals(node.getPid(), 0)).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(root)) {
            return null;
        }
        // 遍历树节点
        root.stream().forEach(rootNode -> {
            rootNode.setChildrenNode(getChildren(rootNode, nodes));
        });

        return root;
    }

    /**
     * 递归获取根节点的子节点信息
     *
     * @param root  根节点
     * @param nodes 要遍历的节点
     * @return
     */
    private List<TreeNode> getChildren(TreeNode root, List<TreeNode> nodes) {
        List<TreeNode> childrenNodes = nodes.stream()
                // 使用stream流过滤出数据的父级id信息等于根节点的id信息的数据
                .filter(node -> Objects.equals(node.getPid(), root.getId()))
                .map(childNode -> {
                    // 同时使用map方法对每个过滤后的数据进行处理,递归找到其对应的子数据
                    childNode.setChildrenNode(getChildren(childNode, nodes));
                    return childNode;
                })
                .collect(Collectors.toList());
        return childrenNodes;
    }
}

测试:

1)单个父节点模式:http://ip:port/treeNode/oneFather

{
    "id": 1,
    "name": "江西省",
    "pid": 0,
    "childrenNode": [
        {
            "id": 2,
            "name": "吉安市",
            "pid": 1,
            "childrenNode": [
                {
                    "id": 3,
                    "name": "遂川县",
                    "pid": 2,
                    "childrenNode": []
                }
            ]
        },
        {
            "id": 5,
            "name": "赣州市",
            "pid": 1,
            "childrenNode": [
                {
                    "id": 6,
                    "name": "章贡",
                    "pid": 5,
                    "childrenNode": []
                }
            ]
        }
    ]
}

2)多个父节点模式:http://ip:port/treeNode/oneFather

[
    {
        "id": 1,
        "name": "江西省",
        "pid": 0,
        "childrenNode": [
            {
                "id": 2,
                "name": "吉安市",
                "pid": 1,
                "childrenNode": [
                    {
                        "id": 3,
                        "name": "遂川县",
                        "pid": 2,
                        "childrenNode": []
                    }
                ]
            },
            {
                "id": 5,
                "name": "赣州市",
                "pid": 1,
                "childrenNode": [
                    {
                        "id": 6,
                        "name": "章贡",
                        "pid": 5,
                        "childrenNode": []
                    }
                ]
            }
        ]
    },
    {
        "id": 7,
        "name": "浙江省",
        "pid": 0,
        "childrenNode": [
            {
                "id": 8,
                "name": "杭州",
                "pid": 7,
                "childrenNode": []
            }
        ]
    }
]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值