第 4 期:数据的心跳 — 深度搞懂 JSON Items 与隐式循环
为什么这一期是全课程最重要的?
在 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 双向交互工作流,让数据真正"流动"起来。