Edição de Dados
A edição de dados é fundamental para transformar, limpar e preparar informações para uso em workflows. Esta seção aborda técnicas avançadas de edição, transformação e manipulação de dados no n8n.
Visão Geral
A edição de dados envolve modificar, limpar e transformar informações para atender às necessidades específicas dos workflows. No n8n, você pode editar dados usando:
- Transformações básicas de tipos e formatos
- Limpeza e normalização de dados
- Enriquecimento com informações adicionais
- Validação e correção automática
- Transformações complexas com JavaScript
Tipos de Edição
Transformações Básicas
Operações fundamentais de transformação de dados:
javascript\n// Converter tipos de dados\nconst converterTipos = (dados) => {\n return dados.map(item => ({\n ...item,\n // Converter string para número\n valor: parseFloat(item.valor) || 0,\n // Converter string para boolean\n ativo: item.ativo === 'true' || item.ativo === true,\n // Converter string para data\n dataCriacao: new Date(item.dataCriacao),\n // Converter número para string\n id: item.id.toString()\n }));\n};\n\n// Formatar valores monetários\nconst formatarMoeda = (valor, moeda = 'BRL') => {\n return new Intl.NumberFormat('pt-BR', {\n style: 'currency',\n currency: moeda\n }).format(valor);\n};\n\n// Exemplo: Formatar preços\nconst produtosFormatados = produtos.map(produto => ({\n ...produto,\n precoFormatado: formatarMoeda(produto.preco),\n precoComDesconto: formatarMoeda(produto.preco * 0.9)\n}));\n
\n\n### Limpeza de Dados\n\nRemover inconsistências e padronizar dados:\n\njavascript\n// Limpar e normalizar texto\nconst limparTexto = (texto) => {\n if (!texto) return '';\n \n return texto\n .trim()\n .toLowerCase()\n .normalize('NFD')\n .replace(/[\u0300-\u036f]/g, '') // Remove acentos\n .replace(/[^\w\s]/g, '') // Remove caracteres especiais\n .replace(/\s+/g, ' '); // Remove espaços múltiplos\n};\n\n// Limpar dados de clientes\nconst limparDadosCliente = (cliente) => {\n return {\n ...cliente,\n nome: limparTexto(cliente.nome),\n email: cliente.email?.toLowerCase().trim(),\n telefone: cliente.telefone?.replace(/[^\d]/g, ''),\n cpf: cliente.cpf?.replace(/[^\d]/g, ''),\n endereco: {\n ...cliente.endereco,\n cep: cliente.endereco?.cep?.replace(/[^\d]/g, ''),\n cidade: limparTexto(cliente.endereco?.cidade || ''),\n estado: cliente.endereco?.estado?.toUpperCase()\n }\n };\n};\n\n// Remover duplicatas\nconst removerDuplicatas = (dados, campo) => {\n const unicos = new Set();\n return dados.filter(item => {\n const valor = item[campo];\n if (unicos.has(valor)) {\n return false;\n }\n unicos.add(valor);\n return true;\n });\n};\n\n// Exemplo: Remover clientes duplicados por email\nconst clientesUnicos = removerDuplicatas(clientes, 'email');\n
\n\n### Enriquecimento de Dados\n\nAdicionar informações complementares aos dados:\n\njavascript\n// Enriquecer dados de endereço com informações do CEP\nconst enriquecerEndereco = async (dados) => {\n const dadosEnriquecidos = [];\n \n for (const item of dados) {\n if (item.endereco?.cep) {\n try {\n // Consultar API do ViaCEP\n const response = await fetch(`https://viacep.com.br/ws/${item.endereco.cep}/json/`);\n const cepData = await response.json();\n \n if (!cepData.erro) {\n dadosEnriquecidos.push({\n ...item,\n endereco: {\n ...item.endereco,\n bairro: cepData.bairro || item.endereco.bairro,\n cidade: cepData.localidade || item.endereco.cidade,\n estado: cepData.uf || item.endereco.estado,\n logradouro: cepData.logradouro || item.endereco.logradouro\n }\n });\n } else {\n dadosEnriquecidos.push(item);\n }\n } catch (error) {\n console.error(`Erro ao consultar CEP ${item.endereco.cep}:`, error);\n dadosEnriquecidos.push(item);\n }\n } else {\n dadosEnriquecidos.push(item);\n }\n }\n \n return dadosEnriquecidos;\n};\n\n// Enriquecer com dados de geolocalização\nconst enriquecerGeolocalizacao = async (dados) => {\n const dadosComGeo = [];\n \n for (const item of dados) {\n if (item.endereco?.cidade && item.endereco?.estado) {\n try {\n const endereco = `${item.endereco.logradouro}, ${item.endereco.cidade}, ${item.endereco.estado}`;\n const response = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(endereco)}&key=YOUR_API_KEY`);\n const geoData = await response.json();\n \n if (geoData.results.length > 0) {\n const location = geoData.results[0].geometry.location;\n dadosComGeo.push({\n ...item,\n geolocalizacao: {\n latitude: location.lat,\n longitude: location.lng\n }\n });\n } else {\n dadosComGeo.push(item);\n }\n } catch (error) {\n console.error('Erro ao obter geolocalização:', error);\n dadosComGeo.push(item);\n }\n } else {\n dadosComGeo.push(item);\n }\n }\n \n return dadosComGeo;\n};\n
\n\n## Edição de Dados Brasileiros\n\n### Formatação de Documentos\n\njavascript\n// Formatar CPF\nconst formatarCPF = (cpf) => {\n if (!cpf) return '';\n \n const numeros = cpf.replace(/[^\d]/g, '');\n if (numeros.length !== 11) return cpf;\n \n return numeros.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4');\n};\n\n// Formatar CNPJ\nconst formatarCNPJ = (cnpj) => {\n if (!cnpj) return '';\n \n const numeros = cnpj.replace(/[^\d]/g, '');\n if (numeros.length !== 14) return cnpj;\n \n return numeros.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, '$1.$2.$3/$4-$5');\n};\n\n// Formatar telefone brasileiro\nconst formatarTelefone = (telefone) => {\n if (!telefone) return '';\n \n const numeros = telefone.replace(/[^\d]/g, '');\n \n if (numeros.length === 11) {\n return numeros.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3');\n } else if (numeros.length === 10) {\n return numeros.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3');\n }\n \n return telefone;\n};\n\n// Formatar CEP\nconst formatarCEP = (cep) => {\n if (!cep) return '';\n \n const numeros = cep.replace(/[^\d]/g, '');\n if (numeros.length !== 8) return cep;\n \n return numeros.replace(/(\d{5})(\d{3})/, '$1-$2');\n};\n\n// Aplicar formatação em lote\nconst formatarDocumentos = (dados) => {\n return dados.map(item => ({\n ...item,\n cpf: formatarCPF(item.cpf),\n cnpj: formatarCNPJ(item.cnpj),\n telefone: formatarTelefone(item.telefone),\n endereco: {\n ...item.endereco,\n cep: formatarCEP(item.endereco?.cep)\n }\n }));\n};\n
\n\n### Validação e Correção\n\njavascript\n// Validar e corrigir CPF\nconst validarECorrigirCPF = (cpf) => {\n if (!cpf) return { valido: false, corrigido: null };\n \n const numeros = cpf.replace(/[^\d]/g, '');\n \n // Verificar se tem 11 dígitos\n if (numeros.length !== 11) {\n return { valido: false, corrigido: null };\n }\n \n // Verificar se não são todos iguais\n if (/^(\d)\1{10}$/.test(numeros)) {\n return { valido: false, corrigido: null };\n }\n \n // Calcular dígitos verificadores\n let soma = 0;\n for (let i = 0; i < 9; i++) {\n soma += parseInt(numeros[i]) * (10 - i);\n }\n const digito1 = ((soma * 10) % 11) % 10;\n \n soma = 0;\n for (let i = 0; i < 10; i++) {\n soma += parseInt(numeros[i]) * (11 - i);\n }\n const digito2 = ((soma * 10) % 11) % 10;\n \n const valido = parseInt(numeros[9]) === digito1 && parseInt(numeros[10]) === digito2;\n \n return {\n valido,\n corrigido: valido ? formatarCPF(numeros) : null\n };\n};\n\n// Validar e corrigir email\nconst validarECorrigirEmail = (email) => {\n if (!email) return { valido: false, corrigido: null };\n \n const emailLimpo = email.toLowerCase().trim();\n const regexEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;\n \n if (regexEmail.test(emailLimpo)) {\n return { valido: true, corrigido: emailLimpo };\n }\n \n // Tentar corrigir erros comuns\n const correcoes = [\n emailLimpo.replace(/\s+/g, ''), // Remove espaços\n emailLimpo.replace(/\.com\.br$/g, '.com.br'), // Corrige domínio\n emailLimpo.replace(/@gmail\.com$/g, '@gmail.com'), // Corrige Gmail\n emailLimpo.replace(/@hotmail\.com$/g, '@hotmail.com') // Corrige Hotmail\n ];\n \n for (const correcao of correcoes) {\n if (regexEmail.test(correcao)) {\n return { valido: true, corrigido: correcao };\n }\n }\n \n return { valido: false, corrigido: null };\n};\n\n// Aplicar validação e correção em lote\nconst validarECorrigirDados = (dados) => {\n const resultados = {\n validos: [],\n corrigidos: [],\n invalidos: []\n };\n \n dados.forEach(item => {\n const cpfResultado = validarECorrigirCPF(item.cpf);\n const emailResultado = validarECorrigirEmail(item.email);\n \n const itemCorrigido = {\n ...item,\n cpf: cpfResultado.corrigido || item.cpf,\n email: emailResultado.corrigido || item.email\n };\n \n if (cpfResultado.valido && emailResultado.valido) {\n resultados.validos.push(itemCorrigido);\n } else if (cpfResultado.corrigido || emailResultado.corrigido) {\n resultados.corrigidos.push(itemCorrigido);\n } else {\n resultados.invalidos.push(item);\n }\n });\n \n return resultados;\n};\n
\n\n## Transformações Avançadas\n\n### Transformações de Arrays\n\njavascript\n// Agrupar dados por campo\nconst agruparPorCampo = (dados, campo) => {\n return dados.reduce((grupos, item) => {\n const valor = item[campo];\n if (!grupos[valor]) {\n grupos[valor] = [];\n }\n grupos[valor].push(item);\n return grupos;\n }, {});\n};\n\n// Exemplo: Agrupar clientes por estado\nconst clientesPorEstado = agruparPorCampo(clientes, 'estado');\n\n// Transformar array em objeto\nconst arrayParaObjeto = (dados, chave, valor) => {\n return dados.reduce((obj, item) => {\n obj[item[chave]] = item[valor];\n return obj;\n }, {});\n};\n\n// Exemplo: Criar mapa de produtos por ID\nconst produtosMap = arrayParaObjeto(produtos, 'id', 'nome');\n\n// Achatamento de arrays aninhados\nconst achatarArray = (dados, campo) => {\n return dados.reduce((resultado, item) => {\n const array = item[campo];\n if (Array.isArray(array)) {\n resultado.push(...array);\n }\n return resultado;\n }, []);\n};\n\n// Exemplo: Achatamento de pedidos\nconst todosOsItens = achatarArray(pedidos, 'itens');\n
\n\n### Transformações Condicionais\n\njavascript\n// Aplicar transformações baseadas em condições\nconst transformarCondicional = (dados, condicoes) => {\n return dados.map(item => {\n let itemTransformado = { ...item };\n \n condicoes.forEach(condicao => {\n const { campo, valor, operador, transformacao } = condicao;\n \n let aplicar = false;\n switch (operador) {\n case '==':\n aplicar = item[campo] === valor;\n break;\n case '>':\n aplicar = parseFloat(item[campo]) > valor;\n break;\n case '<':\n aplicar = parseFloat(item[campo]) < valor;\n break;\n case 'contains':\n aplicar = item[campo].toLowerCase().includes(valor.toLowerCase());\n break;\n }\n \n if (aplicar) {\n itemTransformado = { ...itemTransformado, ...transformacao(item) };\n }\n });\n \n return itemTransformado;\n });\n};\n\n// Exemplo: Aplicar descontos baseados em valor\nconst produtosComDesconto = transformarCondicional(produtos, [\n {\n campo: 'preco',\n valor: 100,\n operador: '>',\n transformacao: (item) => ({\n precoComDesconto: item.preco * 0.9,\n desconto: '10%'\n })\n },\n {\n campo: 'preco',\n valor: 50,\n operador: '>',\n transformacao: (item) => ({\n precoComDesconto: item.preco * 0.95,\n desconto: '5%'\n })\n }\n]);\n
\n\n### Transformações de Datas\n\njavascript\n// Normalizar formatos de data\nconst normalizarData = (data) => {\n if (!data) return null;\n \n // Tentar diferentes formatos\n const formatos = [\n 'YYYY-MM-DD',\n 'DD/MM/YYYY',\n 'MM/DD/YYYY',\n 'DD-MM-YYYY',\n 'MM-DD-YYYY'\n ];\n \n for (const formato of formatos) {\n try {\n const dataObj = new Date(data);\n if (!isNaN(dataObj.getTime())) {\n return dataObj.toISOString().split('T')[0];\n }\n } catch (error) {\n continue;\n }\n }\n \n return null;\n};\n\n// Calcular idade a partir da data de nascimento\nconst calcularIdade = (dataNascimento) => {\n if (!dataNascimento) return null;\n \n const hoje = new Date();\n const nascimento = new Date(dataNascimento);\n let idade = hoje.getFullYear() - nascimento.getFullYear();\n \n const mesAtual = hoje.getMonth();\n const mesNascimento = nascimento.getMonth();\n \n if (mesAtual < mesNascimento || \n (mesAtual === mesNascimento && hoje.getDate() < nascimento.getDate())) {\n idade--;\n }\n \n return idade;\n};\n\n// Formatar data para português brasileiro\nconst formatarDataBR = (data) => {\n if (!data) return '';\n \n const dataObj = new Date(data);\n return dataObj.toLocaleDateString('pt-BR', {\n day: '2-digit',\n month: '2-digit',\n year: 'numeric'\n });\n};\n\n// Aplicar transformações de data\nconst transformarDatas = (dados) => {\n return dados.map(item => ({\n ...item,\n dataNascimento: normalizarData(item.dataNascimento),\n idade: calcularIdade(item.dataNascimento),\n dataFormatada: formatarDataBR(item.dataCriacao)\n }));\n};\n
\n\n## Edição em Tempo Real\n\n### Stream de Transformações\n\njavascript\n// Processar dados em stream para grandes volumes\nconst processarStream = async (dados, transformacoes, tamanhoLote = 1000) => {\n const resultados = [];\n \n for (let i = 0; i < dados.length; i += tamanhoLote) {\n const lote = dados.slice(i, i + tamanhoLote);\n \n // Aplicar transformações no lote\n let loteProcessado = lote;\n for (const transformacao of transformacoes) {\n loteProcessado = await transformacao(loteProcessado);\n }\n \n resultados.push(...loteProcessado);\n \n // Log de progresso\n console.log(`Processados ${Math.min(i + tamanhoLote, dados.length)} de ${dados.length} registros`);\n }\n \n return resultados;\n};\n\n// Exemplo de uso\nconst transformacoes = [\n limparDadosCliente,\n formatarDocumentos,\n validarECorrigirDados\n];\n\nconst dadosProcessados = await processarStream(clientes, transformacoes);\n
\n\n### Transformações Assíncronas\n\njavascript\n// Transformações que requerem chamadas de API\nconst transformacoesAssincronas = async (dados) => {\n const resultados = [];\n \n // Processar em paralelo com limite de concorrência\n const limiteConcorrencia = 5;\n const chunks = [];\n \n for (let i = 0; i < dados.length; i += limiteConcorrencia) {\n chunks.push(dados.slice(i, i + limiteConcorrencia));\n }\n \n for (const chunk of chunks) {\n const promises = chunk.map(async (item) => {\n try {\n // Enriquecer com dados externos\n const dadosEnriquecidos = await enriquecerEndereco([item]);\n const dadosComGeo = await enriquecerGeolocalizacao(dadosEnriquecidos);\n \n return dadosComGeo[0];\n } catch (error) {\n console.error('Erro ao processar item:', error);\n return item;\n }\n });\n \n const chunkResultados = await Promise.all(promises);\n resultados.push(...chunkResultados);\n }\n \n return resultados;\n};\n
\n\n## Validação de Transformações\n\n### Verificação de Integridade\n\njavascript\n// Verificar integridade após transformações\nconst verificarIntegridade = (dadosOriginais, dadosTransformados) => {\n const verificacoes = {\n totalRegistros: dadosOriginais.length === dadosTransformados.length,\n camposObrigatorios: true,\n tiposCorretos: true,\n valoresValidos: true,\n erros: []\n };\n \n // Verificar campos obrigatórios\n const camposObrigatorios = ['id', 'nome', 'email'];\n \n dadosTransformados.forEach((item, index) => {\n camposObrigatorios.forEach(campo => {\n if (!item[campo]) {\n verificacoes.camposObrigatorios = false;\n verificacoes.erros.push(`Campo obrigatório '${campo}' ausente no item ${index}`);\n }\n });\n \n // Verificar tipos\n if (typeof item.valor !== 'number') {\n verificacoes.tiposCorretos = false;\n verificacoes.erros.push(`Tipo incorreto para 'valor' no item ${index}`);\n }\n \n // Verificar valores válidos\n if (item.valor < 0) {\n verificacoes.valoresValidos = false;\n verificacoes.erros.push(`Valor negativo no item ${index}`);\n }\n });\n \n return verificacoes;\n};\n\n// Aplicar transformações com validação\nconst transformarComValidacao = async (dados, transformacoes) => {\n const dadosOriginais = [...dados];\n let dadosAtuais = dados;\n \n for (const transformacao of transformacoes) {\n dadosAtuais = await transformacao(dadosAtuais);\n \n // Validar após cada transformação\n const integridade = verificarIntegridade(dadosOriginais, dadosAtuais);\n \n if (!integridade.totalRegistros) {\n throw new Error('Perda de registros durante transformação');\n }\n \n if (integridade.erros.length > 0) {\n console.warn('Avisos de integridade:', integridade.erros);\n }\n }\n \n return dadosAtuais;\n};\n
\n\n## Workflows de Edição\n\n### Workflow: Pipeline de Limpeza de Dados\n\nmermaid\ngraph TD\n A[HTTP Request: Dados Brutos] --> B[Code: Limpeza Básica]\n B --> C[Code: Validação]\n C --> D[Code: Formatação]\n D --> E[Code: Enriquecimento]\n E --> F[HTTP Request: Salvar Dados Limpos]\n F --> G[Send Notification]\n
\n\n### Workflow: Transformação em Tempo Real\n\nmermaid\ngraph TD\n A[Webhook Trigger] --> B[Code: Validação Rápida]\n B --> C[Code: Transformação]\n C --> D[HTTP Request: API Externa]\n D --> E[Code: Enriquecimento]\n E --> F[HTTP Request: Salvar]\n F --> G[Send Response]\n
Boas Práticas
Performance
- Processe em lotes para grandes volumes
- Use cache para transformações repetitivas
- Paralelize operações independentes
- Monitore uso de memória durante transformações
- Otimize loops e operações custosas
Qualidade
- Valide dados antes e depois das transformações
- Mantenha logs de todas as alterações
- Teste transformações com dados reais
- Documente regras de transformação
- Implemente rollback para transformações críticas
Manutenibilidade
- Modularize transformações em funções reutilizáveis
- Use configurações para parâmetros de transformação
- Implemente versionamento de transformações
- Crie testes unitários para transformações
- Documente dependências entre transformações
Recursos Adicionais
Bibliotecas Úteis
- Lodash: Utilitários para manipulação de dados
- Ramda: Programação funcional para transformações
- date-fns: Manipulação avançada de datas
- validator.js: Validação robusta de dados
Padrões de Transformação
- Builder Pattern: Construir transformações complexas
- Pipeline Pattern: Encadear transformações
- Strategy Pattern: Diferentes algoritmos de transformação
- Observer Pattern: Monitorar mudanças em dados
Próximo: Agregações Estatísticas - Calcule métricas e estatísticas