Como Converter JSON para CSV em JavaScript (2026): Guia Completo com Código
Converter JSON para CSV parece trivial até você topar com objetos aninhados, acentuação, vírgulas dentro de campos e encoding. Este guia mostra abordagens manuais e com PapaParse, tratamento de edge cases, geração de download no browser e ingestão em banco de dados — com código pronto para copiar.
Por Vitor Morais
Fundador do MochaLabz ·
Converta JSON ↔ CSV online
Cole JSON, baixe CSV (ou inverso) — tudo no navegador, sem enviar dados pra servidor.
Usar conversor →Converter JSON em CSV é uma das tarefas mais recorrentes no dia a dia de um dev: API devolve JSON, cliente pede planilha Excel; banco exporta dados em JSON, analista quer abrir no Google Sheets; dashboard coleta eventos em JSON, compliance quer CSV para auditoria. Parece trivial — até você topar com objetos aninhados, vírgulas em campos de texto, acentuação em UTF-8 que quebra no Excel e linhas com schema divergente.
Este guia cobre o caminho completo: abordagem manual (para entender o problema), uso de PapaParse (padrão da indústria), tratamento de edge cases, download no browser, performance em arquivos grandes e ingestão em banco a partir do CSV gerado.
O problema em uma frase
JSON é hierárquico e tipado (strings, números, booleanos, arrays, objetos aninhados). CSV é plano (linhas × colunas) e só tem texto. A conversão exige decidir como representar hierarquia em duas dimensões e como preservar tipos que CSV não conhece.
Conversão manual: o ponto de partida
Para entender o que está acontecendo, implemente sem biblioteca:
function jsonToCsv(jsonArray: Record<string, unknown>[]): string {
if (jsonArray.length === 0) return "";
// Usa as chaves do primeiro objeto como colunas
const fields = Object.keys(jsonArray[0]);
// Cabeçalho
const header = fields.map(escape).join(",");
// Linhas
const rows = jsonArray.map((row) =>
fields.map((f) => escape(String(row[f] ?? ""))).join(","),
);
return [header, ...rows].join("\n");
}
function escape(value: string): string {
// Escapa aspas duplas e envolve em aspas se contiver , " ou \n
if (/[,"\n]/.test(value)) {
return `"${value.replace(/"/g, '""')}"`;
}
return value;
}
// Uso
const data = [
{ id: 1, nome: "João", email: "joao@exemplo.com" },
{ id: 2, nome: 'Maria "Silva"', email: "maria@exemplo.com" },
];
console.log(jsonToCsv(data));
// id,nome,email
// 1,João,joao@exemplo.com
// 2,"Maria ""Silva""",maria@exemplo.comAtenção
PapaParse: padrão da indústria
npm install papaparse
npm install -D @types/papaparseUso básico
import Papa from "papaparse";
const data = [
{ id: 1, nome: "João", email: "joao@exemplo.com" },
{ id: 2, nome: 'Maria "Silva"', email: "maria@exemplo.com" },
];
const csv = Papa.unparse(data);
console.log(csv);
// id,nome,email
// 1,João,joao@exemplo.com
// 2,"Maria ""Silva""",maria@exemplo.comCom configuração avançada
const csv = Papa.unparse(data, {
delimiter: ",", // padrão; pode usar ";" para Excel PT-BR
newline: "\n", // ou "\r\n" para Windows
quotes: false, // true força aspas em todos os campos
quoteChar: '"',
escapeChar: '"',
header: true, // inclui cabeçalho (padrão true)
columns: ["id", "nome", "email"], // ordem e campos fixos
});Dica
;) como separador por padrão (o ponto decimal é vírgula). Se gerar CSV para usuários brasileiros abrindo no Excel local, use delimiter: “;”. Para ferramentas internacionais (Google Sheets, Airtable), vírgula é o padrão.Flatten: lidando com objetos aninhados
CSV não suporta hierarquia. Se você tem JSON como:
[
{
id: 1,
nome: "João",
endereco: {
rua: "Av. Paulista",
cidade: "São Paulo",
uf: "SP"
}
}
]Você precisa transformar em algo como:
id,nome,endereco.rua,endereco.cidade,endereco.uf
1,João,Av. Paulista,São Paulo,SPFunção de flatten
function flatten(
obj: Record<string, unknown>,
prefix = "",
): Record<string, unknown> {
const result: Record<string, unknown> = {};
for (const key of Object.keys(obj)) {
const value = obj[key];
const newKey = prefix ? `${prefix}.${key}` : key;
if (
value !== null &&
typeof value === "object" &&
!Array.isArray(value)
) {
Object.assign(result, flatten(value as Record<string, unknown>, newKey));
} else if (Array.isArray(value)) {
result[newKey] = JSON.stringify(value); // arrays viram JSON string
} else {
result[newKey] = value;
}
}
return result;
}
// Uso
const flatData = data.map((row) => flatten(row));
const csv = Papa.unparse(flatData);Encoding: BOM para Excel abrir corretamente
O maior problema prático é Excel abrindo CSV com acento corrompido. Causa: Excel em Windows tenta detectar encoding a partir de BOM. Sem BOM, assume Windows-1252 (não-UTF-8), e acentos quebram.
function jsonToCsvComBom(data: Record<string, unknown>[]): string {
const csv = Papa.unparse(data);
return "\uFEFF" + csv; // BOM no início
}
// Ao gerar Blob no browser
const csvComBom = jsonToCsvComBom(data);
const blob = new Blob([csvComBom], {
type: "text/csv;charset=utf-8",
});Contexto
Download no browser: o padrão
function baixarCsv(
data: Record<string, unknown>[],
nome = "dados.csv",
) {
const csv = "\uFEFF" + Papa.unparse(data);
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = nome;
link.click();
URL.revokeObjectURL(url); // libera memória
}
// Uso
baixarCsv(meusdados, "relatorio-abril.csv");Node.js: escrevendo direto no arquivo
import Papa from "papaparse";
import { writeFileSync } from "node:fs";
const data = [
{ id: 1, nome: "João" },
{ id: 2, nome: "Maria" },
];
const csv = "\uFEFF" + Papa.unparse(data);
writeFileSync("./dados.csv", csv, "utf8");
console.log("CSV exportado com sucesso.");Para arquivos grandes: streaming
import { createWriteStream } from "node:fs";
import Papa from "papaparse";
const stream = createWriteStream("./dados-grandes.csv");
// Escreve BOM
stream.write("\uFEFF");
// Escreve header
stream.write(Papa.unparse([{}], { header: true, columns: ["id", "nome"] }));
stream.write("\n");
// Escreve linhas em batch
for (const batch of getBatchesFromDb()) {
const chunk = Papa.unparse(batch, { header: false });
stream.write(chunk);
stream.write("\n");
}
stream.end();Edge cases comuns
| Critério | Representação em CSV |
|---|---|
| String simples | João |
| String com vírgula | "João, Maria" |
| String com aspas | "João ""Silva""" |
| String com nova linha | "Texto\nquebrado" |
| Número inteiro | 42 |
| Número decimal | 3.14 |
| Boolean | true | false |
| null | (célula vazia) |
| undefined | (célula vazia) |
| Array de strings | "[\"a\",\"b\"]" |
| Objeto aninhado | flatten ou JSON stringify |
| Data ISO 8601 | 2026-04-20T12:00:00Z |
Preservando tipos com Excel: exportando .xlsx
CSV perde tipos — tudo vira string. Para preservar data, número decimal e formatação, use SheetJS para gerar .xlsx direto:
npm install xlsximport * as XLSX from "xlsx";
function baixarXlsx(data: Record<string, unknown>[], nome = "dados.xlsx") {
const worksheet = XLSX.utils.json_to_sheet(data);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, "Dados");
XLSX.writeFile(workbook, nome);
}
baixarXlsx(meusdados, "relatorio.xlsx");Benchmark: CSV vs XLSX vs JSON
| Critério | Vantagem | Desvantagem |
|---|---|---|
| CSV | Universal, rápido, leve | Perde tipos, sensível a delimitador |
| XLSX (Excel) | Preserva tipos, formatação | Binário, mais pesado |
| JSON | Preserva tipos, hierarquia | Não abre no Excel, analista sofre |
| Parquet | Compressão, tipos, eficiente para big data | Inútil pra usuário final |
| TSV | Similar ao CSV, menos conflito de vírgulas | Menos suportado |
Escolhendo a ferramenta certa
- Export pequeno para o usuário final: CSV com BOM no frontend, download automático.
- Relatório técnico para analista: XLSX via SheetJS (tipos preservados).
- Integração entre sistemas: JSON.
- Big data (milhões+ de linhas): Parquet ou compression CSV (.csv.gz).
- Ingestão em banco a partir de CSV: ver artigo importar CSV para banco.
Componente React pronto
"use client";
import Papa from "papaparse";
interface BotaoExportarProps {
data: Record<string, unknown>[];
nomeArquivo?: string;
children?: React.ReactNode;
}
export function BotaoExportarCsv({
data,
nomeArquivo = "dados.csv",
children = "Exportar CSV",
}: BotaoExportarProps) {
const handleClick = () => {
const csv = "\uFEFF" + Papa.unparse(data);
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = nomeArquivo;
link.click();
URL.revokeObjectURL(url);
};
return (
<button
onClick={handleClick}
disabled={data.length === 0}
className="px-4 py-2 bg-caramel-500 text-white rounded disabled:opacity-50"
>
{children}
</button>
);
}
// Uso
<BotaoExportarCsv data={pedidos} nomeArquivo="pedidos-abril.csv" />Performance em arquivos grandes
PapaParse é rápido, mas há limites. Referência em hardware moderno:
| Critério | Tempo |
|---|---|
| 1.000 linhas × 10 colunas | ~20 ms |
| 10.000 linhas × 10 colunas | ~150 ms |
| 100.000 linhas × 10 colunas | ~1,5 s |
| 1.000.000 linhas × 10 colunas | ~15-25 s (pode congelar UI) |
Vai mais fundo
Erros clássicos
- Esquecer BOM para Excel: usuário abre arquivo com acentuação quebrada.
- Delimitador errado em PT-BR: vírgula em vez de ponto-e-vírgula abre tudo em uma coluna no Excel brasileiro.
- Não escapar aspas internas: arquivo fica malformado.
- Ordem de colunas inconsistente: cada export tem ordem diferente — automação quebra.
- Vazar memória de Blob URL: não chamar revokeObjectURL em loop.
- Serializar Date como número:
new Date()em CSV vira número. Use toISOString() ou pedir ao PapaParse para serializar. - Rodar conversão no main thread com 1M linhas: congela a UI por segundos.
- Não validar campos antes de exportar: campo undefined gera célula vazia inexplicada.
Fluxo completo end-to-end
Um exemplo realista: API devolve JSON paginado, frontend consolida e oferece botão para exportar:
"use client";
import { useState } from "react";
import Papa from "papaparse";
async function buscarTodosDados() {
const todosRegistros: Record<string, unknown>[] = [];
let pagina = 1;
let temMais = true;
while (temMais) {
const res = await fetch(`/api/pedidos?page=${pagina}`);
const { data, hasMore } = await res.json();
todosRegistros.push(...data);
temMais = hasMore;
pagina++;
}
return todosRegistros;
}
export function ExportarPedidos() {
const [loading, setLoading] = useState(false);
const handleExport = async () => {
setLoading(true);
try {
const dados = await buscarTodosDados();
const csv = "\uFEFF" + Papa.unparse(dados, {
columns: ["id", "cliente", "valor", "criado_em", "status"],
});
const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `pedidos-${new Date().toISOString().slice(0, 10)}.csv`;
a.click();
URL.revokeObjectURL(url);
} finally {
setLoading(false);
}
};
return (
<button onClick={handleExport} disabled={loading}>
{loading ? "Exportando..." : "Exportar Pedidos em CSV"}
</button>
);
}Conversão em uma frase
JSON para CSV é uma daquelas tarefas enganosamente simples. Com PapaParse, BOM e cuidado em edge cases (vírgulas, aspas, objetos aninhados, encoding), o código resultante é robusto em produção e suporta milhões de linhas sem surpresa. Sem biblioteca, você reimplementa RFC 4180 e vira mantenedor involuntário de casos-limite — caminho quase nunca justificável.
Perguntas frequentes
Qual a biblioteca JavaScript mais usada para converter JSON em CSV?+
PapaParse é a referência absoluta em 2026. Suporta conversão de/para CSV com lidar com edge cases (aspas, vírgulas em campos, escape), tem API clara e roda em browser e Node. Alternativas: json2csv (mais focada em Node), csv-writer (simples), ou conversão manual para projetos mínimos onde peso do bundle importa. Para 90% dos casos, PapaParse.
Como lidar com objetos JSON aninhados ao converter para CSV?+
CSV é bidimensional (linhas × colunas); JSON pode ser hierárquico. Três estratégias: (1) flatten — transforma {a: {b: 1}} em {a.b: 1}; (2) stringify — converte objeto aninhado em string JSON dentro da célula; (3) descarte — ignora campos aninhados. PapaParse não faz flatten automaticamente — use flat ou flatten-js antes, ou escreva função recursiva de flattening.
Preciso adicionar BOM no início do CSV para Excel?+
Sim, se você abre CSV direto no Excel e precisa que acentos apareçam corretos. BOM (Byte Order Mark — \uFEFF) no início do arquivo sinaliza ao Excel que o encoding é UTF-8. Sem ele, caracteres acentuados aparecem corrompidos. Em exportação programática, sempre adicione BOM: const csvWithBom = '\uFEFF' + csv.
Como lidar com vírgulas dentro de campos de texto?+
CSV usa vírgula como separador. Se um campo contém vírgula, ele precisa ser envolvido em aspas duplas: “Campo, com vírgula”. Se contém aspas duplas, essas precisam ser escapadas duplicando-as: “Campo com \"aspas\" internas”. PapaParse faz isso automaticamente. Implementações manuais precisam cobrir os dois casos — falha comum em código caseiro.
É mais rápido converter JSON para CSV no backend ou no frontend?+
Para arquivos pequenos (até 10 MB ou 100k linhas), o browser moderno converte em milissegundos. Vantagens: zero carga no servidor, download instantâneo. Para arquivos grandes (centenas de MB ou milhões de linhas), prefira backend — o browser pode congelar durante conversão sync. Use streaming de escrita de arquivo em Node.js. Em dúvida, teste: sua estimativa de “grande” pode ser menor do que imagina.
Como gerar download automático do CSV no browser?+
Três linhas: criar Blob com conteúdo CSV, criar URL via URL.createObjectURL, criar elemento <a> com download attribute, chamar click(). No código: const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'dados.csv'; a.click(); URL.revokeObjectURL(url). Nunca esqueça revokeObjectURL para não vazar memória.
CSV é o melhor formato para exportar dados tabulares?+
Para interoperabilidade com Excel e Google Sheets, sim — CSV é o padrão universal. Para dados que serão consumidos por outro programa (API, pipeline), JSON ou Parquet são melhores. Para conjuntos grandes com tipos preservados (datas, números com decimais), Excel (.xlsx) via biblioteca SheetJS é superior a CSV puro. Escolha pelo consumidor, não pelo que é mais fácil de gerar.
Como preservar a ordem das colunas no CSV?+
Defina explicitamente as colunas antes da conversão: const fields = ['id', 'nome', 'email']; Papa.unparse({ fields, data: jsonArray }). Sem essa definição, PapaParse usa a ordem de propriedades do primeiro objeto, que é não-determinística em JavaScript para objetos criados dinamicamente. Para exports em produção (relatórios, cobranças), definição explícita é obrigatória.
Artigos relacionados
JSON vs CSV (2026): Quando Usar Cada Formato e Como Converter
Comparativo completo JSON vs CSV: diferenças técnicas, performance, casos de uso, limitações, parquet como alternativa e código de conversão entre os formatos.
Como Exportar Dados JSON para Excel (2026): Guia Completo com SheetJS
Guia definitivo de exportação de JSON para Excel (.xlsx): SheetJS para JS, OpenPyXL para Python, múltiplas planilhas, formatação, fórmulas e estratégias para arquivos grandes.
Como Ler CSV em JavaScript (2026): FileReader, Node.js, Streams e PapaParse
Guia completo de leitura de CSV em JavaScript: FileReader no browser, csv-parse e streams no Node.js, PapaParse para grandes volumes, parser RFC 4180 do zero, BOM, encoding e benchmarks.
Como Importar CSV para Banco de Dados (2026): PostgreSQL, MySQL, SQLite e MongoDB
Guia completo de ingestão de CSV em banco: COPY do Postgres, LOAD DATA do MySQL, SQLite .import, mongoimport, estratégias para arquivos grandes e validação antes do insert.