Skip to content

n8n 数据处理与转换 - 让数据流动起来

在工作流中,数据就像血液一样在各个节点间流动。掌握数据处理技巧是构建高效工作流的关键。今天我们来深入学习 n8n 的数据处理和转换方法。

数据结构基础

n8n 数据格式

n8n 中的数据以 JSON 格式在节点间传递,基本结构如下:

json
[
  {
    "json": {
      "name": "张三",
      "age": 30,
      "email": "zhangsan@example.com"
    },
    "binary": {},
    "pairedItem": {
      "item": 0
    }
  }
]

关键字段说明

  • json:主要数据内容
  • binary:二进制数据(文件、图片等)
  • pairedItem:数据来源追踪

数据类型

基本类型

  • String:字符串
  • Number:数字
  • Boolean:布尔值
  • Array:数组
  • Object:对象

复杂类型

  • Date:日期时间
  • Binary:二进制数据
  • Null/Undefined:空值

Set 节点详解

Set 节点是最常用的数据处理节点,用于设置、修改或删除数据字段。

基本操作

添加字段

json
{
  "values": {
    "string": [
      {
        "name": "status",
        "value": "processed"
      }
    ],
    "number": [
      {
        "name": "priority",
        "value": 1
      }
    ],
    "boolean": [
      {
        "name": "isActive",
        "value": true
      }
    ]
  }
}

使用表达式

json
{
  "values": {
    "string": [
      {
        "name": "fullName",
        "value": "={{ $json.firstName }} {{ $json.lastName }}"
      },
      {
        "name": "processedAt",
        "value": "={{ $now }}"
      }
    ]
  }
}

高级用法

条件设置

json
{
  "values": {
    "string": [
      {
        "name": "category",
        "value": "={{ $json.age >= 18 ? 'adult' : 'minor' }}"
      }
    ]
  }
}

数组处理

json
{
  "values": {
    "string": [
      {
        "name": "tags",
        "value": "={{ $json.categories.join(', ') }}"
      }
    ]
  }
}

对象操作

json
{
  "values": {
    "object": [
      {
        "name": "address",
        "value": {
          "street": "={{ $json.street }}",
          "city": "={{ $json.city }}",
          "country": "中国"
        }
      }
    ]
  }
}

Code 节点深入

Code 节点提供了最大的灵活性,可以执行复杂的数据处理逻辑。

基本语法

javascript
// 处理单个数据项
const item = items[0].json;

// 数据转换
const processedData = {
  id: item.id,
  name: item.name.trim().toLowerCase(),
  email: item.email.toLowerCase(),
  createdAt: new Date().toISOString()
};

return [{
  json: processedData
}];

批量处理

javascript
// 处理多个数据项
const processedItems = items.map(item => {
  const data = item.json;
  
  return {
    json: {
      ...data,
      processed: true,
      processedAt: new Date().toISOString(),
      hash: require('crypto')
        .createHash('md5')
        .update(data.email)
        .digest('hex')
    }
  };
});

return processedItems;

数据验证

javascript
// 数据验证和清洗
const validItems = [];
const invalidItems = [];

items.forEach(item => {
  const data = item.json;
  
  // 验证必填字段
  if (!data.email || !data.name) {
    invalidItems.push({
      ...item,
      json: {
        ...data,
        error: 'Missing required fields'
      }
    });
    return;
  }
  
  // 验证邮箱格式
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(data.email)) {
    invalidItems.push({
      ...item,
      json: {
        ...data,
        error: 'Invalid email format'
      }
    });
    return;
  }
  
  // 数据清洗
  validItems.push({
    json: {
      id: data.id,
      name: data.name.trim(),
      email: data.email.toLowerCase().trim(),
      phone: data.phone ? data.phone.replace(/\D/g, '') : null,
      isValid: true
    }
  });
});

// 返回有效数据,无效数据可以通过错误处理流程处理
return validItems;

外部库使用

