XBSTACK XBSTACK
小白 / Xiaobai

小白 / Xiaobai

开发者 · 产品构建者

持续构建 AI 工程系统、开发者工具与长期数字资产。

关于作者与 XBSTACK →
n8n 2.35.3 中 $json.data.sort() 返回 null 与 2.34.5 正常数组结果的版本回归对照

n8n 2.35.3 里 $json.data.sort() 为什么返回 null?Array 方法回归与修复

n8n 2.35.3 的 Edit Fields 中,$json.data.sort()、splice()、fill()、copyWithin() 为什么突然返回 null?本文用官方 Docker 镜像对照 2.34.5 与 2.35.3,确认版本回归,并验证复制数组后的临时修复。

发布 · 2026-08-198 分钟阅读XBSTACK 原创
#n8n#Edit Fields#Expressions#Array#Regression#Debugging#Workflow

如果你刚把 n8n 升到 2.35.3,然后发现 Edit Fields 里原本正常的:

{{ $json.data.sort() }}

突然变成:

null

这次可以先别去改输入 JSON,也不用怀疑 JavaScript 的 sort() 语义。我在本机用官方 n8nio/n8n:2.34.5n8nio/n8n:2.35.3 镜像做了隔离对照,已经确认这是一个从 2.34.5 到 2.35.3 出现的可复现回归。

更关键的是,它不只影响 sort()。我实际跑过的四个直接变异调用:

{{ $json.data.sort() }}
{{ $json.data.splice(0, 2) }}
{{ $json.data.fill("X", 0, 2) }}
{{ $json.data.copyWithin(0, 2, 4) }}

在 2.35.3 的 Edit Fields 中都会得到 null。但 reverse() 正常,先复制数组再调用这些方法也正常。

如果你现在只想先恢复工作流,最短答案是:

不要直接改 $json.data
先复制,再操作副本

例如:

{{ [...$json.data].sort() }}

我不只验证了 sort()splice()fill()copyWithin() 的 copy-first 写法也都在 2.35.3 通过。

n8n 2.35.3 生产环境排查与升级建议:确认版本、隔离复现、识别受影响方法、使用 copy-first 缓解并评估回退

我为什么把它判断为版本回归,而不是表达式写错

上游 n8n Issue #36540 在 2026 年 8 月 18 日报告了同一现象:从 2.34.5 升级到 2.35.3 后,Edit Fields 中多个直接作用于 $json 数组的变异方法开始返回 null。报告中列出的 sort()splice()fill()copyWithin() 与我本机的结果一致;报告者同时指出 reverse() 和复制数组后的 sort() 正常。

但仅仅重复 Issue 里的结论,不足以支撑一篇排障文章。所以我没有直接写,而是把 2.34.5 和 2.35.3 两个官方 Docker 镜像都拉下来,用同一份输入、同一种 Edit Fields 节点、同一个表达式结构做版本对照。

实验基础环境:

项目对照 A对照 B
n8n2.34.52.35.3
Docker imagen8nio/n8n:2.34.5n8nio/n8n:2.35.3
Node.jsv24.18.0v24.18.1
数据库SQLite 默认配置SQLite 默认配置
执行方式n8n executen8n execute
Edit Fields同一节点结构同一节点结构

每一个方法都使用一条独立输入分支,输入固定为:

{
  "data": [
    "Mango",
    "Apple",
    "Kiwi",
    "Orange",
    "Blueberry",
    "Banana",
    "Peach",
    "Grape",
    "Pineapple",
    "Strawberry"
  ]
}

之所以要“每个方法独立一份输入”,是因为我第一轮把多个表达式都塞在一个 Edit Fields 节点时,2.34.5 会真的执行原地变异:前一个表达式可能已经改掉 $json.data,后一个表达式看到的就不是原始数组。那个现象本身也值得注意,但不适合作为严谨的版本矩阵。所以最终结论只采用隔离后的结果。

2.34.5 vs 2.35.3:实际结果

最终矩阵如下:

n8n 2.34.5 与 2.35.3 Array 方法回归实测矩阵:sort、splice、fill、copyWithin 在 2.35.3 返回 null,reverse 正常

表达式n8n 2.34.5n8n 2.35.3
$json.data.sort()返回排序后的数组null
$json.data.splice(0, 2)['Mango','Apple']null
$json.data.fill('X', 0, 2)返回前两项被替换后的数组null
$json.data.copyWithin(0, 2, 4)返回原地复制后的数组null
$json.data.reverse()正常正常
[...$json.data].sort()正常正常
$json.data.slice().sort()正常正常

