TL;DR — escolha um método em 10 segundos
- Quer a tabela agora: FormatArc JSON para CSV. Roda no seu navegador, nada é enviado, e objetos aninhados viram colunas em notação de ponto.
- Cada registro contém um array de objetos (pedidos com itens): decida primeiro se uma linha é um pedido ou um item. Vá para Forma por forma.
- Você precisa de uma linha por elemento do array:
mlr --ijson --ocsv cat(Miller) oupandas.json_normalize(data, record_path=...). - Seus IDs têm mais de 15 dígitos: não use um conversor escrito em JavaScript. Veja inteiros acima de 2^53.
- Você vai abrir o arquivo no Excel: leia Abrir o CSV sem quebrá-lo antes de dar dois cliques.
| Método | Instalação | Objetos aninhados | Arrays dentro de um registro | No navegador, sem upload |
|---|---|---|---|---|
| FormatArc | nenhuma | colunas em notação de ponto | mantido como texto JSON em uma célula | sim |
| json-2-csv (npm) | npm i json-2-csv | colunas em notação de ponto | mantido como texto JSON em uma célula | não (Node) |
Miller (mlr) | brew install miller | colunas em notação de ponto | expandido para items.1.sku, items.2.sku… | não (CLI) |
pandas json_normalize | pip install pandas | colunas em notação de ponto | repr do Python em uma célula, ou linhas com record_path | não (Python) |
jq | brew install jq | você escreve o mapeamento | você escreve o mapeamento | não (CLI) |
Tudo na tabela acima é medido, não presumido. As entradas, o script de medição e as saídas brutas estão no repositório, em scripts/benchmarks/json-to-csv-guide/ (medido em 2026-07-26), e todas as saídas abaixo são citadas literalmente dessa execução.
Converter em 30 segundos
Abra JSON para CSV, cole um array de objetos e clique em Executar. Esta entrada:
[
{ "name": "Mika", "email": "mika@example.com", "role": "admin", "address": { "city": "Tokyo" } },
{ "name": "Noah", "email": "noah@example.com", "role": "viewer", "address": { "city": "Osaka" } }
]
produz este CSV:
name,email,role,address.city
Mika,mika@example.com,admin,Tokyo
Noah,noah@example.com,viewer,Osaka


