Entendendo caracteres em JSON na prática
Muita gente trava nos caracteres quando vai salvar ou trafegar dados em JSON. O formato em si é simples, mas os detalhes de codificação e escape geram dor de cabeça real, especialmente quando você não espera. O problema mais comum não é o parser em si. É a combinação de três coisas: encoding errado no arquivo, caracteres especiais mal escapados e normalização inconsistente entre fontes diferentes de dados. Você pode ter um JSON perfeitamente válido em Python que quebra no JavaScript do navegador, só porque o encoding do servidor não foi definido corretamente no cabeçalho HTTP.
O que são jason characters e por que causam problemas
JSON suporta qualquer caractere Unicode dentro de strings, mas obriga você a escapar certos bytes. Barras invertidas, aspas, quebras de linha, tabulações e os caracteres de controle (0x00 a 0x1F) precisam ser transformados em sequências de escape válidas. A maioria dos frameworks faz isso automaticamente, mas quando você constrói strings manualmente — o que acontece frequentemente em scripts de automação — a coisa despenca rápido. Um caso que me lembro é quando precisei processar um feed XML convertido para JSON via um ETL legado. Os dados vinham com caracteres \u0000 embutidos (NUL) vindos de campos binários que não tinham sido limpos. O JSON gerado tecnicamente não era válido porque strings JSON não permitem NUL. Passei duas horas tentando fazer um parser JavaScript rejeitar gracefully, até descobrir que o problema não estava no parser, mas no pipeline de transformação. A solução foi adicionar um filtro regex no step intermediário: replace de qualquer caractere \x00-\x1F que não fosse um escape reconhecido (\n, \t, \r, \f, \b). Isso reduziu o tempo de debug de horas para minutos e eliminou erros de parsing silenciosos que apareciam só em produção.
Escape sequences: o que funciona e o que quebra
As sequências de escape válidas em JSON são fixas. São nove: \uXXXX para Unicode hex, \" para aspas, \\ para barra invertida, \/, \b para backspace, \f para form feed, \n para nova linha, \r para retorno de carro e \t para tabulação. Qualquer outra combinação de barra invertida mais letra é inválida e deve resultar em erro de parsing. O erro clássico é confundir escape de linguagem de programação com escape de JSON. Em Python, '\n' dentro de uma string é uma nova linha. Quando você serializa com json.dumps(), ele vira \n no output JSON. Se você passar uma string que já contém os dois caracteres literais \ e n, o resultado no JSON será \\n, representando uma barra invertida seguida de n, não uma quebra de linha. Isso confunde muita gente na hora de ler dados de arquivos de configuração ou logs.
Outro ponto que ninguém explica direito: o caractere \u0022 é exatamente o mesmo que \". Ambos são válidos em JSON. A escolha entre usar a forma curta ou a forma unicode depende do contexto. Se você está gerando JSON para humanos lerem, use aspas simples escapadas. Se está gerando para sistemas que precisam evitar ambiguidade com outros delimitadores, a forma \uXXXX é mais segura. Muitos serializers modernos oferecem um parâmetro ensure_ascii para decidir se converte todos os não-ASCII para \uXXXX ou deixa os caracteres originais como estão.
Encoding e a armadilha do UTF-8
JSON é definido pela especificação ECMAScript como codificado em UTF-16, mas na prática todo mundo usa UTF-8. Isso raramente causa problema, exceto quando você lê um arquivo sem BOM (byte order mark) e o sistema operacional assume codificação errada. No Windows, por exemplo, arquivos podem ser abertos como CP-1252 ou ISO-8859-1 por padrão em alguns runtimes, o que corrompe caracteres acentuados e emojis imediatamente. A recomendação é simples e quase sempre ignorada: especifique encoding UTF-8 explicitamente em todas as operações de I/O que tocam JSON. No Python, é open(..., encoding='utf-8'). No Node.js, é menos óbvio porque o buffer é binário por padrão, então você precisa declarar utf-8 na leitura de stream. No HTTP, o cabeçalho Content-Type deve incluir charset=utf-8.
Também existe a pegadinha do BOM UTF-8. Alguns editores e ferramentas de geração inserem os bytes EF BB BF no início do arquivo. O parser JSON pode aceitar ou rejeitar dependendo da implementação. O RFC 8259 diz que um BOM não é parte da sintaxe JSON. Na prática, parsers modernos como o do Python ignoram silenciosamente, mas alguns parsers JavaScript mais antigos ou validadores rigorosos dão erro. Se você estiver consumindo arquivos gerados por ferramentas externas, faça um strip do BOM antes de parsear.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Normalização de Unicode: o inimigo invisível
Dois JSONs podem parecer idênticos mas não serem iguals byte a byte se os strings usam formas canônicas diferentes de Unicode. O caractere é, por exemplo, pode ser representado como U+00E9 (forma decomposta ou composta). Da mesma forma, muitos caracteres acentuados têm múltiplas representações válidas. O problema aparece quando você compara strings ou gera hashes: o usuário digita "São Paulo" em um teclado francês e você recebe uma sequência de bytes diferente do que está no seu banco, mesmo que a aparência seja idêntica. A solução é normalizar antes de comparar ou validar. No Python, unicodedata.normalize('NFC', texto) resolve na maioria dos casos. Em JavaScript, não há função nativa, então se você precisa lidar com isso no frontend, use uma biblioteca ou processe os dados no backend antes de enviar. Para dados que vêm de formulários ou APIs externas, a normalização deve ser feita no ponto de entrada, não depois de já estar espalhado pelo sistema.
Um detalhe que passa despercebido: emojis combinados. Um emoji como é na verdade três emojis base separados por zero-width joiners (U+200D). Alguns parsers e engines tratam isso como um único caractere visual, mas a representação JSON terá vários code points. Se você está contando comprimento de strings para limitar input do usuário, contar code points não vai te dar o tamanho visual correto. Isso é especialmente problemático em sistemas que usam length em JavaScript, que conta elementos da sequência UTF-16, não grapheme clusters.
Geração prática de JSON com caracteres especiais
Quando você está construindo JSON manualmente, a regra número um é: não construa JSON manualmente. Use um serializer da sua linguagem. O risco de escapar errado é alto demais e os erros são silenciosos — o JSON pode ser legalmente válido mas representar dados errados. No entanto, existem cenários onde você não tem escolha. Talvez esteja injetando um JSON dentro de um template HTML dentro de outro JSON. Ou talvez esteja lidando com um sistema legado que aceita JSON como string literal em queries SQL. Nesses casos, o escape manual é necessário e você precisa conhecer as regras de forma exausta.
O processo é: primeiro converta o dado para representação Unicode de forma consistente, depois escape apenas os caracteres obrigatórios (aspas, barra invertida, caracteres de controle), e por fim envolva entre aspas duplas. Nada mais. Se você está usando Python, json.dumps() com ensure_ascii=False já faz tudo isso. Se está usando JavaScript, JSON.stringify() também. Se está usando uma linguagem sem biblioteca padrão robusta, considere escrever uma função minimalista baseada em uma máquina de estado simples.
Limitações reais do JSON para caracteres
JSON não suporta comentários. Não suporta chaves duplicadas (o comportamento é undefined, e a maioria dos parsers usa a última ocorrência). Não tem suporte nativo a tipos como date, binary ou regex — tudo vira string. E, talvez o mais importante, JSON puro não distingue entre número e string numericamente. O valor 1 e "1" são coisas completamente diferentes semanticamente, mas ambos são tokens válidos no grammar. Quem consome o JSON precisa saber o contexto, senão você acaba com lógica de negócio quebrada por um tipagem fraca. Outra limitação prática: tamanho máximo de string. A especificação não impõe limite, mas parsers práticos têm. V8, por exemplo, limita strings a cerca de 256MB. Em JSON, isso significa que um único campo string não pode exceder isso. Na prática, você raramente chega perto, mas se estiver trafegando arquivos binários dentro de JSON (base64), o tamanho explode rapidamente. Para payloads grandes, considere usar multipart ou transferência chunked ao invés de embutir tudo em JSON.
Se o seu caso envolve muitos caracteres especiais, internacionalização pesada ou necessidade de semântica rica, formats como MessagePack, CBOR ou mesmo Protocol Buffers podem ser alternativas mais adequadas. Eles lidam melhor com binários, têm schemas definidos e não sofrem dos mesmos problemas de encoding que JSON.
Resumo rápido dos pontos críticos
Sempre use serializers nativos da linguagem. Declare encoding UTF-8 em todas as camadas de I/O. Normalize Unicode antes de comparar ou validar strings. Faça strip de BOM em arquivos de fontes externas. Cuidado com emojis combinados e contagem de caracteres em JavaScript. Evite construir JSON manualmente. Para payloads grandes ou com dados binários, avalie formatos alternativos como MessagePack ou CBOR.