c#将平铺列表转换为树形结构(支持孤儿节点作为独立根节点)

cs 复制代码
 public class ListToTree<T> where T : new()
 {
     /// <summary>
     /// 将平铺列表转换为树形结构(标准版本)
     /// </summary>
     /// <param name="lists">原始平铺数据列表</param>
     /// <param name="code">节点唯一标识字段名(如 "id")</param>
     /// <param name="parentCode">父节点标识字段名(如 "parent")</param>
     /// <param name="value">根节点的父节点值(通常为 "0" 或空字符串)</param>
     /// <param name="childNodeName">子节点集合的字段名,默认为 "children"</param>
     /// <returns>树形结构的列表</returns>
     public static List<T> ListToTrees(List<T> lists, string code, string parentCode, string value, string childNodeName = "children")
     {
         if (lists.Count > 0)
         {
             var tempLists = lists
                 .Where(m => (string?)m?.GetType().GetProperty(parentCode)?.GetValue(m, null) == value).ToList();
             if (tempLists.Count > 0)
             {
                 var treeDatas = new List<T>();
                 T t;
                 Type? type;
                 foreach (var obj in tempLists)
                 {
                     t = obj;
                     type = obj?.GetType();

                     var childs = ListToTrees(lists, code, parentCode,
                         (string)(type?.GetProperty(code)?.GetValue(t, null) ?? ""), childNodeName);
                     if (childs.Count > 0)
                     {
                         type?.GetProperty(childNodeName)?.SetValue(t, childs, null);
                     }

                     treeDatas.Add(t);
                 }

                 return treeDatas;
             }
         }

         return new List<T>();
     }

     /// <summary>
     /// 将平铺列表转换为树形结构(支持孤儿节点作为独立根节点)
     /// 当某个节点的父节点在列表中不存在时,该节点自身作为根节点保留在结果中
     /// </summary>
     /// <param name="lists">原始平铺数据列表</param>
     /// <param name="code">节点唯一标识字段名(如 "id")</param>
     /// <param name="parentCode">父节点标识字段名(如 "parent")</param>
     /// <param name="value">根节点的父节点值(通常为 "0" 或空字符串)</param>
     /// <param name="childNodeName">子节点集合的字段名,默认为 "children"</param>
     /// <returns>树形结构的列表,包含所有节点(包括孤儿节点)</returns>
     public static List<T> ListToTreesWithOrphan(List<T> lists, string code, string parentCode, string value, string childNodeName = "children")
     {
         var result = new List<T>();
         var processedIds = new HashSet<string>();

         var rootNodes = lists
             .Where(m => (string?)m?.GetType().GetProperty(parentCode)?.GetValue(m, null) == value)
             .ToList();

         foreach (var root in rootNodes)
         {
             BuildTree(lists, root, code, parentCode, childNodeName, processedIds, result);
         }

         var orphanNodes = lists
             .Where(m => !processedIds.Contains((string)(m?.GetType().GetProperty(code)?.GetValue(m, null) ?? "")))
             .ToList();

         foreach (var orphan in orphanNodes)
         {
             BuildTree(lists, orphan, code, parentCode, childNodeName, processedIds, result);
         }

         return result;
     }

     /// <summary>
     /// 递归构建子树(内部方法)
     /// </summary>
     /// <param name="lists">原始平铺数据列表</param>
     /// <param name="node">当前节点</param>
     /// <param name="code">节点唯一标识字段名</param>
     /// <param name="parentCode">父节点标识字段名</param>
     /// <param name="childNodeName">子节点集合的字段名</param>
     /// <param name="processedIds">已处理过的节点ID集合(用于去重)</param>
     /// <param name="result">最终结果列表</param>
     private static void BuildTree(List<T> lists, T node, string code, string parentCode, string childNodeName, HashSet<string> processedIds, List<T> result)
     {
         var type = node?.GetType();
         var nodeId = (string)(type?.GetProperty(code)?.GetValue(node, null) ?? "");

         if (processedIds.Contains(nodeId))
             return;

         processedIds.Add(nodeId);

         var children = lists
             .Where(m => (string?)m?.GetType().GetProperty(parentCode)?.GetValue(m, null) == nodeId)
             .ToList();

         if (children.Count > 0)
         {
             var childList = new List<T>();
             foreach (var child in children)
             {
                 BuildTree(lists, child, code, parentCode, childNodeName, processedIds, childList);
             }
             type?.GetProperty(childNodeName)?.SetValue(node, childList, null);
         }

         result.Add(node);
     }
 }

使用方法 :

cs 复制代码
 public async Task<List<MenuListVO>> GetMenuList(MenuListDTO menuListDTO)
 {
     List<IConditionalModel> conditionalModels = new List<IConditionalModel>();
     var query = db.Queryable<h_bd_menu>().AS("h_bd_menu")
         .WhereIF(!StringExtension.IsNullOrEmpty(menuListDTO.title), v => v.title.Contains(menuListDTO.title));
     if (!StringExtension.IsNullOrEmpty(menuListDTO.query))
     {
         conditionalModels = db.Utilities.JsonToConditionalModels(menuListDTO.query.ToJson());
     }

     var list = await query.Where(conditionalModels).ToListAsync();
     ConsoleHelper.WriteLine(list.ToJson());
     var rootvalue = list.Any() ? list.Min(v => v.parent) : 0;
     var menuTree = ListToTree<MenuListVO>.ListToTreesWithOrphan(App.Mapper.MapTo<List<MenuListVO>>(list), "id", "parent", rootvalue.ToString());
     ConsoleHelper.WriteLine(menuTree.ToJson());

     return App.Mapper.MapTo<List<MenuListVO>>(menuTree);
 }

