第 4 期:数据的心跳 — 深度搞懂 JSON Items 与隐式循环

⏱ 预计阅读 13 分钟 更新于 2026/4/10

为什么这一期是全课程最重要的?

在 n8n 社区论坛中,超过 40% 的新手问题都与数据流转有关:

  • "我的节点为什么被执行了 5 次?"
  • "Merge 节点的输出为什么不对?"
  • "为什么 Code 节点里 $input.first()$input.all() 结果不同?"

这些问题的根源都是:没有理解 n8n 的 Item-Driven 执行模型


Item 的结构解剖

在 n8n 中,所有数据都以 Item 的形式在节点之间传递。一个 Item 就是一个标准的 JSON 对象,包含两个顶层字段:

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// n8n Data Item 的完整结构
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

const item = {
  // 📦 json: 核心数据载体
  // 所有的业务数据都存放在这个字段中
  // 类型: 任意 JSON 对象 (Object)
  "json": {
    "id": 42,
    "name": "张三", 
    "email": "[email protected]",
    "tags": ["VIP", "enterprise"],     // 支持数组
    "address": {                        // 支持嵌套对象
      "city": "上海",
      "district": "浦东"
    }
  },
  
  // 📎 binary (可选): 二进制文件载体
  // 当 Item 附带文件时使用 (图片、PDF、CSV 等)
  "binary": {
    "attachment": {                     // 键名可自定义
      "data": "base64_encoded_string",  // 文件内容 (Base64)
      "mimeType": "image/png",          // MIME 类型
      "fileName": "screenshot.png",     // 原始文件名
      "fileSize": 1024000               // 文件大小 (bytes)
    }
  }
};

Items 是数组!

关键点:节点之间传递的不是单个 Item,而是 Item 数组

