A gramática é generosa; os parsers não são

Na gramática JSON, um número é apenas uma sequência de dígitos com uma fração e um expoente opcionais. A especificação não coloca limite em quantos dígitos ele pode ter, então 12345678901234567890 e um decimal com cinquenta casas após o ponto são ambos JSON perfeitamente válido. A armadilha é que a generosidade da gramática não é acompanhada pelos parsers. A RFC 8259 avisa explicitamente que os softwares diferem em como lidam com números, e na prática o comportamento mais comum, herdado do JavaScript, é converter todo número para um float de precisão dupla IEEE 754.

O que um double pode e não pode armazenar

Um float de precisão dupla tem 52 bits de fração, o que lhe dá cerca de quinze a dezesseis dígitos decimais significativos e inteiros exatos apenas até 2 elevado a 53, aproximadamente nove quatrilhões. Além disso, inteiros começam a arredondar: o parser não consegue distinguir certos valores grandes, então dois números diferentes na origem podem se tornar o mesmo número após a análise. Decimais não se saem melhor, porque a maioria das frações decimais finitas, como 0,1, não tem representação binária exata e é armazenada como o valor disponível mais próximo. O número que você lê de volta é próximo, mas nem sempre é o número que foi escrito.

Onde isso realmente morde

Esta não é uma preocupação teórica. Identificadores de banco de dados e IDs do tipo snowflake do Twitter rotineiramente excedem 2 elevado a 53, então um ID de 19 dígitos passado por uma biblioteca JSON descuidada pode voltar alterado em uma ou duas unidades no final, apontando para o registro errado. Valores monetários armazenados como decimais podem derivar por uma fração de centavo, o que é inaceitável em dados financeiros. As defesas padrão são carregar tais valores como strings, ou usar um parser que os preserve, justamente porque a conversão padrão para float é com perda.

Preservando o literal

A abordagem segura para uma ferramenta que reformata JSON é nunca converter um número para float. O formatador mantém cada número exatamente como o seu texto literal original e o emite sem alteração, então reformatar um documento com um ID de 20 dígitos ou um decimal de alta precisão retorna os mesmos dígitos com que você começou. Formatar e minificar mudam o espaçamento ao redor de um número, nunca o número em si. Quando você precisa saber se um valor está em risco, a regra prática é simples: qualquer inteiro além de cerca de dezesseis dígitos, ou qualquer decimal onde o valor exato importa, deve ser tratado como algo que um parser ingênuo vai arredondar.