转换前数据

javascript 复制代码
[
    {
        "title": "测试表单",
        "number": "test",
        "component": "layouts/DetailLayout.vue",
        "desc": "",
        "name": "AppTest",
        "icon": "canyinkafei",
        "sortid": 1,
        "parent": 0,
        "islock": false,
        "id": 2039617586328956928,
        "created_by": 1913844754068672512,
        "created_time": "2026-04-02 16:15:01",
        "IsDeleted": false
    },
    {
        "title": "表单",
        "number": "test-pending",
        "component": "",
        "desc": "二次封装表单",
        "name": "AppTestTestPending",
        "icon": "jiaoseguanli1",
        "sortid": 1,
        "parent": 2039617586328956928,
        "islock": false,
        "id": 2039629889204056064,
        "created_by": 1913844754068672512,
        "created_time": "2026-04-02 17:03:54",
        "IsDeleted": false
    },
    {
        "title": "表单原版",
        "number": "form",
        "component": "views/template/Form.vue",
        "desc": "",
        "name": "AppTestTestPendingForm",
        "icon": "geren-wodeweituo",
        "sortid": 1,
        "parent": 2039629889204056064,
        "islock": true,
        "id": 2039642027431751680,
        "created_by": 1913844754068672512,
        "created_time": "2026-04-02 17:52:08",
        "IsDeleted": false
    },
    {
        "title": "配置表单",
        "number": "config-form",
        "component": "views/template/ConfigFormExample.vue",
        "desc": "",
        "name": "AppTestTestPendingConfigForm",
        "icon": "canyinkafei",
        "sortid": 3,
        "parent": 2039629889204056064,
        "islock": false,
        "id": 2039642271640907776,
        "created_by": 1913844754068672512,
        "created_time": "2026-04-02 17:53:06",
        "IsDeleted": false
    },
    {
        "title": "菜单管理",
        "number": "menumanage",
        "component": "views/menu/index.vue",
        "desc": "",
        "name": "AppTestTestPendingMenuManage",
        "icon": "geren-wodeweituo",
        "sortid": 1,
        "parent": 2039642863855661056,
        "islock": false,
        "id": 2039642995829436416,
        "created_by": 1913844754068672512,
        "created_time": "2026-04-02 17:55:59",
        "IsDeleted": false
    }
]

转化后数据

javascript 复制代码
[
    {
        "id": "2039617586328956928",
        "title": "测试表单",
        "number": "test",
        "component": "layouts/DetailLayout.vue",
        "desc": "",
        "name": "AppTest",
        "icon": "canyinkafei",
        "sortid": "1",
        "parent": "0",
        "islock": false,
        "children": [
            {
                "id": "2039629889204056064",
                "title": "表单",
                "number": "test-pending",
                "component": "",
                "desc": "二次封装表单",
                "name": "AppTestTestPending",
                "icon": "jiaoseguanli1",
                "sortid": "1",
                "parent": "2039617586328956928",
                "islock": false,
                "children": [
                    {
                        "id": "2039642027431751680",
                        "title": "表单原版",
                        "number": "form",
                        "component": "views/template/Form.vue",
                        "desc": "",
                        "name": "AppTestTestPendingForm",
                        "icon": "geren-wodeweituo",
                        "sortid": "1",
                        "parent": "2039629889204056064",
                        "islock": true
                    },
                    {
                        "id": "2039642271640907776",
                        "title": "配置表单",
                        "number": "config-form",
                        "component": "views/template/ConfigFormExample.vue",
                        "desc": "",
                        "name": "AppTestTestPendingConfigForm",
                        "icon": "canyinkafei",
                        "sortid": "3",
                        "parent": "2039629889204056064",
                        "islock": false
                    }
                ]
            }
        ]
    },
    {
        "id": "2039642995829436416",
        "title": "菜单管理",
        "number": "menumanage",
        "component": "views/menu/index.vue",
        "desc": "",
        "name": "AppTestTestPendingMenuManage",
        "icon": "geren-wodeweituo",
        "sortid": "1",
        "parent": "2039642863855661056",
        "islock": false
    }
]
相关推荐
陈随易2 小时前
在 VSCode 里,把项目一键部署到服务器
前端·后端·程序员
Highcharts.js2 小时前
教程:基于 React + Highcharts 构建一个单页应用程序、按需数据拉取与图表渲染
开发语言·前端·数据结构·react.js·前端框架·highcharts·页面应用
To_OC3 小时前
我被 useState 坑了两次之后,终于把它的脾气摸透了
前端·javascript·react.js
鱼樱前端3 小时前
我用 Claude Code 后,编码效率翻 3 倍。但更值钱的是别的。
前端·后端·ai编程
_lucas3 小时前
给知识库网站接入AI问答
前端·ai编程·全栈
头发还在的女程序员4 小时前
医院陪诊管理系统怎么选择?——2026 年选型避坑与架构参考
java·开发语言·陪诊系统·陪诊app·医院陪诊陪护
前端糕手4 小时前
前端面试题大全:JavaScript + Vue3 + React + TypeScript + 工程化 + 性能优化
前端
CodeStats4 小时前
【Spring事务】Spring事务注解 @Transactional 完整体系:从 MySQL 隔离级别到 MyBatis 原理详解
java·spring·mybatis·事务·transactional
我命由我123455 小时前
Android 开发问题:为 PDFView 设置一个带有黑色边框的背景 drawable,但边框没有生效
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
我不叫武5 小时前
一个用 Rust 写的离线编码查询桌面工具
前端