☰
JSON数组操作实战:访问、遍历、嵌套、修改与删除
2026/10/17 13:12:19 网站建设 项目流程

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 中,访问不存在的索引不会报错,而是返回undefined:

const arr = [1, 2, 3]; console.log(arr[5]); // undefined

这样看似“宽容”,实则是隐患。如果你拿undefined去参与计算或拼接字符串,很可能得到一串莫名其妙的结果。比如某个字段取出来是undefined,你直接往页面上渲染,用户看到的就会是空或者是“undefined”这几个字。

Python 则严格得多,越界直接抛IndexError:

arr = [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 循环。它的好处是你同时掌握着索引,可以根据条件跳过、提前退出。

JavaScript:

const arr = ["A", "B", "C"]; for (let i = 0; i < arr.length; i++) { console.log(arr[i]); }

Python:

arr = ["A", "B", "C"] for i in range(len(arr)): print(arr[i])

Python 里更推荐直接遍历元素:

for item in arr: print(item)

但如果需要索引,可以用enumerate:

for 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 None

4.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 通过索引直接修改

最简单的修改方式,就是拿到索引后直接覆盖。

JavaScript:

const arr = ["苹果", "香蕉", "橙子"]; arr[1] = "西瓜"; console.log(arr); // ["苹果", "西瓜", "橙子"]

Python:

arr = ["苹果", "香蕉", "橙子"] arr[1] = "西瓜" print(arr) # ['苹果', '西瓜', '橙子']

这里有个容易忽略的问题:如果数组原本长度是 2,你直接给arr[5]赋值,JavaScript 会把中间空出来的位置填上empty空位,打印出来是[元素1, 元素2, empty × 3, "新值"]。这种“稀疏数组”后续遍历时很可能产生非预期结果,所以最好避免。

Python 则是直接报错,不允许越界赋值。所以修改前先判断索引范围总没错。

5.2 批量修改:map 方法更符合“不变式”思维

在某些框架(尤其前端框架)里,直接改原数组有时不会触发视图更新。这时候用map返回新数组更合适。

比如把一组数字都乘以 2:

const 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 返回undefined,Python 直接抛异常。如果你在解析一段不确定是否包含数据的 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.parse,Python 用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,取不到值时,别急着往上补可选链。我一般按这个顺序排查:

  1. 先确认数据结构对不对:直接console.log(JSON.stringify(data))打印出来看一眼。
  2. 确认是对象还是数组:用Array.isArray(data.res)判断。
  3. 确认中间某层是不是空:比如list[0]是否存在。
  4. 最后再考虑代码写法问题。

很多时候你以为的“嵌套数组取不到值”,其实是中间有一个字段名拼错了,或者后端返回了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.stringify(JS)或json.loads/json.dumps(Python)。每次报错都该先想一想过层了没有。

第二,我自己的编码习惯是:能用filter和map解决的,尽量不用循环加 splice 的组合。这不仅让代码更短,还天然避免了“遍历时修改原数组”的坑。但要注意,filter和map生成新数组的开销对超大数组还是有影响的,如果你在跑几十万条数据的批处理,传统循环可能更稳。

第三,嵌套数组千万别硬背路径,写代码时多用可选链或者 Python 的 dict get 兜底。之前我在某个项目里看到一个接口返回的层级深到 5 层,大家每层都去判断非空,代码写了一大坨。后来把数据结构梳理清楚,用一个固定的取值函数统一处理,代码清爽了不少。核心思路是:把嵌套数组的访问逻辑收敛到一处,不要散落在各处。

第四,也是我最想强调的一点:JSON 数组是数据交换的“通用语言”,但不同的编程语言处理它的细节差异很大。你完全不需要背每种语言的 API,只需要记住几个核心概念——索引从 0 开始、数组长度、越界行为、原地修改 vs 返回新数组。这四个概念拿出来,不管换到什么语言,都能快速适应。

最后分享一个小技巧:调试 JSON 数组时,不要光看console.log(arr),因为有些浏览器控制台会惯性展开实时数据,反而看不清当时的快照。我习惯用console.log(JSON.stringify(arr))打印出字符串形式,所有结构一眼看穿,而且是当时时刻的真正状态。这招在排查复杂嵌套数组时帮我省了不少时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询