De CSV para JSON: os cinco casos que partem qualquer conversor
Publicado a 17/07/2026 · 15 min de leitura · Ferramentas para programadores
Daniel Okonkwo — Programador front-end e redator de Tecnologia na OneKitly
Desempenho web · Formatos de ficheiro
Verificado a partir de 4 fontes
O CSV não tem norma, só o RFC 4180 — um RFC informativo que descreve o que a maioria dos programas já fazia em 2005, não uma regra obrigatória. Cinco casos separam um analisador que funciona de um que corrompe em silêncio. Um: um delimitador dentro de um campo entre aspas. name,city / Tom,"Paris, France" fica em duas colunas, e uma aspa duplicada lá dentro sai como uma só. Dois: uma quebra de linha dentro de um campo entre aspas. O analisador lê até à aspa de fecho e não até ao fim da linha, portanto uma morada em duas linhas sobrevive como um único valor. Três: os tipos. A conversão vem desligada; ligue-a e 1 passa a ser o número 1, ao passo que 0044, 1.0, 1e3, 2026-08-18 e 9007199254740993 continuam strings, porque uma célula só é convertida se o número se reimprimir exatamente como chegou. true passa a booleano mas TRUE não, porque os booleanos JSON são minúsculos, e null passa a null de JSON — o que surpreende no dia em que um apelido é Null. Quatro: cabeçalhos repetidos. name,name,name dá name, name_2 e name_3 em vez de uma só coluna sobrevivente, e um cabeçalho vazio vira column2. Cinco: a codificação. O BOM UTF-8 é retirado, e o navegador absorve os BOM de UTF-8 e UTF-16 antes de a ferramenta ver o texto — mas não há seletor de codificação, pelo que uma exportação Windows-1252 chega como Andr�, e U+FFFD não se desfaz. Dois casos que não salva: um campo entre aspas precedido de um espaço, Tom, "Paris, France", não é tratado como citado, e uma linha de pista do Excel sep=; é consumida como cabeçalho.
Delimitadores entre aspas, quebras de linha embutidas, tipos ambíguos, cabeçalhos repetidos e codificação. Cada caso passou pelo conversor e a saída exata está aqui — incluindo os dois que ele não salva.
O RFC 4180 é uma descrição, não uma regra
Toda a gente cita o RFC 4180 como se fosse a norma do CSV. O seu próprio cabeçalho diz o contrário: é informativo, o que em linguagem IETF significa que não especifica norma alguma da Internet. Foi publicado em 2005 para registar o que os programas já faziam, e di-lo a respeito do seu próprio objeto: a regra 5 nota que alguns programas, o Microsoft Excel incluído, não usam aspas de todo. É essa a raiz de todos os problemas deste artigo. Não há autoridade a que recorrer quando duas ferramentas divergem sobre o mesmo ficheiro, porque nenhuma está a violar seja o que for.
O RFC define de facto duas coisas que ajudariam, e nenhuma sobrevive à passagem por um ficheiro. Define um parâmetro header no tipo de media text/csv, com os valores present e absent, para dizer ao destinatário se a primeira linha são nomes de coluna. E diz que o uso corrente é US-ASCII, com outros conjuntos de carateres transportados pelo parâmetro charset. Ambos são parâmetros MIME: vivem numa resposta HTTP ou num anexo, não dentro dos bytes. Guarde os mesmos dados como vendas.csv e as duas informações desapareceram. É por isso que qualquer leitor de CSV do mundo tem uma caixa «a primeira linha é cabeçalho», e é por isso que a codificação tem de ser adivinhada.
Casos 1 e 2 — o delimitador e a quebra de linha dentro de um campo citado
Estes dois são o mesmo erro com fatos diferentes, e ambos vêm de cortar pelo carácter em bruto em vez de analisar. Um conversor que faz text.split(",") transforma Tom,"Paris, France" em três colunas, e todas as linhas abaixo herdam a coluna a mais. Um conversor que faz primeiro text.split("\n") corta ao meio uma morada de duas linhas e produz uma linha com um só campo. O remédio é o mesmo: percorrer a string carácter a carácter, manter uma bandeira «estou dentro de aspas», e tratar um delimitador ou uma quebra de linha como estruturais só quando a bandeira está em baixo.
Ambos passam. name,address / Tom,"12 rue A\nParis" / Ann,"3 rue B" devolve exatamente dois objetos, o primeiro com uma morada de duas linhas. Uma aspa duplicada é desescapada à entrada, portanto "He said ""hi"" loudly" volta como He said "hi" loudly. Vale a pena conhecer um desvio face ao RFC: a ferramenta normaliza todo o CR LF para LF antes de analisar, pelo que uma quebra de linha que era CR LF dentro de um campo citado sai do conversor como LF simples. Nada se perde, mas se comparar a ida e volta byte a byte, é aí que está a diferença.
O caso que não salva é aquele sobre o qual o RFC é explícito. A secção 2.4 diz que os espaços fazem parte do campo e não devem ser ignorados: em Tom, "Paris, France" o primeiro carácter do campo é um espaço e a aspa que se segue é só um carácter, não um delimitador de abertura. O analisador dá razão ao RFC e produz duas colunas partidas, " Paris e France". A maioria de quem escreve essa linha queria um campo citado. Se o seu exportador põe um espaço depois do delimitador, retire-o antes de converter ou as aspas são decorativas.
Caso 3 — os tipos, e o teste de ida e volta que salva os seus telefones
O CSV não tem tipos. Cada célula é texto, e assim que produz JSON tem de decidir se 1 é a string "1" ou o número 1. Por omissão o conversor não decide nada: a conversão é um interruptor e começa desligada, pelo que uma execução simples dá um array de objetos cujos valores são todos strings. É a omissão certa, porque uma string é sempre recuperável e um número não.
Ligue o interruptor e a regra é um único teste de ida e volta: uma célula torna-se número só se reimprimir esse número devolver exatamente os carateres que chegaram. Ao correr, 1 passa a 1, mas 0044 fica "0044" porque Number("0044") se imprime 44. 1.0 fica "1.0" porque se imprime 1. 1e3 fica "1e3" porque se imprime 1000. .5 e +1 continuam strings pela mesma razão. E 9007199254740993 continua string, porque o double mais próximo em JavaScript se imprime 9007199254740992: um conversor sem esta guarda muda em silêncio o último dígito de um identificador longo, e nada a jusante lho dirá.
Três coisas escapam ao teste de ida e volta, porque passam por comparação literal. true e false passam a booleanos, mas só em minúsculas: TRUE, True e FALSE continuam strings, o que importa porque o Excel escreve os booleanos em maiúsculas e as interfaces francesa e alemã escrevem VRAI e WAHR. null passa ao null de JSON, e aí há uma armadilha a sério: um campo de texto cujo valor são as quatro letras null não é o mesmo que um valor ausente, e depois da conversão já não os distingue. E com as datas nada é feito: 2026-08-18 continua a string "2026-08-18", que é a resposta certa, porque um conversor que analisa datas tem de escolher um fuso horário e vai enganar-se.
Caso 4 — cabeçalhos repetidos, cabeçalhos vazios, linhas irregulares
Um CSV pode repetir um nome de coluna; um objeto JSON não. Com name,name,name sobre a,b,c, a implementação ingénua escreve três vezes a mesma chave e o JSON fica com a última: obtém {"name": "c"} e duas colunas de dados desapareceram sem erro nenhum. Este conversor renomeia: name, name_2, name_3. Também trata o caso seguinte, quando o nome inventado colide com um real — name,name,name_2 dá name, name_2 e name_2_2, porque o renomeador verifica contra tudo o que já foi usado e não só contra os cabeçalhos originais.
Uma célula de cabeçalho vazia recebe um nome posicional: name,,name, sobre a,b,c,d dá name, column2, name_2 e column4. Os nomes de cabeçalho são aparados, portanto " name , age " produz name e age. E a largura da saída é a da linha mais larga do ficheiro, não a do cabeçalho: a,b,c sobre as duas linhas 1,2 e 3,4,5,6 dá quatro chaves a cada objeto, com c vazio na linha curta e um column4 a levar o 6 que o cabeçalho nunca contemplou. Nada é deitado fora, e é a decisão certa para um conversor: truncar em silêncio uma linha longa é destruir exatamente a linha que era preciso ver.
Caso 5 — a codificação, a que a ferramenta não pode reparar
Um ficheiro CSV são bytes. Nada lá dentro diz que tabela transforma esses bytes em carateres, e o RFC 4180 põe essa informação num parâmetro MIME que um ficheiro em disco não leva. Dois mecanismos tapam em parte o buraco. Uma marca de ordem de bytes no início do ficheiro identifica UTF-8, UTF-16 LE e UTF-16 BE, e o leitor de ficheiros do navegador consome-a: largue um ficheiro UTF-16 LE com BOM na ferramenta e o texto chega bem descodificado, com a marca já retirada. A ferramenta volta a retirar um BOM por sua conta, o que apanha o caso em que a marca chega pela área de transferência e não por um ficheiro.
O buraco que fica aberto é o ficheiro sem marca nenhuma, que são a maioria. Guarde uma folha de cálculo como CSV simples numa máquina Windows da Europa ocidental e obtém Windows-1252, um byte por carácter, sem BOM. Este conversor não tem seletor de codificação: o leitor cai para UTF-8, o byte E9 que significava é não é UTF-8 válido, e é substituído por U+FFFD. A ferramenta analisa depois sem problemas e devolve {"name": "Andr�", "city": "K�ln"} sem aviso algum, porque do ponto de vista dela nada falhou. O U+FFFD não guarda registo do byte que substituiu, portanto isto não é reparável a posteriori: reexporte o ficheiro em UTF-8, ou cole o texto em vez de largar o ficheiro, porque o texto da área de transferência já foi descodificado pela aplicação que o detém.
Um último caso de codificação tem o gume afiado: UTF-16 sem marca de ordem de bytes. Não há nada para farejar, portanto o ficheiro é lido como UTF-8, um em cada dois bytes é um zero, e o que volta é um único objeto cuja chave contém carateres NUL. Parece lixo e não texto ligeiramente errado, e esse é o bom desfecho: vai reparar de imediato. As falhas perigosas são as silenciosas, e o Windows-1252 lido como UTF-8 é a mais silenciosa de todas, porque as colunas alinham na perfeição e só as letras acentuadas estão erradas.
O sexto caso que ninguém enumera: a linha sep=
O Excel aceita uma primeira linha da forma sep=; como instrução sobre que carácter separa os campos, e muitas rotinas de exportação emitem-na para que um ficheiro com ponto e vírgula abra bem num leitor cujo separador de listas é a vírgula. Não está no RFC 4180 e nunca esteve: é uma convenção de fabricante que se espalhou porque funciona. Para um conversor que nunca ouviu falar dela, é simplesmente o primeiro registo do ficheiro.
É exatamente o que acontece aqui. Dê ao conversor sep=; seguido de Name;Ville;Montant e duas linhas de dados: a deteção automática escolhe corretamente o ponto e vírgula — porque a linha sep= contém um — mas o passo do cabeçalho consome-a. Obtém três objetos em vez de dois, com as chaves "sep=", column2 e column3, e os nomes reais de coluna Name, Ville e Montant aparecem como valores do primeiro. É evidente assim que olha para a saída, e invisível se a encadear diretamente noutra coisa. Apague a primeira linha antes de converter, ou converta primeiro o delimitador e deixe o conversor de delimitador reescrever a pista por si.
| Entrada | O que sai | Porquê |
|---|---|---|
| Tom,"Paris, France" | Dois campos: Tom e Paris, France | O analisador segue um estado «entre aspas»; um delimitador citado é dado |
| Tom, "Paris, France" (espaço depois da vírgula) | Três campos: Tom, " Paris e France" | RFC 4180 secção 2.4: o espaço faz parte do campo, logo a aspa não abre nada |
| 0044 com a conversão de tipos ligada | A string "0044" | Number("0044") imprime-se 44, que não é o que chegou, portanto a célula fica como está |
| TRUE com a conversão de tipos ligada | A string "TRUE"; só true em minúsculas passa a booleano | O teste é uma comparação literal com as duas palavras-chave JSON, que são minúsculas |
| name,name,name sobre a,b,c | Chaves name, name_2 e name_3 — os três valores mantidos | Uma chave repetida num objeto destrói dados: a segunda e a terceira são renomeadas |
| Um ficheiro Windows-1252 largado na ferramenta | Andr� e K�ln, analisados sem problema e sem aviso | Não há seletor de codificação: o leitor assume UTF-8 e substitui cada byte inválido |
| Um ficheiro que começa por sep=; | O ponto e vírgula é bem detetado, mas sep= passa a primeira chave e o cabeçalho real passa a linha de dados | A pista é uma convenção do Excel, alheia a qualquer definição de CSV: o analisador lê-a como um registo |
Perguntas frequentes
- Ligo a conversão de tipos ou deixo-a desligada?
- Deixe-a desligada a não ser que algo a jusante precise mesmo de números. Uma string é uma representação sem perda do que estava na célula; um número é uma representação com perda, e a perda é irreversível. A guarda de ida e volta faz com que esta ferramenta concreta não estrague 0044, 1.0 nem um identificador de 19 dígitos, mas vai converter uma coluna de códigos postais sem zero inicial: 75001 e 75008 passam a números enquanto um código neerlandês como 1012 AB continua string — uma coluna, dois tipos, e quem consumir o JSON tem de tratar os dois. Se precisar de números, converta depois as colunas que lhe interessam, onde as pode nomear, em vez de deixar uma heurística decidir coluna a coluna.
- Como sabe o conversor que o meu ficheiro usa ponto e vírgula?
- Conta os delimitadores candidatos — vírgula, ponto e vírgula, tabulação e barra vertical — apenas no primeiro registo, saltando o que estiver entre aspas, e fica com o mais frequente. Ler só o primeiro registo é deliberado: um cabeçalho como "Nom;Prénom" não deve ser pontuado por uma vírgula enterrada numa morada citada trezentas linhas abaixo. A limitação é o espelho disso. Se o seu cabeçalho contiver uma vírgula e as linhas de dados usarem ponto e vírgula, o detetor escolhe a vírgula e cada linha passa a ser um único campo. Dois sintomas denunciam-no de imediato: uma só chave por objeto, e uma chave cujo nome é a linha de cabeçalho inteira. Na dúvida, fixe o delimitador explicitamente em vez de confiar na deteção.
- Porque é que os meus carateres acentuados viraram pontos de interrogação ou losangos pretos?
- Porque o ficheiro não era UTF-8 e nada o disse ao leitor. O losango preto com interrogação é U+FFFD, o carácter de substituição do Unicode, e é o que um descodificador emite quando uma sequência de bytes não é válida na codificação que lhe mandaram assumir. O seu ficheiro era quase de certeza Windows-1252 ou ISO 8859-1, onde é é o byte único E9; o UTF-8 precisa de dois bytes para é, e E9 sozinho não é o início legal de nada. O estrago acontece antes de o analisador CSV correr, portanto nenhuma definição de CSV o desfará. Abra o original num editor que lhe deixe escolher a codificação, guarde como UTF-8 e converta outra vez. Se o ficheiro veio de uma folha de cálculo, exporte-o com a opção UTF-8 em vez de CSV simples.
- As minhas linhas não têm todas o mesmo número de campos. Vou perder dados?
- Não. A largura da saída é a da linha mais larga do ficheiro, incluindo linhas mais largas do que o cabeçalho. Uma linha curta recebe strings vazias para as colunas em falta; uma linha longa recebe chaves extra chamadas column4, column5 e assim por diante para os campos que o cabeçalho nunca nomeou. Nada é truncado, e isso importa: uma linha demasiado longa costuma ser o sintoma de um delimitador sem escape algures acima, e truncar apagaria a prova. Se vir column4 no seu JSON e o cabeçalho só tinha três nomes, procure um campo com um delimitador sem aspas — é aí que o ficheiro se estragou.
- Existe uma versão de CSV sem estes problemas?
- Dentro do próprio CSV não, porque o formato não tem onde pôr os metadados que resolveriam as questões. O que existe são convenções postas por cima: um ficheiro de esquema que acompanha e nomeia as colunas e os seus tipos, um perfil de exportação fixo acordado entre os dois sistemas, ou um formato que carrega os seus próprios tipos. Se controla as duas pontas, o JSON Lines — um objeto JSON por linha — resolve de uma vez as aspas, as quebras de linha e os tipos, ao preço de um ficheiro maior que não abre numa folha de cálculo. Se não controla as duas pontas, a resposta prática é ser aborrecido: UTF-8 com BOM, vírgula ou ponto e vírgula de forma constante, todos os campos citados, sem linha sep=, e um cabeçalho com nomes únicos e sem o delimitador.
Artigos que podem interessar-lhe
Todos os guias →Ferramentas relacionadas
Isto descreve o que estes conversores fazem hoje, verificado executando-os, e não o que uma norma obrigue um conversor a fazer. O CSV não tem norma prescritiva: o RFC 4180 é informativo e descreve uma prática corrente, pelo que duas ferramentas aparentemente corretas podem divergir sobre o mesmo ficheiro sem que nenhuma esteja errada. O achatamento, a deteção de tipos e a de arrays são convenções, não regras. Antes de converter dados que não possa reexportar, passe primeiro por uma cópia e compare o número de linhas e colunas nas duas pontas.
Fontes
- IETF — RFC 4180, Common Format and MIME Type for Comma-Separated Values (CSV) Files, October 2005 — Informational, not a standard: section 2 rules 5 to 7 on double quotes, section 2.4 on spaces being part of a field, and the header and charset parameters of the text/csv media type
- IETF — RFC 8259, The JavaScript Object Notation (JSON) Data Interchange Format, December 2017 — section 4 on object member names being unordered and the consequences when names repeat, and section 6 on the interoperability limits of numbers
- WHATWG — Encoding Standard — the decoder algorithms, the BOM sniffing rules, and the use of U+FFFD as the replacement for a byte sequence that is not valid in the chosen encoding
- W3C — File API — the read operation and its encoding determination: an explicit encoding, then the blob's charset parameter, then a byte order mark, then UTF-8 as the fallback
Detetaste um erro neste artigo?