在做前端开发或者处理接口数据时,经常会遇到结构嵌套很深的 JSON 数据。比如从一个后台接口拿到用户信息,里面不仅有基础字段,还可能包含地址列表、订单记录,每个订单又关联着商品详情、物流信息,一层套一层。这时候如果还用传统的点操作符一层层去取值,代码很快就会变得臃肿难看。
为什么需要递归处理?
举个例子,假设你正在开发一个电商后台的显示调校功能,要动态渲染商品配置项。返回的 JSON 可能长这样:
{
"name": "手机套餐",
"items": [
{
"type": "color",
"options": [
{ "label": "黑色", "price": 0 },
{ "label": "白色", "price": 50 }
]
},
{
"type": "storage",
"items": [
{ "label": "128GB", "price": 0 },
{
"label": "256GB",
"extra": {
"discount": true,
"items": [ { "gift": "耳机" } ]
}
}
]
}
]
}
可以看到,items 字段反复出现,而且层级不固定。如果想把所有带 label 的选项提取出来,用循环加判断会非常麻烦。而使用递归,就能让代码变得更清晰、更通用。
写一个简单的递归解析函数
核心思路是:不管当前节点是什么类型,先判断是不是对象或数组,如果是,就遍历它的每一项并再次调用自己。碰到目标字段(比如 label),就收集起来。
function traverse(json, callback) {
if (Array.isArray(json)) {
json.forEach(item => traverse(item, callback));
} else if (typeof json === 'object' && json !== null) {
Object.keys(json).forEach(key => {
callback(key, json[key]);
traverse(json[key], callback);
});
}
}
// 使用示例:找出所有 label
const labels = [];
traverse(data, (key, value) => {
if (key === 'label') {
labels.push(value);
}
});
console.log(labels); // ['黑色', '白色', '128GB', '256GB', '耳机']
这个方法的好处是不用关心数据有几层,也不用硬编码路径。只要结构变了,函数照样能跑。
实际应用场景
在“显示调校”这类功能中,经常需要根据原始 JSON 动态生成表单、筛选条件或配置面板。比如电视盒子的界面定制系统,通过递归解析配置 JSON,自动识别出哪些是颜色项、哪些是开关、哪些支持多选,然后渲染成对应的 UI 组件。
再比如调试 API 返回的数据时,想快速查看某个字段是否存在,直接写个递归搜索函数,几行代码就能搞定,比手动翻 JSON 方便得多。
注意事项
递归虽然好用,但也得小心无限循环。如果 JSON 里存在引用循环(比如父子节点互相引用),不加控制就会导致栈溢出。可以在递归时加一个已访问节点的 Set 来避免重复处理。
function safeTraverse(json, callback, visited = new WeakSet()) {
if (typeof json === 'object' && json !== null) {
if (visited.has(json)) return;
visited.add(json);
if (Array.isArray(json)) {
json.forEach(item => safeTraverse(item, callback, visited));
} else {
Object.keys(json).forEach(key => {
callback(key, json[key]);
safeTraverse(json[key], callback, visited);
});
}
}
}
这种写法在处理复杂第三方数据时特别实用,尤其是当你不确定数据是否干净的时候。