在 Laravel 开发里,我们常从数据库中取出带有父子关系或多层关联的数据,这些数据经模型集合承载后,序列化为 JSON 时容易出现层级参差。直接交给前端处理既不安全也低效,因此需要在后端把嵌套集合展平,并让最终数组层级保持一致。

为什么需要展平嵌套集合
Laravel 的 Eloquent 返回的是 IlluminateSupportCollection 实例,当模型中定义了 hasMany 或 belongsTo 等多层关联,集合里就会嵌套子集合。如果不加处理直接 toJson,输出的结构可能是 {id:1, children:[{id:2, children:[...]}]},深度随数据变化。
前端在遍历时无法用统一逻辑消费,要么写递归要么做容错。更麻烦的是,有些关联被加载为字典形式,有些是索引数组,导致同一字段在不同记录里类型不同。后端提前展平并归约层级,能显著降低接口耦合度,也方便做字段过滤与权限裁剪。
使用集合自带的 flatten 方法
Collection 类提供了 flatten 方法,可以把多维集合压成一维。它接受深度参数,默认不限制。看下面示例,我们有一个带两层嵌套的数组集合:
$collection = collect([
['id' => 1, 'name' => 'A', 'subs' => collect([
['id' => 2, 'name' => 'B'],
['id' => 3, 'name' => 'C'],
])],
['id' => 4, 'name' => 'D'],
]);
$flat = $collection->flatten(2);
// 结果是一维集合,但原始键名如 subs 会消失
print_r($flat->toArray());
上例中 flatten(2) 把深度为 2 的嵌套摊开,但原本 subs 下的子项直接并到外层,键名丢失,索引被重排。这在只需要值的列表时没问题,但若要保留关系标识就不合适。
另一个隐患是,如果集合里混合了字符串键和整数键,flatten 会统一转成整数索引,可能让前端误判。因此在需要保留业务含义时,不能盲目调用该方法。
自定义递归展平并统一结构
为了既展平又保留必要字段,我们可以写递归函数,把每一条记录提取为平级数组,同时用固定字段标记来源层级。下面给出一个通用处理方式:
function flattenWithLevel($items, $level = 0) {
$result = [];
foreach ($items as $item) {
$base = [
'id' => $item['id'],
'name' => $item['name'],
'level' => $level,
];
$result[] = $base;
if (!empty($item['subs'])) {
// 递归处理子集合,层级加一
$children = $item['subs'] instanceof IlluminateSupportCollection
? $item['subs']->toArray()
: $item['subs'];
$result = array_merge($result, flattenWithLevel($children, $level + 1));
}
}
return $result;
}
$flatList = flattenWithLevel($collection->toArray());
echo json_encode($flatList, JSON_UNESCAPED_UNICODE);
这个函数把嵌套转为带 level 字段的一维数组,前端用 level 就能还原树形或做缩进。所有记录都拥有相同键集合,JSON 层级彻底统一为单层数组,避免了类型不一致。
相比原生 flatten,自定义方式可控性强:你可以过滤敏感字段、改写键名、甚至把某些关联拍平为逗号字符串。缺点是需自己维护逻辑,若数据结构复杂要写好单元测试。
在资源类里完成转换
Laravel 的 API 资源(Resource)是规范化输出的好地方。我们可以在 toArray 方法中调用上述展平逻辑,让控制器只管取模型,格式归资源管。
class FlatNodeResource extends IlluminateHttpResourcesJsonResourceCollection
{
public function toArray($request)
{
$raw = $this->collection->toArray();
return [
'data' => flattenWithLevel($raw),
'count' => count(flattenWithLevel($raw)),
];
}
}
这样返回的 JSON 永远是被展平并且层级统一的,调用方无需关心数据库里是不是三层关联。若以后要恢复树形,只需改资源类,不影响业务代码。
需注意资源集合里 $this->collection 本身已是集合,避免再次 collect 包裹。另外如果数据量极大,递归展平会有内存压力,此时应考虑在 SQL 中用递归 CTE 先拉平,再用集合做轻量转换。
常见误区与建议
不少开发者以为 toArray 之后 json_encode 就完事,结果前端收到混合结构。还有人用 flatten 后惊讶发现关联键没了。建议明确接口契约:要么全树形要么全平级,并在文档注明。
如果业务真要平级结构,优先在资源层封装,不要散落在控制器。利用 Laravel 集合的高阶方法如 map、flatMap 也能写出简洁逻辑,但复杂层级还是递归直观。统一 JSON 数组层级不是小事,它决定了前后端协作的长期成本。