Reglas de Formato de Números JSON: Gramática, Un Solo Tipo y Precisión (RFC 8259)
Dev Tools

Reglas de Formato de Números JSON: Gramática, Un Solo Tipo y Precisión (RFC 8259)

Introducción: Un Número No Es Solo "Cualquier Dígito que Escribas"

Los números parecen la parte más fácil de JSON. Escribes 42 o 3.14 y sigues adelante. Sin embargo, los números son donde JSON esconde algunos de sus filos más afilados. La gramática de lo que cuenta como número válido es más estricta de lo que la mayoría de desarrolladores espera, JSON se niega deliberadamente a distinguir enteros de valores en coma flotante, y en cuanto tus datos abandonan el texto y entran en un programa en ejecución, reglas de aritmética binaria de décadas de antigüedad empiezan a redondear tus valores de formas que pueden corromper en silencio dinero, identificadores y mediciones.

Esta guía es una referencia precisa y basada en ejemplos sobre cómo deben escribirse los números según la RFC 8259 — el estándar de Internet vigente para el formato de intercambio de datos JSON — y sobre qué les ocurre a esos números después de que un analizador los lee. Recorreremos la gramática exacta de los números (el menos opcional, la parte entera sin ceros a la izquierda, la fracción opcional, el exponente opcional), explicaremos por qué JSON tiene un único tipo numérico sin división int/float, mostraremos por qué NaN e Infinity están prohibidos, y luego afrontaremos los riesgos de precisión del mundo real: el redondeo IEEE 754, los enteros grandes que pierden precisión más allá de 253, y por qué los sistemas serios representan el dinero como cadenas o decimales. Cada regla va acompañada de ejemplos válidos e inválidos. Mientras lees, pega fragmentos en nuestro formateador y validador JSON para ver exactamente dónde un número mal formado deja de ser JSON válido.

Qué Dice la RFC 8259 Sobre los Números

La RFC 8259, "The JavaScript Object Notation (JSON) Data Interchange Format", se publicó en diciembre de 2017 y está designada como STD 90, un estándar de Internet completo. Define los cuatro tipos primitivos de JSON — cadenas, números, booleanos y null — y dos tipos estructurados, objetos y arrays. Su tratamiento de los números es breve pero deliberado: un número se escribe casi como en la mayoría de lenguajes de programación, en base diez, con un signo menos opcional, una parte fraccionaria opcional y un exponente opcional. De forma crucial, la especificación define solo la forma textual de un número. No impone cómo debe almacenar el software ni calcular con el valor una vez analizado, y reconoce abiertamente que distintas implementaciones fijan límites diferentes de rango y precisión. Esa brecha entre "lo que el texto permite" y "lo que tu lenguaje almacena" es el origen de casi toda sorpresa numérica en JSON.

La RFC también señala que, como las implementaciones genéricas suelen usar coma flotante de doble precisión IEEE 754, la interoperabilidad es mejor cuando los números permanecen dentro del rango y la precisión que ese formato puede representar de forma exacta. Es una recomendación, no una regla de sintaxis — la gramática acepta encantada un número de cincuenta dígitos — pero es la razón por la que existen los consejos prácticos del resto de esta guía.

La Gramática de los Números, Pieza a Pieza

La RFC 8259 define un número como un signo menos opcional, seguido de una parte entera, seguida de una fracción opcional, seguida de un exponente opcional. En orden, los cuatro componentes son:

  • Menos opcional — puede aparecer un único - a la izquierda. No hay + a la izquierda del número en su conjunto; +5 es inválido.
  • Parte entera (obligatoria) — o bien un único 0, o bien un dígito de 1 a 9 seguido de cualquier número de dígitos adicionales. Esta es la regla que prohíbe los ceros a la izquierda: 0 es válido y 0.5 es válido, pero 01, 007 y 00 son todos inválidos.
  • Fracción opcional — un punto decimal . seguido de uno o más dígitos. Los dígitos son obligatorios en cuanto aparece el punto, así que 1. es inválido, y no existe la forma de decimal desnudo, así que .5 es inválido porque falta la parte entera.
  • Exponente opcional — la letra e o E, un signo + o - opcional y uno o más dígitos. Así que 1e10, 2.5E+4 y 6.022e-23 son todos válidos, mientras que 1e (sin dígitos de exponente) y 1e+ son inválidos.