这里最重要的不是“某个函数偶发失败”,而是三件事同时成立:

  1. 输入和节点配置不变,只切换 n8n 版本,结果改变。
  2. 不是所有 Array 方法都坏。reverse() 在 2.35.3 正常。
  3. **不是排序能力坏了。**复制数组后的 sort()slice().sort() 都正常。

这让问题范围明显收窄:它表现为 2.35.3 对直接挂在 $json 输入数组上的部分变异方法进行了不同处理。至于内部究竟是哪一层代理、只读保护、表达式沙箱或序列化逻辑导致,当前上游 Issue 还没有给出可以引用的修复提交,因此我不会在这里把“表现”强行写成“根因”。

最快的临时修复:先复制数组

对于现在已经在生产上报错或产生 null 的工作流,我更建议先做最小变更,不要为了绕过一个表达式回归就把整条工作流改成 Code 节点。

n8n 2.35.3 Array 方法临时修复:避免直接变异 $json.data,先复制数组后再调用 sort、splice、fill 或 copyWithin

sort()

原来:

{{ $json.data.sort() }}

改成:

{{ [...$json.data].sort() }}

或者:

{{ $json.data.slice().sort() }}

两种我都在 2.35.3 验证通过。

splice()

原来:

{{ $json.data.splice(0, 2) }}

改成:

{{ [...$json.data].splice(0, 2) }}

本机 2.35.3 返回:

["Mango", "Apple"]

要注意 splice() 的 JavaScript 语义本来就是“返回被删除的元素”,不是返回删除后的剩余数组。不要因为修复了 null,又把业务语义改错。

fill()

原来:

{{ $json.data.fill("X", 0, 2) }}

改成:

{{ [...$json.data].fill("X", 0, 2) }}

2.35.3 实测返回:

["X", "X", "Kiwi", "Orange", "Blueberry", "Banana", "Peach", "Grape", "Pineapple", "Strawberry"]

copyWithin()

原来:

{{ $json.data.copyWithin(0, 2, 4) }}

改成:

{{ [...$json.data].copyWithin(0, 2, 4) }}

2.35.3 实测返回:

["Kiwi", "Orange", "Kiwi", "Orange", "Blueberry", "Banana", "Peach", "Grape", "Pineapple", "Strawberry"]

这四组不是“理论上应该能用”,而是同一台机器、同一个 2.35.3 官方镜像里实际执行通过的结果。

为什么复制数组反而是更稳的写法

即使上游之后修复了这个回归,我仍倾向于在 n8n 表达式里少直接修改 $json 提供的输入对象。

n8n 的数据映射本质上是在当前节点里引用前序节点产生的数据。对于 sort()splice()fill()copyWithin() 这类原地修改数组的方法,如果直接对 $json.data 调用,你同时做了两件事:读取输入、修改输入对象。尤其当一个节点里有多个字段表达式、或者后面还有表达式依赖同一个对象时,副作用会让排错变得困难。

复制后再变异:

{{ [...$json.data].sort() }}

至少把“我要得到一个排序结果”和“我要修改当前输入数组”分开了。对于对象数组,需要根据数据结构决定浅拷贝是否足够,但对本次字符串数组场景,这个边界非常清楚。

是否应该直接降级到 2.34.5

我不会把“降级”写成第一推荐,因为是否回滚取决于工作流数量和生产风险。

如果受影响的地方只有几个 Edit Fields 表达式,copy-first 改法通常比整体回退版本影响更小。你可以先全局搜索:

