
本文详解如何将含 pattern_type_name、pattern_name、marker_description 三字段的扁平数组,按层级关系重构为「类型 → 名称 → 描述」的树形结构,无需id字段,纯靠字符串键自动分组聚合。
本文详解如何将含 pattern_type_name、pattern_name、marker_description 三字段的扁平数组,按层级关系重构为「类型 → 名称 → 描述」的树形结构,无需id字段,纯靠字符串键自动分组聚合。
在实际数据展示场景中(如医学检验项目分类、配置项导航菜单等),我们常遇到结构扁平但语义分明的数据源:它不带显式父子ID,却天然具备层级逻辑——例如 pattern_type_name 表示顶级分类(如“Blood Sugar”),pattern_name 是其下二级分组(如“TEST”),而 marker_description 则是末端叶子节点(如“A/G ratio”)。此时,目标不是渲染HTML,而是构建一个可递归遍历、支持任意深度扩展的嵌套数组结构,便于后续用于JSON API响应、Vue/React组件树或模板引擎渲染。
核心思路:以键为路径,逐层归集
关键在于理解:字符串字段值即天然键名(key),而非需查找的值(value)。因此,转换的本质是将每条记录映射为一条“路径”:
['Blood Sugar']['TEST']['A/G ratio'] → true ['Blood Sugar']['TEST']['Albumin'] → true ['Red Blood Cell']['TEST3']['BUN'] → true
该路径结构天然规避了ID依赖,且自动去重(同名路径重复赋值无副作用)。
实现代码(PHP)
<?php
$data = '[
{"pattern_type_name":"Blood Sugar","pattern_name":"TEST","marker_description":"A\/G ratio"},
{"pattern_type_name":"Blood Sugar","pattern_name":"TEST","marker_description":"Albumin"},
{"pattern_type_name":"Blood Sugar","pattern_name":"TEST","marker_description":"Alk Phos"},
{"pattern_type_name":"Red Blood Cell","pattern_name":"TEST3","marker_description":"A\/G ratio"},
{"pattern_type_name":"Red Blood Cell","pattern_name":"TEST3","marker_description":"Albumin"},
{"pattern_type_name":"Cardiovascular","pattern_name":"TEST1","marker_description":"EX3DWSQ"},
{"pattern_type_name":"Red Blood Cell","pattern_name":"TEST4","marker_description":"FEXTAFIX"}
]';
// 1. 解析原始JSON为关联数组
$table = json_decode($data, true);
// 2. 构建三层嵌套树结构(键驱动,无ID)
$tempTable = [];
foreach ($table as $row) {
$type = trim($row['pattern_type_name']); // 建议清洗空格
$name = trim($row['pattern_name']);
$desc = trim($row['marker_description']);
// 自动创建多维键路径,值设为true(仅占位,可替换为完整对象)
$tempTable[$type][$name][$desc] = true;
}
// 3. 可选:转换为标准树形数组(含label/value等字段,便于前端消费)
$tree = [];
foreach ($tempTable as $type => $names) {
$typeNode = [
'label' => $type,
'children' => []
];
foreach ($names as $name => $descriptions) {
$nameNode = [
'label' => $name,
'children' => array_map(function($desc) {
return ['label' => $desc, 'isLeaf' => true];
}, array_keys($descriptions))
];
$typeNode['children'][] = $nameNode;
}
$tree[] = $typeNode;
}
// 输出结果(JSON格式化便于查看)
header('Content-Type: application/json; charset=utf-8');
echo json_encode($tree, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
?>输出示例(精简版)
[
{
"label": "Blood Sugar",
"children": [
{
"label": "TEST",
"children": [
{"label": "A/G ratio", "isLeaf": true},
{"label": "Albumin", "isLeaf": true},
{"label": "Alk Phos", "isLeaf": true}
]
}
]
},
{
"label": "Red Blood Cell",
"children": [
{
"label": "TEST3",
"children": [{"label": "A/G ratio", "isLeaf": true}, ...]
},
{
"label": "TEST4",
"children": [{"label": "FEXTAFIX", "isLeaf": true}]
}
]
}
]注意事项与最佳实践
- ✅ 键值清洗:务必对 $row['pattern_type_name'] 等字段调用 trim(),避免因前后空格导致同一分类被拆分为多个键;
- ✅ 空值防御:生产环境应增加 isset() 或 array_key_exists() 检查,防止缺失字段报错;
- ✅ 性能考量:若数据量超万级,建议改用 SplFixedArray 或流式处理;当前方案时间复杂度为 O(n),空间复杂度为 O(唯一路径数);
- ⚠️ 避免过度嵌套:本例固定三层,若未来新增字段(如 sub_category),只需扩展 $tempTable[$a][$b][$c][$d] 即可,但需同步更新遍历逻辑;
- ? 扩展性提示:如需保留原始数据对象(不止 true),可将 $tempTable[$type][$name][$desc] = $row;,此时叶子节点即为完整数据行。
此方法摒弃了传统ID递归或多次 array_filter 的低效循环,以“键即路径”的思维直击本质,简洁、健壮、易维护,是处理无ID层级数据的推荐范式。