Juntándolo todo, aquí tienes números válidos que ejercitan cada componente:

0 · -0 · 42 · -17 · 3.14 · -0.5 · 1e10 · 1E10 · 2.5e-3 · 6.022e23 · 0.0

Ten en cuenta que -0 es un token legal: la gramática permite un menos delante de un cero. La mayoría de analizadores lo leen como un cero ordinario, aunque algunos lenguajes preservan un cero negativo distinto. Aquí un catálogo de los números inválidos más comunes, cada uno con una razón de una línea:

  • +5 — no se permite un más a la izquierda del número.
  • 01 — cero a la izquierda; la parte entera no puede empezar por 0 a menos que sea cero.
  • 1. — un punto decimal debe ir seguido de al menos un dígito.
  • .5 — la parte entera es obligatoria; no existe la forma de decimal desnudo.
  • 1e — el exponente necesita al menos un dígito.
  • 0x1F — el hexadecimal no forma parte de JSON; solo base diez.
  • 1_000 y 1,000 — no se permiten separadores de dígitos ni de miles.
  • 0b101 — los literales binarios no son números JSON.
  • 5. y --3 — punto final y signo duplicado están ambos mal formados.

Estos son los mismos filos que hacen tropezar a quien copia literales numéricos desde un lenguaje de programación, donde los guiones bajos, el hexadecimal y los puntos finales suelen ser legales. En JSON no lo son.

JSON Tiene Exactamente Un Solo Tipo Numérico

Este es el hecho más determinante sobre los números JSON, y es invisible en la sintaxis. JSON no distingue un entero de un valor en coma flotante. Hay un único tipo llamado "número", y la gramática se limita a describir cómo escribirlo. Los tokens 10, 10.0 y 1e1 denotan todos el mismo valor numérico; JSON no les asigna ninguna identidad separada de "entero" frente a "flotante".

Lo que esto significa en la práctica depende enteramente del lenguaje que lee el JSON, porque la correspondencia entre el único tipo de JSON y los varios tipos numéricos de un lenguaje es una decisión que toma cada analizador. JavaScript lee cada número JSON hacia un único doble IEEE 754, así que 10 y 10.0 se convierten ambos en el número 10 e imprimen de forma idéntica. La biblioteca estándar de Python, en cambio, lee 10 hacia un int y 10.0 hacia un float — así que aquí la presencia o ausencia de un punto decimal cambia el tipo resultante, aunque JSON en sí no trace tal línea. El paquete encoding/json de Go decodifica por defecto cada número hacia un float64 salvo que pidas otra cosa. La lección es que no puedes suponer que el lado receptor preserva la "cualidad de entero". Si un campo debe viajar de ida y vuelta como número entero, no dependas de que el emisor omita el punto decimal; depende de un esquema y del tipo elegido por el receptor. Nuestra guía complementaria sobre sintaxis de objetos JSON cubre cómo se nombran y enmarcan esos campos en torno a estos valores.

Reglas de Formato de Números JSON: Gramática, Un Solo Tipo y Precisión (RFC 8259)

Sin NaN, Sin Infinity, Sin Trucos de Cero Negativo

El estándar de coma flotante IEEE 754 define tres valores especiales que no son números ordinarios: infinito positivo, infinito negativo y "no es un número" (NaN). Muchos lenguajes pueden producirlos — dividir por cero, sacar la raíz cuadrada de un negativo o desbordar el rango — y muchos los imprimirán encantados como Infinity o NaN. Ninguno de ellos es JSON válido. La gramática de los números no tiene ninguna producción para ellos: Infinity, -Infinity y NaN son palabras sueltas, no números, y un analizador estricto los rechaza de plano.