.sort(
.splice(
.fill(
.copyWithin(

然后重点检查这些调用是不是直接挂在 $json.xxx 数组上。

如果你的实例里有大量工作流依赖这种写法,又没有时间逐条回归,版本回退才可能更合适。但回退 n8n 不能只看一个节点表达式,还要考虑当前数据库迁移、其他节点升级、凭据和生产部署策略。至少先备份数据库和工作流,再在测试实例验证 2.34.5。

升级到 2.35.3 后,我建议这样排查

如果你的症状和本文相似,可以按这个顺序做:

  1. 确认实例实际版本确实是 2.35.3,而不是浏览器缓存或 worker/main 版本不一致。
  2. 找到返回 null 的 Edit Fields 字段,保留原表达式。
  3. 用同一输入改成 [...$json.xxx] 的 copy-first 版本做 A/B。
  4. 如果 copy-first 正常,继续检查是否属于 sort/splice/fill/copyWithin 这一类。
  5. 不要一次性重构整条工作流,先恢复最小故障点。
  6. 对生产环境做一次全局扫描,找出其他直接修改 $json 数组的表达式。
  7. 上游修复发布后,再在测试环境撤掉 workaround 做回归,不要直接在生产批量还原。

这和昨天的 n8n HTTP Request Stream 问题不是一回事

XBSTACK 昨天刚写过一篇 n8n HTTP Request Raw Body 返回 _readableState / Stream 的排障。两篇看起来都属于 n8n,但搜索意图完全不同。

昨天的问题发生在 HTTP Request 节点的 Raw Body 与 Response Format 组合,症状是本应得到 JSON,却暴露 Stream 内部对象。

今天这个问题发生在 Edit Fields 表达式求值,症状是升级 2.35.3 后,对 $json 数组直接调用部分变异方法得到 null

因此这不是为了追 n8n 热点重复发内容,而是两个可以独立复现、独立搜索、独立修复的故障类。更通用的 n8n 失败处理可以继续看 n8n Workflow 错误处理Workflow 专题

当前证据边界

截至 2026 年 8 月 19 日,我能明确确认的是:

  • 上游 n8n Issue #36540 于 8 月 18 日报告了 2.35.3 的同类回归。
  • XBSTACK 使用官方 2.34.5 与 2.35.3 Docker 镜像完成了本地版本对照。
  • sort/splice/fill/copyWithin 的 direct-on-$json 调用在 2.35.3 稳定返回 null
  • reverse() 没有表现出同样问题。
  • 对数组先复制后再执行四个受影响方法,在 2.35.3 全部通过。

我暂时不会写“根因已经确定”“某个 commit 导致”或“2.35.4 一定修复”,因为目前没有上游修复提交可以支撑这些结论。

上游跟踪:

如果上游后续合并修复,我会把这篇文章更新成 受影响版本 → 修复版本 → workaround 是否仍需要 的版本矩阵,而不是再新建一个重复 URL。

专题入口 / AI Workflow Hub

继续按 n8n 生产排障链路读

自托管、Queue Mode、Webhook、错误处理和案例文统一沉淀到 Workflow 专题页:部署文做主力页,案例文做长尾页,对比文承接工具选择流量。

继续阅读

返回专题 →
n8n 2.33.4 升级后 Baserow 工作流无法激活:Could not resolve parameter dependencies 怎么解决?n8n 2.33.4 升级后 Baserow 工作流报 Could not resolve parameter dependencies. Max iterations reached 怎么办?本文对比 2.32.7 与 2.33.4 的 Baserow 参数树,复现同一错误,并给出回滚、验证与官方修复前的安全处理方式。n8n HTTP Request 返回 _readableState 而不是 JSON:Raw Body 为什么会变成 Stream?n8n HTTP Request 使用 Raw Body 且显式选择 JSON Response 时,为什么输出会变成 _readableState/_writableState Stream?本文用四组对照复现问题,核对 n8n 2.34.5 源码,并给出已验证的临时处理方式。n8n 2.33.7 distroless ARM64 报 GLIBC_PRIVATE:复现、原因与临时方案n8n 2.33.7 distroless 在 ARM64 上启动 Python task runner 时出现 __tunable_is_initialized / GLIBC_PRIVATE 怎么办?本文给出同机 2.26.9/2.33.7 对照、当前 Dockerfile ABI 边界、单独替换 libc 失败实验和已验证临时回退方案。n8n AI Agent 不调用工具怎么办?tool_choice、模型兼容与 Memory 排查n8n AI Agent 不调用工具:n8n AI Agent 明明连接了 Tool 却直接回答怎么办?本文基于官方 Issue 与本地请求契约实验,排查首轮 tool_choice、OpenAI 兼容模型、Tool Schema、描述冲突和 Memory 丢失。

AI 工程周报

只发真正改变工程判断的变化、故障、实验和新资产。

评论与补充证据

参与讨论

问题、验证与勘误

登录后可发表评论。所有新评论先进入审核;审核期间仅评论者本人和管理员可见,通过后才公开。

登录评论 审核后公开
正在加载评论区…