A conversão acontece dentro da própria página: seu JSON nunca é enviado a um servidor. Isso importa quando o que você cola é a resposta de uma API de produção com dados de clientes. Em Conversores online são seguros? explicamos o que essa diferença realmente protege.
Se a entrada for rejeitada, o erro volta com o número da linha, pelo mesmo caminho usado pelo JSON Formatter. Como corrigir erros de parse de JSON cobre as causas mais comuns.
O mesmo JSON, quatro conversores, quatro respostas diferentes
JSON é uma árvore; CSV é um retângulo. Todo conversor inventa regras para achatar um dentro do outro, e essas regras não são iguais entre as ferramentas. Passamos 15 entradas por quatro conversores e registramos a saída exata de cada um.
Ambiente (conforme results.json): Node v26.3.1 no macOS arm64, FormatArc lib/tooling.ts (PapaParse 5.5.2), json-2-csv 5.5.11, Miller 6.19.0, pandas 3.0.5. Os quatro rodam com as opções padrão — e é esse o ponto: é o que você obtém quando não configura nada.
Onde os quatro concordam
Objetos aninhados são achatados em notação de ponto. Com {"name":"Mika","address":{"city":"Tokyo","zip":"150-0001"}}, os quatro produzem:
name,address.city,address.zip
Mika,Tokyo,150-0001
Três níveis se comportam igual (meta.created.by.name). Um objeto solto no nível superior, sem array, vira um CSV de uma linha nos quatro. E valores com vírgula, aspas duplas ou quebra de linha são citados de forma idêntica:
who,quote,note
"Smith, John","She said ""hi""","line1
line2"
Isso corresponde às regras de citação da seção 2 do RFC 4180Abre em uma nova aba: campos com quebras de linha, aspas duplas ou vírgulas são envolvidos em aspas duplas, e uma aspa interna é escapada duplicando-a. Uma diferença útil se você compara arquivos: o FormatArc termina os registros com CRLF (o padrão do PapaParse, e o que o RFC 4180 especifica), enquanto json-2-csv, Miller e pandas usam LF.
Arrays se dividem em três respostas
Entrada:
[{"name":"Mika","tags":["admin","billing"]},{"name":"Noah","tags":["viewer"]}]
| Conversor | Saída |
|---|---|
| FormatArc | name,tags / Mika,"[""admin"",""billing""]" / Noah,"[""viewer""]" |
| json-2-csv | idêntica à do FormatArc |
| Miller | name,tags.1,tags.2 / Mika,admin,billing / Noah,viewer, |
| pandas | name,tags / Mika,"['admin', 'billing']" / Noah,['viewer'] |
São três produtos genuinamente diferentes. FormatArc e json-2-csv guardam o array como texto JSON válido em uma célula, então você pode reprocessá-la depois. O Miller alarga a tabela, o que é conveniente até um registro trazer 40 tags e o número de colunas explodir. O pandas escreve um repr do Python com aspas simples, que não é JSON válido e vai falhar se algo adiante tentar dar JSON.parse nessa célula.
Arrays de objetos se comportam do mesmo jeito: {"order":"A-1","items":[{"sku":"X1","qty":2},{"sku":"X2","qty":1}]} vira uma célula de texto JSON no FormatArc e no json-2-csv, e items.1.sku, items.1.qty, items.2.sku, items.2.qty no Miller.
Chaves ausentes: célula vazia, a palavra "undefined" ou um erro fatal
Respostas de API reais têm campos opcionais. Entrada:
[{"id":1,"name":"Mika"},{"id":2,"name":"Noah","nickname":"No"},{"id":3,"phone":"03-0000-0000"}]
FormatArc e pandas deixam células vazias:
id,name,nickname,phone
1,Mika,,
2,Noah,No,
3,,,03-0000-0000
O json-2-csv escreve a string literal undefined em cada lacuna:
id,name,nickname,phone
1,Mika,undefined,undefined
2,Noah,No,undefined
3,undefined,undefined,03-0000-0000
O Miller recusa o arquivo inteiro: mlr: CSV schema change: first keys "id,name"; current keys "id,phone". É um comportamento defensável para uma ferramenta de streaming, mas significa que um único campo opcional pode parar um pipeline que funcionava ontem.
O FormatArc usa a união de todas as chaves de todos os registros, na ordem em que aparecem, então o número de colunas fica definido antes de a primeira linha ser escrita. Nenhuma linha pode deslocar.
null: uma célula vazia ou as quatro letras n-u-l-l
[{"id":1,"deleted_at":null},{"id":2,"deleted_at":"2026-07-01"}]
FormatArc e pandas escrevem uma célula vazia. json-2-csv e Miller escrevem o texto null. Se você carregar o CSV em um banco, um grupo entrega um NULL de verdade e o outro entrega uma string de quatro caracteres que parece um. É a causa mais comum de "por que meu WHERE não encontra nada" depois de uma conversão.
Um objeto vazio produz quatro tabelas diferentes
Com [{"id":1,"meta":{}},{"id":2,"meta":{"source":"api"}}]:
| Conversor | Saída |
|---|---|
| FormatArc | id,meta,meta.source — o {} vazio mantém uma coluna meta que fica em branco em todas as linhas |
| json-2-csv | mesmo cabeçalho, mas escreve o literal {} e duplica o valor em {"source":"api"} |
| Miller | erro: CSV schema change: first keys "id,meta"; current keys "id,meta.source" |
| pandas | remove a coluna meta inteira: id,meta.source |
O comportamento do FormatArc aqui é honesto, mas não bonito: sobra uma coluna vazia. Se você vir uma, existe um objeto vazio em algum ponto do payload.
Um ponto literal no nome da chave sobrescreve uma coluna em silêncio
Esse caso custa dados, e três dos quatro conversores os perdem. Entrada:
[{"a.b":1,"a":{"b":2}}]
O registro tem dois valores distintos: a chave literalmente chamada a.b, com o valor 1, e o caminho aninhado a.b, com o valor 2. FormatArc, Miller e pandas produzem:
a.b
2
O 1 sumiu. Só o json-2-csv os distingue, escapando a chave literal:
a\.b,a.b
1,2
Se o seu JSON usa nomes de chave com pontos (eventos de analytics como page.view.count, documentos no estilo MongoDB, rótulos no estilo Prometheus), verifique colisões antes de converter ou use o json-2-csv.
Inteiros acima de 2^53 são arredondados pelos conversores JavaScript
[{"id":9007199254740993,"order_no":12345678901234567890}]
| Conversor | Saída |
|---|---|
| FormatArc | 9007199254740992,12345678901234567000 |
| json-2-csv | 9007199254740992,12345678901234567000 |
| Miller | 9007199254740993,12345678901234567890 |
| pandas | 9007199254740993,12345678901234567890 |
Não é um problema do CSV nem um bug das duas ferramentas JavaScript: o JSON.parse transforma todo número em double, e doubles não representam todos os inteiros acima de 2^53. O valor já chega errado ao escritor de CSV. IDs do Snowflake, do X (Twitter), algumas referências de pagamento e chaves de banco de 64 bits caem nessa faixa. Se seus IDs são longos, coloque-os entre aspas como strings no JSON, ou converta com Miller, pandas ou jq, que preservam os dígitos.
Entrada quebrada: um erro explícito ou um arquivo vazio silencioso
| Entrada | FormatArc | json-2-csv | Miller | pandas |
|---|---|---|---|---|
["a","b","c"] | erro: "cada elemento do array deve ser um objeto" | três linhas em branco, sem erro | exceção | exceção |
[] | erro: "o array JSON está vazio" | uma linha em branco, sem erro | saída vazia, sem erro | saída vazia, sem erro |
Saída vazia com código de saída 0 é o pior dos três desfechos, porque um cron vai sobrescrever tranquilamente o arquivo bom de ontem com nada.
O que o conversor do FormatArc faz, exatamente
As regras abaixo são a implementação, não um resumo dela. Vêm de convertJsonToCsv em lib/tooling.ts e batem com as medições acima.
- O nível superior precisa ser um array de objetos ou um objeto único. Um objeto único vira um CSV de uma linha. Um array de primitivos, um array vazio e uma string ou número soltos são rejeitados com uma mensagem.
- Objetos aninhados são achatados recursivamente em colunas de notação de ponto:
address.city,meta.created.by.name. Não há limite de profundidade. - Arrays não são expandidos. O array é serializado com
JSON.stringifye guardado como texto JSON em uma célula, então["admin","billing"]continua parseável. - As colunas são a união de todas as chaves de todos os registros, na ordem em que aparecem. Uma chave que só existe no último registro também ganha coluna, e todas as linhas anteriores recebem célula vazia nela. Linhas não podem deslocar.
nulleundefinedviram células vazias. Um objeto vazio{}também vira célula vazia e mantém a própria coluna.- A citação segue o
unparsedo PapaParse: campos com vírgulas, aspas ou quebras de linha são citados, aspas internas são duplicadas e os registros terminam em CRLF. - JSON inválido é reportado com número de linha, pelo mesmo caminho de erro do JSON Formatter.
Nada sai da aba. Não existe etapa de upload nem arquivo temporário em um servidor: a conversão é uma chamada de função dentro da página que você já carregou.
Forma por forma: o que fazer com cada tipo de JSON
Um array plano de objetos
Não há nada a decidir. Cole e execute.
Registros com objetos aninhados
Notação de ponto é a resposta padrão em toda parte, e é reversível na mão: address.city diz exatamente de onde o valor veio. Verifique duas coisas antes: nomes de chave que já contêm ponto (veja acima) e se quem consome o CSV aguenta pontos nos cabeçalhos. Alguns carregadores SQL exigem o cabeçalho entre aspas; o Google Sheets aceita sem problema.
Registros com um array de valores simples
Escolha uma opção:
- Manter como texto JSON em uma célula (padrão do FormatArc). Melhor quando o CSV é um arquivo intermediário que outro script vai reler.
- Expandir para
tags.1,tags.2com o Miller. Melhor quando o comprimento máximo é pequeno e fixo (um par de coordenadas, um trio RGB). - Juntar com um separador antes de converter, quando uma pessoa for ler a coluna:
jq '.[] |= (.tags |= join(";"))' data.jsone então converter. Use;em vez de,para a célula não precisar de aspas.
Registros com um array de objetos (o caso difícil)
Decida o que uma linha do CSV representa antes de tocar em qualquer conversor.
- Uma linha por pai (um pedido por linha): deixe o array como texto JSON em uma célula. É o padrão do FormatArc. Os itens ficam intactos e um script pode processar a célula depois.
- Uma linha por filho (um item por linha): isso é outra tabela, não outro formato. Use
pandas.json_normalize(data, record_path="items", meta=["order"]), que repete os campos do pai em cada linha filha, ou reestruture antes comjq. - Dois arquivos: converta os campos do pai para
orders.csve extraia os filhos comjq '[.[] | .order as $o | .items[] | {order:$o} + .]' data.jsonpara converter emitems.csv. É a resposta normalizada, e a escolha certa se o CSV vai para um banco de dados.
O que evitar com arrays de tamanho variável é alargar a linha do pai em items.1.sku, items.2.sku, … (o padrão do Miller): a contagem de colunas é decidida pelo registro que por acaso tiver mais filhos, então o esquema muda toda vez que os dados mudam.
Registros com chaves inconsistentes
FormatArc e pandas resolvem isso sem configuração. O Miller precisa de unsparsify, que preenche as lacunas em vez de abortar:
mlr --ijson --ocsv unsparsify data.json
Ou unifique os registros antes com jq '[.[] | {id, name, nickname, phone}]', que força cada registro a carregar todas as chaves.
Abrir o CSV no Excel ou no Sheets sem quebrá-lo
O que corrompe seus dados quase nunca é o conversor: é a planilha.
- Codificação. CSV não tem campo de codificação; o RFC 4180Abre em uma nova aba apenas registra que conjuntos de caracteres diferentes de US-ASCII são usados via o parâmetro MIME
charset, que um arquivo local não carrega. O FormatArc baixa UTF-8 sem marca de ordem de byte (BOM). O Excel no Windows pode então ler texto não ASCII como Windows-1252, então importe por Dados, De Texto/CSV escolhendo UTF-8, em vez de dar dois cliques. O Google Sheets detecta UTF-8 corretamente. - Números com mais de 15 dígitos. A própria especificação do Excel lista a precisão numérica como 15 dígitosAbre em uma nova aba; tudo além disso é substituído por zeros. Somado ao arredondamento de 2^53 acima, um ID longo pode ser danificado duas vezes. Importe essas colunas como Texto.
- Zeros à esquerda.
007e03-0000-0000viram7e uma data, a menos que a coluna seja importada como Texto. - Strings parecidas com data. Valores como
2026-07-01e, pior, números de versão como1-2e nomes de genes são convertidos automaticamente. Importe como Texto ou corrija o tipo da coluna na caixa de diálogo de importação. - Células começando com
=,+,-ou@. Nenhum conversor da nossa matriz as escapa: os quatro escreveram=1+1sem alteração. Quem decide se aquilo é avaliado é a planilha, e é nisso que se baseia a injeção de CSVAbre em uma nova aba. Se o CSV contém texto enviado por usuários e outra pessoa vai abri-lo, prefixe essas células com uma aspa simples ou importe a coluna como Texto.
Quando o navegador não é o lugar certo
Converter no navegador é o caminho mais rápido para uma resposta de API que você acabou de colar do curl. É a ferramenta errada para uma exportação de 2 GB ou para um passo dentro de um job noturno.
# Miller: achata objetos, expande arrays em colunas indexadas, processa em streaming
mlr --ijson --ocsv cat data.json > out.csv
# jq: você escreve o mapeamento explicitamente, então nada é adivinhado
jq -r '(.[0] | keys_unsorted), (.[] | [.[]]) | @csv' data.json > out.csv
# pandas: achatamento em notação de ponto e uma linha por elemento do array via record_path
python3 -c "import pandas,json; print(pandas.json_normalize(json.load(open('data.json'))).to_csv(index=False))" > out.csv
Os três não são intercambiáveis. O json_normalize expande dicionários em colunas com pontos, mas precisa de record_path para produzir uma linha por elemento de array. O @csv do jq exige que os dados já estejam moldados em arrays de valores simples e falha diante de um objeto aninhado em vez de achatá-lo. O Miller achata objetos e arrays por padrão com índices começando em 1, e para com erro de esquema em registros heterogêneos, como medimos acima. Os detalhes estão na documentação de flatten do MillerAbre em uma nova aba, no manual do jqAbre em uma nova aba e na referência do pandas.json_normalizeAbre em uma nova aba.
Para o sentido inverso e suas próprias armadilhas (inferência de tipos, codificações, saída NDJSON), veja Converter CSV para JSON.
Voltar atrás não é uma conversão sem perdas
Uma ida e volta não devolve o documento original. Passe o CSV do primeiro exemplo pelo CSV para JSON:
[
{
"name": "Mika",
"email": "mika@example.com",
"role": "admin",
"address.city": "Tokyo"
}
]
address.city volta como uma chave plana com um ponto no nome, não como um objeto address aninhado. Nada re-aninha a notação de ponto automaticamente, porque address.city é um nome de chave perfeitamente legal por si só e o conversor não tem como saber qual dos dois você queria: exatamente a colisão medida no caso C13.
Trate a conversão de JSON para CSV como uma exportação de mão única para planilhas e análise, e mantenha o JSON como fonte da verdade. Se precisar recuperar a forma aninhada, faça isso deliberadamente, por exemplo com jq 'map(reduce (to_entries[]) as {$key,$value} ({}; setpath($key | split("."); $value)))', e confira o resultado.
Perguntas frequentes
Por que meu CSV tem uma única coluna com JSON dentro?
Porque o nível superior era um objeto único com um array grande dentro, tipo {"data":[...]}. Extraia o array primeiro (cole apenas o valor de data, ou rode jq '.data' response.json) e converta depois. O FormatArc achata o invólucro em colunas como data em vez de tratar o array como as linhas.
Consigo uma linha por elemento de um array aninhado?
Com o conversor do navegador não, e isso é deliberado: a contagem de linhas dependeria dos dados e os campos do pai seriam duplicados silenciosamente. Use pandas.json_normalize(data, record_path="items", meta=[...]) ou reestruture antes com jq, como descrito acima.
Por que uma coluna contém [{"sku":"X1"...}] em vez de valores?
É um array de objetos preservado como texto JSON em uma única célula. É intencional e reversível. As três formas de mudar isso estão em o caso difícil.
O conversor altera meus números?
Só do jeito que o próprio JavaScript altera: inteiros acima de 2^53 são arredondados durante o parse, como medimos no caso C14. Strings não são tocadas. Se seus IDs têm mais de 15 dígitos, coloque-os entre aspas no JSON ou use uma ferramenta que não seja JavaScript.
Meus dados são enviados para algum lugar?
Não. A conversão roda na página, então o JSON nunca sai da aba: não existe requisição que o carregue. Conversores online são seguros? explica por que essa distinção importa e como verificar você mesmo no painel de rede.
E se meu JSON for inválido?
Você recebe uma mensagem com número de linha em vez de um CSV quebrado. Como corrigir erros de parse de JSON cobre vírgulas finais, aspas simples e os demais suspeitos de sempre, e dicas de formatação de JSON mostra como mantê-lo legível depois.
Qual formato devo manter como original?
O que carrega a estrutura. CSV é um retângulo sem tipos: aninhamento, arrays e a diferença entre null e "" se perdem. Mantenha o JSON e trate o CSV como uma visão dele. O que é CSV? detalha os limites do formato.