graph LR
    subgraph "节点 A 的输出"
        I1["Item 0
{name: '张三'}"] I2["Item 1
{name: '李四'}"] I3["Item 2
{name: '王五'}"] end subgraph "线缆传输" PIPE["📦📦📦
3 个 Items"] end subgraph "节点 B 的输入" R1["接收 Item 0"] R2["接收 Item 1"] R3["接收 Item 2"] end I1 & I2 & I3 --> PIPE --> R1 & R2 & R3 style PIPE fill:#f59e0b,stroke:#d97706,color:#fff

隐式循环机制 (The Implicit Loop)

这是 n8n 与传统编程最大的区别

在传统代码中,处理 3 条数据需要手写循环:

# ❌ 传统编程方式: 需要手动写循环
for user in users:              # 手动遍历
    result = process(user)      # 逐个处理
    results.append(result)      # 手动收集

在 n8n 中,循环是天然内置的——你甚至不会意识到它的存在:

sequenceDiagram
    participant HTTP as HTTP Request 节点
    participant Set as Set 节点
    participant Gmail as Gmail 节点
    
    Note over HTTP: 🔄 调用 API,返回 3 个用户
    HTTP->>HTTP: 输出: [{name:"张三"}, {name:"李四"}, {name:"王五"}]
    
    rect rgb(40, 60, 40)
        Note over Set: 🔁 隐式循环开始 (你看不到循环代码!)
        HTTP->>Set: Item 0: {name:"张三"}
        Set->>Set: 添加字段 greeting: "你好,张三"
        HTTP->>Set: Item 1: {name:"李四"}
        Set->>Set: 添加字段 greeting: "你好,李四"
        HTTP->>Set: Item 2: {name:"王五"}
        Set->>Set: 添加字段 greeting: "你好,王五"
        Note over Set: 🔁 隐式循环结束
    end
    
    Set->>Gmail: 3 个带 greeting 的 Items
    Note over Gmail: 自动发送 3 封邮件!

黄金法则:n8n 中的每个节点默认对每一个 Item 执行一次。上游吐出 N 个 Items,下游节点就执行 N 次。你不需要写任何循环。


五大数据操控模式

模式 1: 一进多出 (Split)

一个 Item 中包含数组字段,需要拆散成多个独立 Items。

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 输入: 1 个 Item,内含 3 个订单的数组
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// { "json": { "orders": [
//     { "id": 1, "amount": 99 },
//     { "id": 2, "amount": 150 },
//     { "id": 3, "amount": 75 }
// ]}}

// 使用 Code 节点拆分:
const orders = $input.first().json.orders;  // 取出数组

// 将数组中的每个元素转为独立的 Item
return orders.map(order => ({ json: order }));

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 输出: 3 个独立的 Items
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// [
//   { "json": { "id": 1, "amount": 99 } },
//   { "json": { "id": 2, "amount": 150 } },
//   { "json": { "id": 3, "amount": 75 } }
// ]

模式 2: 多进一出 (Aggregate)

多个 Items 合并为一个。

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 输入: 3 个 Items
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const allItems = $input.all();  // 获取所有上游 Items

// 聚合: 计算总金额
const totalAmount = allItems.reduce(
  (sum, item) => sum + item.json.amount,
  0  // 初始值
);

// 返回单个汇总 Item
return [{
  json: {
    totalOrders: allItems.length,     // 订单总数: 3
    totalAmount: totalAmount,          // 总金额: 324
    averageAmount: Math.round(totalAmount / allItems.length)  // 平均: 108
  }
}];

模式 3: 一一对应转换 (Map)

// 每个 Item 都独立处理,输入几个输出几个
// 使用 $input.item 获取当前正在处理的那一个 Item

const currentItem = $input.item;  // 当前 Item(隐式循环中的"这一个")

return {
  json: {
    ...currentItem.json,                     // 保留原有字段 (展开运算符)
    processedAt: new Date().toISOString(),    // 新增: 处理时间戳
    nameLength: currentItem.json.name.length  // 新增: 名字长度
  }
};

模式 4: 过滤 (Filter)

// 只保留满足条件的 Items
const items = $input.all();

return items.filter(item => {
  // 只保留 VIP 用户 且 消费超过 1000 的
  return item.json.isVIP === true && item.json.totalSpent > 1000;
});
// 输入 10 个 Items,可能只输出 3 个

模式 5: 双流合并 (Merge)

graph LR
    subgraph "数据源 1: 用户表"
        U["[{id:1, name:'张三'},
{id:2, name:'李四'}]"] end subgraph "数据源 2: 订单表" O["[{userId:1, amount:99},
{userId:2, amount:150}]"] end U --> M[Merge 节点
模式: Combine by Field
匹配字段: id = userId] O --> M M --> R["[{id:1, name:'张三', amount:99},
{id:2, name:'李四', amount:150}]"] style M fill:#6366f1,stroke:#4f46e5,color:#fff

常见陷阱与解法

陷阱 现象 原因 解决方案
空 Items 下游节点没有执行 上游输出了 [] 空数组 使用 If 节点检测 {{ $input.all().length > 0 }}
多余执行 Gmail 发了 100 封邮件 上游有 100 个 Items 流入 聚合后再发,或使用 Limit 节点
Binary 丢失 处理后文件消失 Code 节点没有传递 binary 字段 返回时包含 { json: {...}, binary: item.binary }
嵌套 JSON 无法访问深层字段 表达式写法错误 使用 {{ $json.address.city }} 用点号访问嵌套

数据流可视化总结

graph TB
    subgraph "n8n Item 生命周期"
        Birth["🐣 诞生
Trigger 产出初始 Items"] Transform["🔄 变换
Set / Code 节点修改字段"] Split["✂️ 拆分
1 个 Item → N 个 Items"] Filter["🔍 过滤
N 个 Items → M 个 (M ≤ N)"] Merge["🔗 合并
多源 Items 按条件 JOIN"] Aggregate["📊 聚合
N 个 Items → 1 个汇总"] Output["🎯 消费
Gmail / Slack / DB 节点使用"] end Birth --> Transform --> Split --> Filter --> Merge --> Aggregate --> Output Transform -->|"直接消费"| Output Filter -->|"直接消费"| Output

下一步

在 Ep 05 中,我们将把这些理论付诸实践——构建一个真实的 Webhook + Telegram Bot 双向交互工作流,让数据真正"流动"起来。