Esto provoca un fallo genuino y frecuente. En JavaScript, JSON.stringify({ x: Infinity, y: NaN }) no lanza error y no emite Infinity; en su lugar escribe silenciosamente {"x":null,"y":null}, convirtiendo ambos valores especiales en null. Así que la pérdida de datos ocurre en silencio en el momento de la serialización, y el receptor ve null sin ninguna pista de que un cálculo se desbordó o quedó indefinido. Si tu pipeline puede producir estos valores, debes decidir explícitamente cómo codificarlos — habitualmente como una cadena tipo "Infinity", como null con un indicador de estado aparte, o fijándolos a un valor centinela que tu esquema documente. Nunca supongas que un valor especial en crudo sobrevivirá a un salto por JSON; no lo hará.

Redondeo Doble IEEE 754: Por Qué 0.1 + 0.2 No Es 0.3

Incluso un número JSON perfectamente válido puede perder exactitud en el instante en que un analizador típico lo almacena, porque la mayoría de implementaciones usan coma flotante de doble precisión IEEE 754, que representa los valores en binario. Muchas fracciones decimales que parecen exactas sobre el papel no tienen representación binaria finita, exactamente igual que un tercio no tiene representación decimal finita. La demostración clásica: en cualquier entorno de doble IEEE 754, sumar 0.1 y 0.2 da 0.30000000000000004, no 0.3. El texto JSON 0.1 es válido e inequívoco, pero el valor almacenado tras el análisis es el doble más cercano, que está muy ligeramente desviado, y el error aflora cuando calculas con él.

No es un fallo de JSON ni de ningún analizador concreto — es la aritmética que corre por debajo de la mayor parte de la computación. Las consecuencias para los datos JSON son prácticas: no compares números en coma flotante ya analizados buscando igualdad exacta, no acumules muchos decimales pequeños esperando un total limpio, y ten presente que un valor que escribiste como 1.1 puede volver a serializarse como 1.1000000000000001 tras una ida y vuelta por algunos entornos. Cuando la exactitud importa, el valor no debería ser un número en coma flotante en absoluto, lo que conduce directamente a los dos mayores riesgos del mundo real que siguen.

Los Enteros Grandes Más Allá de 253 Pierden Precisión en Silencio

Como un doble almacena su mantisa en 52 bits, solo puede representar cada entero de forma exacta hasta 253. En JavaScript este techo se expone como Number.MAX_SAFE_INTEGER, cuyo valor es 9007199254740991 (es decir, 253 menos uno). Por debajo y hasta esa magnitud, los enteros son exactos. Por encima, los huecos entre dobles representables crecen por encima de uno, así que enteros consecutivos pueden colapsar sobre el mismo valor almacenado.

El efecto es fácil de ver y genuinamente alarmante. En cualquier entorno basado en dobles, 9007199254740992 y 9007199254740993 se redondean ambos al mismo valor almacenado, así que 9007199254740992 === 9007199254740993 se evalúa como true, y sumar uno a 253 devuelve 253. Ahora imagina que ese número era un identificador de base de datos de 64 bits, un ID tipo snowflake, o una referencia de transacción financiera enviada como número JSON desnudo. El texto es JSON perfectamente válido, pero un cliente JavaScript que haga JSON.parse leerá un entero distinto del que se escribió, y no se lanza ningún error. La solución es transmitir los identificadores enteros grandes como cadenas"9007199254740993" — de modo que ningún tipo aritmético los redondee, y analizarlos con una biblioteca de enteros grandes solo cuando realmente necesites calcular. Muchas APIs que exponen IDs de 64 bits hacen exactamente esto por esta razón. Una cadena JSON correctamente escapada conserva cada dígito; un número JSON no.

El Dinero Va en Cadenas o Decimales, No en Flotantes