javascript
// 使用内置库
const crypto = require('crypto');
const moment = require('moment');

const processedItems = items.map(item => {
  const data = item.json;
  
  return {
    json: {
      ...data,
      hash: crypto.createHash('sha256').update(data.id).digest('hex'),
      formattedDate: moment(data.createdAt).format('YYYY-MM-DD HH:mm:ss'),
      timestamp: Date.now()
    }
  };
});

return processedItems;

数据转换实战

JSON 数据扁平化

javascript
// 将嵌套对象扁平化
function flattenObject(obj, prefix = '') {
  const flattened = {};
  
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const newKey = prefix ? `${prefix}.${key}` : key;
      
      if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
        Object.assign(flattened, flattenObject(obj[key], newKey));
      } else {
        flattened[newKey] = obj[key];
      }
    }
  }
  
  return flattened;
}

const processedItems = items.map(item => {
  const flatData = flattenObject(item.json);
  
  return {
    json: flatData
  };
});

return processedItems;

CSV 数据处理

javascript
// 处理 CSV 格式数据
const csvData = items[0].json.csvContent;
const lines = csvData.split('\n');
const headers = lines[0].split(',').map(h => h.trim());

const processedData = lines.slice(1)
  .filter(line => line.trim()) // 过滤空行
  .map(line => {
    const values = line.split(',').map(v => v.trim());
    const obj = {};
    
    headers.forEach((header, index) => {
      obj[header] = values[index] || '';
    });
    
    return { json: obj };
  });

return processedData;

API 响应数据处理

javascript
// 处理 API 响应数据
const apiResponse = items[0].json;

// 提取分页信息
const pagination = {
  currentPage: apiResponse.page,
  totalPages: apiResponse.total_pages,
  totalItems: apiResponse.total,
  hasNext: apiResponse.page < apiResponse.total_pages
};

// 处理数据项
const processedItems = apiResponse.data.map(item => ({
  json: {
    id: item.id,
    title: item.title,
    description: item.description,
    createdAt: new Date(item.created_at).toISOString(),
    author: {
      id: item.author.id,
      name: item.author.name,
      email: item.author.email
    },
    tags: item.tags || [],
    isPublished: item.status === 'published'
  }
}));

// 添加分页信息到第一个项目
if (processedItems.length > 0) {
  processedItems[0].json.pagination = pagination;
}

return processedItems;

Function 节点应用

Function 节点适合简单的数据转换操作。

字符串处理

javascript
// 格式化姓名
return {
  fullName: $json.firstName + ' ' + $json.lastName,
  initials: ($json.firstName.charAt(0) + $json.lastName.charAt(0)).toUpperCase()
};

数字计算

javascript
// 计算折扣价格
return {
  originalPrice: $json.price,
  discount: $json.discount || 0,
  finalPrice: $json.price * (1 - ($json.discount || 0) / 100),
  savings: $json.price * (($json.discount || 0) / 100)
};

日期处理

javascript
// 日期格式化
const date = new Date($json.timestamp);
return {
  originalTimestamp: $json.timestamp,
  formattedDate: date.toLocaleDateString('zh-CN'),
  formattedTime: date.toLocaleTimeString('zh-CN'),
  dayOfWeek: ['日', '一', '二', '三', '四', '五', '六'][date.getDay()],
  isWeekend: date.getDay() === 0 || date.getDay() === 6
};

数据聚合与分组

使用 Code 节点进行分组

javascript
// 按类别分组数据
const groupedData = {};

items.forEach(item => {
  const data = item.json;
  const category = data.category || 'uncategorized';
  
  if (!groupedData[category]) {
    groupedData[category] = [];
  }
  
  groupedData[category].push(data);
});

// 转换为数组格式
const result = Object.keys(groupedData).map(category => ({
  json: {
    category: category,
    items: groupedData[category],
    count: groupedData[category].length,
    totalValue: groupedData[category].reduce((sum, item) => sum + (item.value || 0), 0)
  }
}));

