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
    }
]
相关推荐
雨季mo浅忆2 小时前
第四项目梳理
前端·面试·vue2
a1117762 小时前
三维地图可视化 ThreeJS vue 开源项目
前端·javascript·vue.js
李少兄2 小时前
Windows系统JDK安装与环境配置指南(2026年版)
java·开发语言·windows
好家伙VCC4 小时前
**神经编码新视角:用Python实现生物启发的神经信号压缩与解码算法**在人工智能飞速发展的今天
java·人工智能·python·算法
接着奏乐接着舞。4 小时前
部署BFF与前端的踩坑与经验记录
前端·node.js
一灯架构10 小时前
90%的人答错!一文带你彻底搞懂ArrayList
java·后端
xiaoshuaishuai811 小时前
C# 接入 OpenClaw
windows·visualstudio·c#
小李子呢021111 小时前
前端八股CSS(2)---动画的实现方式
前端·javascript
Y40900111 小时前
【多线程】线程安全(1)
java·开发语言·jvm