Combina las dos secciones anteriores y llegas a la regla más firme del diseño práctico con JSON: nunca representes el dinero como un número JSON en coma flotante. Un precio como 19.99 no es exactamente representable en un doble binario, así que en cuanto se analiza se convierte en un valor a un pelo de 19.99, y sumar miles de esos valores deriva por céntimos. Peor aún, la deriva es silenciosa y difícil de reproducir.

Hay dos patrones robustos. El primero es almacenar el dinero como un número entero de unidades menores — céntimos, satoshis o la unidad más pequeña de la moneda — de modo que 19.99 viaje como el entero 1999 con una moneda y una escala documentadas. Los enteros por debajo de 253 son exactos, así que esto es seguro mientras tus importes permanezcan dentro de ese rango, cosa que los valores monetarios ordinarios hacen con holgura. El segundo patrón es almacenar el dinero como una cadena que contenga el decimal exacto, como "19.99", y analizarlo hacia un tipo decimal (el BigDecimal de Java, el decimal.Decimal de Python, o un equivalente) en el lado receptor. Las cadenas conservan cada dígito de forma exacta y nunca invocan aritmética de coma flotante en el tránsito. El patrón que elijas depende de tu stack, pero lo que no debes hacer es dejar un importe monetario como número JSON desnudo esperando que el redondeo nunca muerda. Cuando necesites forzar que un campo sea una cadena con esta forma, o un entero dentro de un rango seguro, un esquema es la herramienta adecuada — combina las reglas de aquí con nuestras herramientas JSON para validar la estructura junto a la sintaxis.

Ejemplo Trabajado: Un Payload que Acierta con los Números

Aquí tienes un único objeto válido que aplica cada recomendación anterior — un identificador entero seguro como cadena, dinero en unidades menores, una cantidad medida como flotante genuino y una escala explícita:

{
"orderId": "9007199254740993",
"currency": "USD",
"amountMinor": 1999,
"scale": 2,
"weightKg": 2.5,
"quantity": 3,
"discountRate": 0.15
}

Cada valor aquí es válido según la gramática de números allí donde es un número, y los dos valores que deben permanecer exactos — el identificador de 64 bits y el precio — se mantienen totalmente fuera de la coma flotante. Contrástalo con una versión ingenua que escribe "orderId": 9007199254740993 y "amount": 19.99: es igual de válida sintácticamente, e igual de peligrosa, porque ambos campos serán alterados en silencio por un analizador basado en dobles.

Cómo Encajan Estas Reglas en el Trabajo Real con JSON

Los bugs numéricos en JSON son insidiosos precisamente porque el texto es válido — ningún analizador se queja, no aparece ningún subrayado rojo, y la corrupción se manifiesta solo más tarde como un ID que no cuadra, una conciliación desviada por un céntimo, o una comprobación de igualdad que debería haber pasado y no lo hizo. Acertar con los números es, por tanto, sobre todo cuestión de disciplina de diseño: decide el tipo y la precisión de cada campo numérico de antemano, mantén los campos de valor exacto (identificadores, dinero) fuera de la coma flotante, y documenta la escala y la moneda junto a los importes. Para la gramática que rodea los objetos que transportan estos campos, consulta nuestra guía de sintaxis de objetos JSON según RFC 8259; para las reglas que gobiernan las cadenas que a menudo usarás para guardar enteros grandes y decimales de forma segura, consulta las reglas de escape de cadenas JSON.

Valida Cada Regla con el Formateador JSON

Nuestro formateador y validador JSON es la forma más rápida de confirmar todo lo de esta guía. Pega un documento y da formato bonito al JSON válido y señala el carácter exacto donde un número inválido — un cero a la izquierda, un decimal desnudo, un Infinity despistado — deja de ser JSON. Como se ejecuta enteramente en tu navegador, ningún dato sale nunca de tu máquina, lo que lo hace seguro para pegar payloads de producción que contienen identificadores e importes reales. Prueba los números inválidos de esta guía uno a uno, y luego pega el ejemplo trabajado anterior: ver dónde falla cada token mal formado, y cómo pasa el payload seguro, convierte estas reglas abstractas en algo que puedes verificar y recordar.

← Volver al Blog