JSON 数组这个知识点看起来简单但我在实际项目里见过太多人在这里栽跟头。比如从接口拿回一个数组想取第三个元素结果下标从 0 开始数直接取到了隔壁老王再比如后端返回的嵌套数组前端同学遍历的时候一脸懵不知道该怎么把内层数据捞出来。所以今天我不讲那些教科书式的大道理就从一个实际开发者的角度把 JSON 数组的访问、遍历、嵌套、修改、删除这五件事彻底梳理一遍。看完这篇你至少能少踩一半的坑。这篇内容主要适合这几类人刚接触前后端联调的新人写脚本处理 JSON 数据的测试或运维同学以及偶尔要跟 JSON 打交道的全栈开发者。我会用 JavaScript 和 Python 两种语言对照演示因为这两者在 JSON 处理上最常用其他语言思路都类似。1. JSON 数组基础认知先搞清楚它到底是什么1.1 数组在 JSON 结构里的位置JSON 只有两种复合结构对象花括号括起来的一组键值对和数组方括号括起来的一组有序值。在这两种结构里数组又显得更特殊一些——对象有键名数组没有键名只有位置编号也就是索引。举个例子一个标准的 JSON 数组长这样[苹果, 香蕉, 橙子]这背后包含两层信息第一它是一个数组第二数组里有三个字符串元素。在实际业务里数组里最常见的不是字符串而是一堆对象。比如一个用户列表[ {id: 1, name: 张三}, {id: 2, name: 李四} ]这种“数组包对象”的组合才是开发中接触最高频的结构。很多新手一看到方括号里有花括号就懵了其实本质很简单数组就是一个“装东西的盒子”里面可以装任何 JSON 支持的数据类型包括对象、字符串、数字、布尔值甚至另一个数组。我建议你把数组理解成一个“按顺序排好的货架”。货架有编号索引从 0 开始货架上的每一格都有固定的位置你可以往任意一格放东西、换东西、拿走东西。这样理解后面所有操作都通了。1.2 为什么单独把数组操作拎出来讲对象操作往往比较简单——通过键名取值。数组不一样它多了一层“顺序”的概念。顺序带来了三个新的问题怎么定位某个元素访问、怎么把每个元素都过一遍遍历、怎么在保持顺序的前提下增删改。还有一个容易忽略的细节JSON 本身是一种数据交换格式它不是可执行代码。所以当你把 JSON 文本解析成编程语言里的数组之后操作方式完全取决于你的语言。比如 JSON 里的数组没有“方法”这个概念但 JavaScript 的数组对象有push、splice这类方法Python 的列表也有自己的一套 API。这就导致了一个常见困惑有些操作在 JSON 层面根本不存在必须等解析为语言内置结构之后才能做。说到底你需要掌握两件事一是 JSON 语法层的数组规则值用方括号、逗号分隔不能有尾逗号二是编程语言层的数组操作方法增删改查。这两件事搞清了JSON 数组的操作你就掌握了 80%。2. 访问数组值索引、边界与常见误区2.1 索引访问的正确姿势访问数组值默认方式就是通过索引。无论是 JavaScript 还是 Python索引都是从 0 开始的。也就是说数组第一个元素的索引是 0第二个是 1依此类推。JavaScript 示例const fruits [苹果, 香蕉, 橙子]; console.log(fruits[0]); // 苹果 console.log(fruits[1]); // 香蕉 console.log(fruits[2]); // 橙子Python 示例fruits [苹果, 香蕉, 橙子] print(fruits[0]) # 苹果 print(fruits[1]) # 香蕉 print(fruits[2]) # 橙子这里有个小细节Python 支持负数索引fruits[-1]直接取最后一个元素fruits[-2]取倒数第二个。这在处理尾部数据时非常方便。比如你想拿一个列表里的最后一条日志Python 里一行代码搞定JavaScript 里就得用fruits[fruits.length - 1]或者用 ES2022 新增的fruits.at(-1)方法。但要注意向前兼容性方面at方法在老旧环境里不可用如果你还在维护需要兼容旧浏览器的项目老实写arr[arr.length - 1]最稳。2.2 越界访问不会报错但结果可能让你抓狂访问数组时最容易踩的坑是索引越界。但这里有个反直觉的现象JavaScript 和 Python 的规则不一样。JavaScript 中访问不存在的索引不会报错而是返回undefinedconst arr [1, 2, 3]; console.log(arr[5]); // undefined这样看似“宽容”实则是隐患。如果你拿undefined去参与计算或拼接字符串很可能得到一串莫名其妙的结果。比如某个字段取出来是undefined你直接往页面上渲染用户看到的就会是空或者是“undefined”这几个字。Python 则严格得多越界直接抛IndexErrorarr [1, 2, 3] print(arr[5]) # IndexError: list index out of range所以 Python 写代码时如果你不确定数组长度最好先判断长度再访问if len(arr) 5: print(arr[5])JavaScript 虽然没有报错但也建议加个兜底判断const value arr[5] ?? 默认值;这里??是空值合并运算符只有当左边是null或undefined时才用右边的默认值。用它比用||更安全因为||会把空字符串、0、false 这种“假值”也一起处理掉。2.3 访问对象数组指定的属性值数组里存对象时访问方式就是“先取数组元素再取对象属性”const users [ {id: 1, name: 张三}, {id: 2, name: 李四} ]; console.log(users[0].name); // 张三 console.log(users[1][name]); // 李四两种写法都行第二种适合属性名是动态拼接的场景。Python 里对应的是users [ {id: 1, name: 张三}, {id: 2, name: 李四} ] print(users[0][name]) # 张三这里我强烈建议写代码时保持一致的风格不要一会儿用点号一会儿用方括号避免团队协作时阅读理解成本上升。3. 遍历数组各种姿势背后的性能与取舍3.1 传统 for 循环最基础也最可靠遍历数组最原始的方式就是 for 循环。它的好处是你同时掌握着索引可以根据条件跳过、提前退出。JavaScriptconst arr [A, B, C]; for (let i 0; i arr.length; i) { console.log(arr[i]); }Pythonarr [A, B, C] for i in range(len(arr)): print(arr[i])Python 里更推荐直接遍历元素for item in arr: print(item)但如果需要索引可以用enumeratefor i, item in enumerate(arr): print(i, item)在实际开发里for 循环最大的价值是可以在中途break或continue。尤其是从数组里查找某个符合条件的数据时一旦找到就退出能省下不少无谓的循环。3.2 JavaScript 的 for...of 与高阶方法如果你不需要索引那for...of是遍历数组最清爽的写法for (const item of arr) { console.log(item); }注意别用for...in遍历数组那取到的是字符串类型的索引键而不是数组元素处理顺序也可能不按预期。数组还自带很多遍历方法比如forEach、map、filter、reduce。它们本质都是函数式遍历但用途不一样forEach只做遍历不返回新数组。map遍历并返回一个加工后的新数组。filter遍历并筛选出符合条件的元素组成新数组。reduce遍历并把数组归约为一个值。这里我特别想提一个常见的性能误区很多人喜欢在循环体里写arr.length每轮循环都读取一次长度性能上其实影响微乎其微。真正要警惕的是在循环过程中修改数组本身比如边循环边splice删除元素这会导致索引错位漏掉元素或出现undefined。3.3 遍历 JSON 对象数组解构让代码更优雅实际业务中我们经常要遍历一个对象数组然后只取其中几个字段。JavaScript 里配合解构赋值非常优雅const users [ {id: 1, name: 张三, age: 20}, {id: 2, name: 李四, age: 30} ]; for (const {id, name} of users) { console.log(id, name); }Python 里如果字典结构固定也可以类似解包for user in users: print(user.get(id), user.get(name))生产环境里我建议对象数组里的字段都用get方法Python或可选链JavaScript来取防止某条数据缺字段直接崩掉for (const user of users) { console.log(user?.name ?? 未知); }4. JSON 对象中的嵌套数组定位、遍历与展平4.1 嵌套数组的取数逻辑嵌套数组就是在数组的元素里又遇到了数组或者数组里包对象、对象里包数组、数组里再包数组。这种结构最典型的场景是“分类下挂多级子项”或者“多级菜单”。看一个经典结构{ categories: [ { name: 电子产品, items: [ {id: p1, title: 手机}, {id: p2, title: 电脑} ] }, { name: 图书, items: [ {id: b1, title: 小说} ] } ] }想取“电子产品”下面第一个商品标题访问链是const data JSON.parse(jsonText); const title data.categories[0].items[0].title; // 手机这种一层层点下去的方式就是标准的“路径访问”。路径越长越容易出错关键要分清楚每一层到底是数组还是对象然后再决定用索引还是用键名。一个实用技巧如果你经常要访问深层数据可以先做一步空值保护。JavaScript 里用可选链const title data?.categories?.[0]?.items?.[0]?.title;这样即使中间某一层断了也只会返回undefined不会直接抛异常。Python 里没有可选链但你可以用 try/except 兜底或者用dict.get的方式逐层取categories data.get(categories) or [] items categories[0].get(items) if categories else [] title items[0].get(title) if items else None4.2 多层嵌套数组的遍历循环套循环遍历嵌套数组最直觉的方式就是嵌套循环。外层遍历第一层内层遍历第二层。还是按上面那个分类的例子for (const category of data.categories) { console.log(分类名称:, category.name); for (const item of category.items) { console.log(商品标题:, item.title); } }Python 版本for category in data.get(categories, []): print(分类名称:, category.get(name)) for item in category.get(items, []): print(商品标题:, item.get(title))这种循环嵌套写法本身没错但三层以上代码就会变得很难维护。我建议一旦超过两层就考虑用递归或者把结构先展平再用单层循环处理。4.3 展平嵌套数组从数据里“捞”出所有值展平flatten是指把嵌套数组变成一维数组。比如[[1,2],[3,4]]变成[1,2,3,4]。JavaScript 可以直接用flat方法const nested [[1, 2], [3, 4]]; console.log(nested.flat()); // [1, 2, 3, 4]更深层的可以传参数nested.flat(Infinity)表示无限层展平。Python 里可以用列表推导式处理两层nested [[1, 2], [3, 4]] flat [x for sub in nested for x in sub] print(flat) # [1, 2, 3, 4]超过两层就别折腾推导式了写个递归函数更清晰def flatten(arr): result [] for item in arr: if isinstance(item, list): result.extend(flatten(item)) else: result.append(item) return result这个思路同样适用于处理任意深度的嵌套数组。在实际解析第三方接口时嵌套数组展平后常常是为了方便做统计或筛选。5. 修改数组值直接赋值还是用方法要分场景5.1 通过索引直接修改最简单的修改方式就是拿到索引后直接覆盖。JavaScriptconst arr [苹果, 香蕉, 橙子]; arr[1] 西瓜; console.log(arr); // [苹果, 西瓜, 橙子]Pythonarr [苹果, 香蕉, 橙子] arr[1] 西瓜 print(arr) # [苹果, 西瓜, 橙子]这里有个容易忽略的问题如果数组原本长度是 2你直接给arr[5]赋值JavaScript 会把中间空出来的位置填上empty空位打印出来是[元素1, 元素2, empty × 3, 新值]。这种“稀疏数组”后续遍历时很可能产生非预期结果所以最好避免。Python 则是直接报错不允许越界赋值。所以修改前先判断索引范围总没错。5.2 批量修改map 方法更符合“不变式”思维在某些框架尤其前端框架里直接改原数组有时不会触发视图更新。这时候用map返回新数组更合适。比如把一组数字都乘以 2const arr [1, 2, 3]; const doubled arr.map(n n * 2); // [2, 4, 6]map的好处是不会改动原数组避免副作用。如果你需要在原数组基础上修改也可以把结果重新赋值回去。Python 里等效的写法是列表推导式arr [1, 2, 3] doubled [n * 2 for n in arr] print(doubled) # [2, 4, 6]5.3 修改对象数组的子字段当数组元素是对象时我们常常只需要改里面某个字段const users [ {id: 1, age: 10}, {id: 2, age: 20} ]; users[0].age 15;这里有个性能小技巧如果你要改整批对象的同一个字段用循环或map都行。但要注意用map时如果对象的引用没变只是改了对象内部的属性原数组对应的对象也会跟着变。这涉及 JavaScript 的引用传递新手容易晕。要想彻底不可变需要浅拷贝对象const updated users.map(user user.id 1 ? {...user, age: 15} : user);这属于进阶话题但实际开发里靠它解决了不少“改了没渲染”“上一个页面把数据串了”的诡异 bug。6. 删除数组项目三种方式决策依据要清楚6.1 splice 删除精准且能指定数量JavaScript 里真正用于从数组中删除元素的方法是splice。它的第一个参数是起始索引第二个参数是删除几个元素const arr [a, b, c, d]; arr.splice(1, 2); console.log(arr); // [a, d]arr.splice(1, 2)的意思是从索引 1 开始删除 2 个元素也就是把 b 和 c 干掉剩下 a 和 d。注意splice是直接修改原数组同时返回被删除的元素数组。如果删除后要立即插入新元素splice也可以做到arr.splice(1, 2, x, y);这样就变成了先删除再替换。这个能力在你处理拖拽排序、列表动态增删时非常有用。6.2 delete 操作符不要用它删数组元素JavaScript 里delete arr[1]会把索引 1 的元素删掉但数组长度不会变那个位置会变成一个空位empty。很多新人误以为这是“删除”结果遍历时发现索引还在只是值是空的导致各种诡异问题。所以对于数组尽量不要用delete。它更适合用来删除对象的属性而不是数组元素。6.3 filter 过滤按条件删除的本质如果你想按条件删除元素比如把所有小于 18 的项目删掉那filter是最稳妥的const arr [10, 20, 30, 15]; const filtered arr.filter(n n 18); console.log(filtered); // [20, 30]filter不会改变原数组而是返回一个新数组。如果你确实想覆盖原数组可以直接arr arr.filter(...)但这要求变量不是用const声明。在某些框架里这种“替换引用”的方式比直接改原数组更安全更容易触发组件更新。Python 里删除元素的方式分三种pop(index)按索引删除并返回该元素类似splice的取回功能。remove(value)按值删除第一个匹配的元素。del arr[index]按索引直接删除不返回被删值。arr [a, b, c, d] del arr[1] print(arr) # [a, c, d]如果你要在循环里边遍历边删除我强烈建议你换个思路用“遍历索引倒着删”或者用生成一个新数组。正着删除会引起后面的元素前移而循环变量还停在原来的步数很容易跳过元素。6.4 实际业务里的删除陷阱数组是 const 时怎么办很多人在做前端开发时用const声明了一个数组然后想通过filter重新赋值给这个变量结果报错const list [{id: 1}, {id: 2}]; list list.filter(item item.id ! 1); // TypeError: Assignment to constant variable.这种场景最常见的解法是换个做法直接在一个新变量里保存结果或者用splice方式原地修改const index list.findIndex(item item.id 1); if (index ! -1) { list.splice(index, 1); }这个模式也经常用于表格数据删除。找到目标索引之后再删比直接用filter重新设值更贴近“操作原数据”的语义。7. 常见问题与排查技巧实录7.1 索引越界与空数组访问空数组的任意索引JavaScript 返回undefinedPython 直接抛异常。如果你在解析一段不确定是否包含数据的 JSON访问前先判断长度if (Array.isArray(data) data.length 0) { // 安全操作 }Python 里if isinstance(data, list) and len(data) 0: # 安全操作很多线上报错都是因为后端返回了空数组前端没有做保护就直接取下标 0结果拿到一个undefined再访问它的某个属性直接报“Cannot read properties of undefined”。这可以说是 JSON 数组领域最经典的事故现场。7.2 解析 JSON 文本时的类型陷阱从接口拿到的原始数据是字符串必须先解析成数组才能操作。JavaScript 用JSON.parsePython 用json.loads。解析时有一个容易忽略的问题JSON 数组元素里并没有日期类型、undefined类型只有字符串、数字、布尔值、null、对象、数组。所以如果你把一个 JavaScript 对象直接转成 JSON再转回来原来的undefined会消失或者变成null。写序列化逻辑时要注意避免把关键字段搞丢。另外解析可能失败。后端偶尔会返回一段异常文本比如错误提示而不是 JSON直接JSON.parse会抛异常。稳妥做法是包一层 try/catch或者在解析前先判断字符串是否以[或{开头当然更合理的是看状态码判断是否正常返回。7.3 遍历中删除导致索引错乱这是一个频率极高的坑。假设你在 JavaScript 里这样写逻辑找出所有值为某些元素的项并删掉你通常会先 filter 再处理或者用 while 循环加内部 splice。如果图省事在forEach里直接splice你会发现漏删了某些元素因为splice会让后续元素前移而forEach的索引已经往后走了。我给出的处理建议按场景分成两种如果不关心原数组引用直接arr arr.filter(condition)。如果必须在原数组上删就倒着遍历for (let i arr.length - 1; i 0; i--) { if (shouldDelete(arr[i])) { arr.splice(i, 1); } }倒着删就不会影响前面位置的索引所有splice的目标都是“当前尚未处理到的位置”安全可靠。类似逻辑也适用于 Python。7.4 嵌套数组取不到值的排查思路当你的嵌套数组结构比较深比如data.res.list[0].name取不到值时别急着往上补可选链。我一般按这个顺序排查先确认数据结构对不对直接console.log(JSON.stringify(data))打印出来看一眼。确认是对象还是数组用Array.isArray(data.res)判断。确认中间某层是不是空比如list[0]是否存在。最后再考虑代码写法问题。很多时候你以为的“嵌套数组取不到值”其实是中间有一个字段名拼错了或者后端返回了data.list你写成了data.items。打印一遍原始 JSON一步就定位了。7.5 与后端联调时的数组字段一致性前后端联调时经常出现沟通问题后端字段叫arr前端代码里写data.array结果界面永远空白。或者后端返回{list: null}而不是[]代码里直接遍历null就炸了。针对这种情况我给前端同学一个建议在和后端约定的接口文档里明确数组字段的默认值。如果顶多为空理应返回[]而不是null。但如果后端确实返回了null你就在前端兜底const list data.list ?? [];Python 后端也可以做类似防护data.get(list) or []如果项目里到处都得写这种兜底说明后端接口设计不够规范最好推动接口层修而不是在前端各个角落打补丁。8. 实操总结与经验延伸写到这里JSON 数组的访问、遍历、嵌套、修改、删除这些核心操作我都过了一遍。最后我再补充几个在实际项目里反复验证过的个人经验。第一处理 JSON 数组之前先明确“你要操作的是原始 JSON 文本还是已被解析成语言结构的数组”。两者之间的转换是JSON.parse/JSON.stringifyJS或json.loads/json.dumpsPython。每次报错都该先想一想过层了没有。第二我自己的编码习惯是能用filter和map解决的尽量不用循环加 splice 的组合。这不仅让代码更短还天然避免了“遍历时修改原数组”的坑。但要注意filter和map生成新数组的开销对超大数组还是有影响的如果你在跑几十万条数据的批处理传统循环可能更稳。第三嵌套数组千万别硬背路径写代码时多用可选链或者 Python 的 dict get 兜底。之前我在某个项目里看到一个接口返回的层级深到 5 层大家每层都去判断非空代码写了一大坨。后来把数据结构梳理清楚用一个固定的取值函数统一处理代码清爽了不少。核心思路是把嵌套数组的访问逻辑收敛到一处不要散落在各处。第四也是我最想强调的一点JSON 数组是数据交换的“通用语言”但不同的编程语言处理它的细节差异很大。你完全不需要背每种语言的 API只需要记住几个核心概念——索引从 0 开始、数组长度、越界行为、原地修改 vs 返回新数组。这四个概念拿出来不管换到什么语言都能快速适应。最后分享一个小技巧调试 JSON 数组时不要光看console.log(arr)因为有些浏览器控制台会惯性展开实时数据反而看不清当时的快照。我习惯用console.log(JSON.stringify(arr))打印出字符串形式所有结构一眼看穿而且是当时时刻的真正状态。这招在排查复杂嵌套数组时帮我省了不少时间。