return result;

数据统计

javascript
// 计算统计信息
const values = items.map(item => item.json.value || 0);
const count = values.length;
const sum = values.reduce((a, b) => a + b, 0);
const avg = count > 0 ? sum / count : 0;
const min = Math.min(...values);
const max = Math.max(...values);

// 计算中位数
const sorted = values.sort((a, b) => a - b);
const median = count % 2 === 0 
  ? (sorted[count / 2 - 1] + sorted[count / 2]) / 2
  : sorted[Math.floor(count / 2)];

return [{
  json: {
    statistics: {
      count,
      sum,
      average: avg,
      minimum: min,
      maximum: max,
      median,
      range: max - min
    },
    data: items.map(item => item.json)
  }
}];

错误处理与数据验证

数据验证模式

javascript
// 定义验证规则
const validationRules = {
  email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
  phone: /^1[3-9]\d{9}$/,
  required: ['name', 'email']
};

const processedItems = items.map(item => {
  const data = item.json;
  const errors = [];
  
  // 检查必填字段
  validationRules.required.forEach(field => {
    if (!data[field] || data[field].toString().trim() === '') {
      errors.push(`${field} is required`);
    }
  });
  
  // 验证邮箱格式
  if (data.email && !validationRules.email.test(data.email)) {
    errors.push('Invalid email format');
  }
  
  // 验证手机号格式
  if (data.phone && !validationRules.phone.test(data.phone)) {
    errors.push('Invalid phone format');
  }
  
  return {
    json: {
      ...data,
      isValid: errors.length === 0,
      errors: errors
    }
  };
});

return processedItems;

异常处理

javascript
try {
  const processedItems = items.map(item => {
    const data = item.json;
    
    // 可能出错的操作
    const parsedDate = new Date(data.dateString);
    if (isNaN(parsedDate.getTime())) {
      throw new Error(`Invalid date: ${data.dateString}`);
    }
    
    return {
      json: {
        ...data,
        parsedDate: parsedDate.toISOString(),
        processed: true
      }
    };
  });
  
  return processedItems;
} catch (error) {
  // 返回错误信息
  return [{
    json: {
      error: true,
      message: error.message,
      originalData: items
    }
  }];
}

性能优化技巧

批量处理优化

javascript
// 分批处理大量数据
const BATCH_SIZE = 100;
const allItems = items;
const batches = [];

for (let i = 0; i < allItems.length; i += BATCH_SIZE) {
  const batch = allItems.slice(i, i + BATCH_SIZE);
  batches.push(batch);
}

// 处理每个批次
const processedBatches = batches.map((batch, batchIndex) => {
  const processedBatch = batch.map(item => {
    // 处理逻辑
    return {
      json: {
        ...item.json,
        batchIndex,
        processed: true
      }
    };
  });
  
  return processedBatch;
});

// 合并结果
return processedBatches.flat();

内存优化

javascript
// 避免创建大量临时对象
const processedItems = [];

for (let i = 0; i < items.length; i++) {
  const item = items[i];
  const data = item.json;
  
  // 直接修改现有对象而不是创建新对象
  data.processed = true;
  data.processedAt = Date.now();
  
  processedItems.push(item);
}

return processedItems;

小结

数据处理是 n8n 工作流的核心技能,主要要点:

  1. 理解数据结构:掌握 n8n 的数据格式和传递机制
  2. 选择合适工具:Set 节点用于简单操作,Code 节点用于复杂逻辑
  3. 数据验证:确保数据质量和完整性
  4. 错误处理:优雅地处理异常情况
  5. 性能优化:合理处理大量数据

下一篇文章,我们将学习条件逻辑和分支控制,这是构建智能工作流的重要技术。

记住,好的数据处理不仅要功能正确,还要考虑性能、可维护性和错误处理。多练习不同类型的数据转换,你会发现数据处理的乐趣所在。