Todo empieza con una petición de red
Todavía recuerdo la primera vez que abrí las herramientas de desarrollador del navegador con la pestaña Network activa. Estaba depurando algo que no funcionaba, seguramente un formulario que enviaba datos y no recibía respuesta, y entre la maraña de peticiones encontré una fila verde que prometía 200 OK. Hice clic y me encontré con un muro de texto. No era HTML, no era XML con su ruido de etiquetas, sino algo que a primera vista parecía una página de un libro de contabilidad escrita por alguien con prisa: llaves, dos puntos, comas, valores entre comillas.
{"ok":true,"data":{"id":12874,"name":"usuario_201","status":"active"}}
En ese momento no sabía muy bien qué estaba mirando. Sabía leerlo a grandes rasgos: había algo que se llamaba name y que valía "usuario_201", y algo llamado status con valor "active". Pero tardé un rato en entender que ese muro de texto era, en realidad, la forma en que dos sistemas que no se conocían de nada acababan de ponerse de acuerdo. Uno había dicho "dame los datos del usuario" y el otro había contestado con esa cadena. Sin ceremonia, sin contrato firmado, sin un estándar complejo que hubiera que estudiar durante semanas.
Esa escena se repite millones de veces cada segundo en Internet, en cada móvil que refresca un feed, en cada aplicación que sincroniza datos con su servidor, en cada panel de administración que carga una tabla. Y detrás de todo eso, casi siempre, hay el mismo formato. Un formato tan discreto que casi no se nota, tan simple que nadie le dedica un tutorial serio, y tan ubicuo que resulta difícil encontrar un proyecto moderno que no lo use en algún punto.
Hablemos de JSON.
Qué es el JSON en realidad
Las siglas significan JavaScript Object Notation, notación de objetos de JavaScript, y la parte del nombre es a la vez su origen y su trampa. La historia empieza a finales de los años noventa. Douglas Crockford, programador de larga trayectoria que trabajaba en empresas de software de Internet, se encontró con el problema de siempre: necesitaba un formato para intercambiar datos entre un servidor y un navegador, y las opciones existentes no le convencían. XML era verboso y requería librerías enteras para parsearlo; no había nada ligero y directo.
La solución la tenía delante. JavaScript ya permitía escribir objetos literales, esa sintaxis con llaves y pares de clave: valor, así que no había que inventar nada nuevo: bastaba con tomar esa idea, recortarla hasta dejarla en un subconjunto mínimo y seguro, y estandarizarla lo suficiente como para que cualquiera pudiera leerla y escribirla sin depender de una librería. Alrededor del año 2001, Crockford empezó a promover ese formato con el nombre de JSON, y la cosa creció despacio pero con una constancia notable. Primero lo adoptaron proyectos concretos, luego se publicó en json.org con una descripción simple del formato y un puñado de ejemplos, y con el tiempo llegó la formalización.
Hoy hay dos documentos que definen JSON de manera oficial. Por un lado está el estándar ECMA-404, mantenido por Ecma International, y por otro el RFC 8259, publicado por el IETF en 2017, que revisó y actualizó el antiguo RFC 7159 y el RFC 4627 original de 2006. Ambos describen lo mismo, con más o menos el mismo detalle, y lo describen con una precisión que contrasta con lo sencillo que es el formato en sí.
Conviene ser honesto con el nombre: es un nombre poco afortunado. Decir que JSON es "JavaScript Object Notation" sugiere que es un formato de JavaScript, cuando en realidad la relación con JavaScript es casi anecdótica. El formato toma prestada la sintaxis de los literales de objeto del lenguaje, sí, pero no depende de JavaScript para nada. Un servidor escrito en Go, una base de datos en PostgreSQL y un cliente en Python pueden intercambiar JSON sin que en toda la conversación aparezca ni una línea de JavaScript. La sintaxis tiene un pedigrí javascriptiano, pero el formato vive su vida completamente independiente del lenguaje que le dio nombre. Es como si llamáramos a la pizza "comida italiana" y luego resultara que se come en todo el mundo sin que Italia tenga que intervenir en nada.
Al final, lo que define a JSON no es su nombre ni su historia, sino dos propiedades que lo han hecho irresistible. Es legible para las personas: puedes abrir un archivo JSON y entender su estructura sin manual, porque usa las llaves, las comas y las comillas que todo programador reconoce. Y es legible para las máquinas: cualquier lenguaje moderno puede parsearlo con una función de la librería estándar, sin dependencias externas. Esas dos cosas, juntas, son más raras de lo que parecen, y explican casi todo lo que ha pasado con JSON en estos veinticinco años.
Por qué le ganó al XML
Para entender por qué JSON se impuso, hay que recordar primero el mundo en el que apareció. A principios de los dos mil, XML era el rey absoluto del intercambio de datos. Los servicios web se construían sobre SOAP, un protocolo que envolvía las llamadas en capas de XML con sobres, cabeceras y cuerpos, y que hacía que una simple petición de "dame el precio de este producto" se convirtiera en un documento que podía ocupar varias pantallas. Los archivos de configuración de un montón de herramientas se escribían en XML y a menudo parecían páginas de una guía telefónica: apertura de etiqueta, atributos, más etiquetas, cierre de etiqueta, y así durante cientos de líneas para expresar lo que hoy son cinco líneas de configuración.
Y había debates. Recuerdo la discusión recurrente entre atributos y elementos, si un dato debía ir como <precio unidad="EUR"> o como <precio><unidad>EUR</unidad></precio>, discusión que podía consumir horas de reuniones sin que nadie llegara a un acuerdo definitivo. Luego estaban los namespaces, ese mecanismo para evitar colisiones de nombres que añadía prefijos como xmlns:soap y que complicaba la lectura de cualquier documento. Y para rematar, si querías transformar un XML en otro, necesitabas XSLT, un lenguaje de plantillas con una sintaxis propia que costaba más aprender que el propio XML que pretendía transformar.
Todo eso era potente, sin duda. XML es un formato muy expresivo, con herramientas de validación serias y una familia de estándares alrededor que cubría casi cualquier necesidad. Pero esa potencia tenía un precio, y el precio era la fricción. Escribir XML a mano es un castigo, leerlo requiere un esfuerzo que rara vez compensa, y su flexibilidad para representar el mismo dato de cinco maneras distintas significaba que dos sistemas que "hablaban XML" no tenían ninguna garantía de entenderse.
JSON ganó por una razón muy concreta: no porque fuera bonito, que no lo es especialmente, sino porque todo lo demás era más molesto. El formato mapea de manera directa y casi uno a uno con las estructuras de datos que ya existen en todos los lenguajes de programación. Un objeto es un diccionario, un hashmap, un mapa, un objeto, lo que tu lenguaje prefiera llamarlo. Un array es una lista. Las cadenas son cadenas, los números son números, el booleano es un booleano y el null es la ausencia de valor. No hay que aprender a traducir de un modelo de datos a otro porque el modelo de datos de JSON ya es el modelo de datos de tu lenguaje.
Y se puede leer sin esquema. Un XML a menudo no te dice nada hasta que conoces el esquema o el documento que lo define; un JSON, en cambio, se entiende solo, porque las estructuras que usa son las que llevas años usando en tu código. La curva de aprendizaje es prácticamente plana: si sabes escribir un diccionario o una lista en cualquier lenguaje, ya sabes escribir y leer JSON. En el año 2006, cuando un formato te permitía ir del problema a la solución sin pasar por un curso de XSLT ni por un libro de 400 páginas sobre SOAP, la partida estaba decidida aunque entonces nadie lo supiera.
Los seis tipos de datos
JSON tiene exactamente seis tipos de datos, y eso es todo. No hay más. Esa escasez es una de sus mayores virtudes, porque significa que no hay sorpresas: cualquier dato que quieras representar tiene que encajar en una de estas seis cajas, y si no encaja, te toca a ti decidir cómo traducirlo.
Objeto. Una colección de pares clave-valor entre llaves. Es la estructura que se usa para representar entidades, registros, cualquier cosa que tenga nombre y valor. Las claves son siempre cadenas.
{"nombre": "Ana", "edad": 34, "activa": true}
Array. Una lista ordenada de valores entre corchetes. Puede contener cualquier tipo, incluso mezclado, y puede estar vacía o anidar otras estructuras.
["rojo", "verde", "azul"]
Cadena. Texto entre comillas dobles. Es el único tipo que admite caracteres que no se pueden teclear directamente, mediante escapes.
"una cadena con una \"cita\" dentro"
Número. Entero o decimal, con signo opcional, y con la posibilidad de usar notación exponencial. No hay distinción entre entero y flotante en el formato: un número es un número.
42
-3.1416
6.02e23
Booleano. Dos valores posibles, escritos siempre en minúsculas: true y false. No valen True, ni TRUE, ni 1.
Null. El valor para "no hay nada aquí". Se escribe null, en minúsculas, y no debe confundirse con una cadena vacía ni con el número cero.
Con esos seis ladrillos se construye todo lo demás. En la práctica, casi nunca trabajas con un valor suelto, sino con un objeto que los combina a todos. Un ejemplo típico, un registro de usuario:
{
"id": 12874,
"nombre": "Usuario 201",
"email": "usuario201@example.com",
"activo": true,
"roles": ["lector", "editor"],
"ultimo_acceso": "2026-08-11T14:32:07Z",
"perfil": {
"pais": "AR",
"idioma": "es",
"preferencias": {
"tema": "oscuro",
"notificaciones": false
}
},
"equipo_actual": null
}
Fíjate en lo que pasa aquí sin que apenas te des cuenta. Los roles son una lista. El perfil es un objeto dentro de un objeto. Las preferencias, otro nivel de anidación. La fecha viene como una cadena porque JSON no tiene tipo fecha, y el equipo actual es null porque el usuario no tiene uno. En unas pocas líneas has expresado una entidad completa, con jerarquía, listas y valores opcionales, y lo has hecho con una sintaxis que cualquier persona con un mínimo de contacto con la programación puede leer de un vistazo. Eso, y no otra cosa, es el superpoder de JSON.
Las aristas de la sintaxis
Que JSON sea simple no significa que no tenga trampas. Hay un puñado de detalles sintácticos en los que hasta los más veteranos tropiezan de vez en cuando, y conocerlos evita un montón de quebraderos de cabeza.
Las claves van siempre entre comillas dobles. Esto sorprende a quien viene de JavaScript, donde un objeto puede escribirse sin comillas en las claves, pero JSON es estricto: {nombre: "Ana"} es inválido, y {"nombre": "Ana"} es lo único correcto. Las cadenas, por su parte, solo aceptan comillas dobles. Las comillas simples, tan naturales en otros contextos, aquí no existen.
No hay comentarios. Cero. Ni //, ni /* */, ni #. Un archivo JSON es solo datos, sin anotaciones. Esto es una de las quejas más antiguas contra el formato, y la veremos con más detalle más adelante, porque explica por qué existen tantos dialectos como JSON5 o JSONC.
No se permiten comas finales. Es decir, la última pareja de claves o el último elemento de un array no pueden llevar coma después. Los humanos tendemos a añadirla por inercia, sobre todo cuando editamos a mano, y los parsers la rechazan. Es una fuente clásica de errores en archivos de configuración editados a mano.
En los números, no se permiten ceros a la izquierda. 01 es inválido; tiene que ser 1. Tampoco existen NaN, ni Infinity, ni -Infinity: son valores que JavaScript entiende pero que JSON rechaza de plano, porque no se pueden representar como números JSON. El cero negativo, -0, es legal según la gramática, aunque la mayoría de los parsers lo convierten en cero sin más. Y la notación exponencial está permitida: 1e3 es mil.
Las claves duplicadas son legales pero problemáticas. La gramática no prohíbe que un objeto tenga dos veces la misma clave, y lo que ocurre entonces queda a criterio de cada parser. En la práctica, casi todos se quedan con el último valor, pero "casi todos" no es "todos", así que no cuentes con ello. Un JSON con claves duplicadas es un JSON que está mal formado en espíritu aunque pase el parser.
Los caracteres especiales se manejan con escapes. Cualquier carácter Unicode se puede escribir directamente si tu archivo está en UTF-8, que es lo habitual, o mediante la secuencia \uXXXX con el código hexadecimal. Para los caracteres fuera del plano básico, como los emojis, se usan dos \uXXXX que forman un par de sustitución: por ejemplo, el emoji 😀 se escribe como \ud83d\ude00. El juego completo de escapes que acepta el formato es pequeño y cerrado:
\"para una comilla doble dentro de una cadena\\para una barra invertida\/para una barra normal, que no es obligatorio pero está permitido\bpara retroceso\fpara salto de página\npara salto de línea\rpara retorno de carro\tpara tabulación\uXXXXpara un carácter Unicode
Nada más. Si intentas usar un escape que no está en esta lista, como \x41 o \a, el parser te va a rechazar el documento. JSON no acepta inventos.
¿Es JSON un subconjunto de JavaScript?
Esta es una pregunta que genera más confusión de la que debería. La respuesta corta es: no, no exactamente, aunque la idea de que lo es ha estado flotando desde el principio y tiene su miga histórica.
Un objeto literal de JavaScript y un objeto JSON se parecen, pero no son lo mismo. En JavaScript puedes escribir {clave: 'valor'} con comillas simples y sin comillas en las claves, y puedes añadir una coma final después del último elemento. En JSON, ninguna de esas tres cosas está permitida. JavaScript admite comentarios en el código; JSON, no. Un objeto de JavaScript puede tener como valor una función, o undefined, o NaN, o Infinity, o una instancia de Date; en JSON, todo eso no tiene representación posible. No puedes serializar una función a JSON, ni un undefined, ni un NaN (JSON.stringify convierte NaN e Infinity en null, y en el caso de las propiedades con valor undefined o función, directamente las omite; otro pequeño comportamiento que sorprende a quien lo descubre por primera vez).
La historia del "JSON es un subconjunto de JavaScript" viene del RFC 4627, publicado en 2006, que describía JSON como un subconjunto de JavaScript. Con el tiempo esa afirmación resultó incómoda, porque no es del todo cierta. El ejemplo clásico son los separadores de línea U+2028 y U+2029, dos caracteres Unicode que en JSON son perfectamente válidos dentro de una cadena pero que, en versiones antiguas de JavaScript, se interpretaban como finales de línea dentro de una cadena literal, lo que rompía el código si intentabas incrustar el JSON directamente en un script. Cuando llegó la formalización, el estándar ECMA-404, publicado en 2013, definió JSON como un formato independiente con su propia gramática: inspirada en JavaScript, sí, pero no dependiente de él. El lenguaje moderno ha ido cerrando además esa brecha, porque desde la edición de 2019 de ECMAScript, U+2028 y U+2029 también son válidos dentro de las cadenas de JavaScript. Pero la lección quedó: llamar a JSON "subconjunto de JavaScript" siempre fue más un atajo que una verdad técnica.
¿Por qué importa esta distinción en la práctica? Porque determina qué herramienta usas para convertir texto en datos. Si crees que JSON es JavaScript y que basta con evaluarlo, acabas usando eval() para parsearlo, y eso, como veremos dentro de un momento, es una de las peores decisiones que se pueden tomar. Si, por el contrario, entiendes que JSON es un formato independiente con su propia gramática, usas un parser de verdad, JSON.parse, que no ejecuta nada y que aplica las reglas del formato sin sorpresas. JSON.parse es estricto y se nota: te lanza una excepción ante cualquier desviación, y eso, lejos de ser un defecto, es exactamente lo que quieres cuando desconfías de todo lo que te llega por la red.
Dónde te lo encuentras a diario
JSON no es un formato de laboratorio que se estudie y se olvide. Está por todas partes, y una vez que empiezas a fijarte, ya no puedes dejar de verlo. Repasemos los lugares donde te lo vas a cruzar en el trabajo de cualquier día.
Cuerpos de peticiones y respuestas de APIs REST. Este es el gran escenario. Cuando una aplicación móvil pide una lista de productos, cuando un formulario envía tus datos de registro, cuando el frontend consulta el estado de un pedido, la conversación casi siempre viaja en JSON. La respuesta típica es un objeto con un indicador de éxito, los datos y quizá un mensaje, y las peticiones van por el mismo camino en sentido inverso. Es tan habitual que damos por sentado que "consumir una API" significa, casi literalmente, "parsear JSON".
Archivos de configuración. No hace falta ir muy lejos: el package.json de cualquier proyecto de Node.js es JSON, y el tsconfig.json también. Pero la lista no termina ahí. Hay configuración en JSON en herramientas de linting, en editores, en servicios de integración continua, en extensiones de navegador, en decenas de programas que prefieren un formato simple a inventarse uno propio. Lo irónico es que estos archivos se editan a mano, y el formato prohíbe los comentarios, así que todo el mundo acaba conviviendo con la ausencia de notas explicativas en su configuración.
Almacenamiento en el navegador. localStorage guarda solo cadenas de texto, así que si quieres guardar un objeto, lo serializas a JSON con JSON.stringify y lo lees con JSON.parse. IndexedDB es más potente y también acepta JSON como forma de almacenar objetos. Cualquier dato que sobreviva a un refresco de página en tu aplicación, casi seguro que pasó por JSON en algún momento.
Bases de datos. PostgreSQL tiene el tipo jsonb, que guarda JSON en un formato binario optimizado y permite consultarlo con operadores específicos. MongoDB almacena sus documentos en BSON, que no es JSON pero sí una variante binaria que extiende su modelo de datos: es JSON con tipos adicionales como fechas, binarios y números de coma flotante de 128 bits. Aunque uses SQL toda tu vida, es muy probable que acabes leyendo o escribiendo JSON en alguna columna o en algún campo.
JSON Lines, o NDJSON. Para los casos en los que quieres procesar datos línea a línea, en streaming, existe esta variante: un JSON distinto por línea, separados por saltos de línea. Cada línea es un documento independiente. Es lo que usan muchos sistemas de logging, pipelines de datos y herramientas de exportación masiva, porque permite leer un archivo enorme sin cargarlo entero en memoria: vas leyendo línea a línea, procesas, y listo.
Mensajes de WebSocket. Cuando un servidor quiere empujarte datos en tiempo real, desde notificaciones hasta actualizaciones de un precio, el mensaje suele viajar como un pequeño JSON. Es compacto, se parsea sin librerías y le sirve tanto al cliente como al servidor.
La función fetch del navegador. La API moderna para hacer peticiones HTTP trae métodos de conveniencia: response.json() te devuelve una promesa que resuelve al objeto ya parseado, y JSON.stringify es la forma natural de preparar el cuerpo de una petición. No es exagerado decir que buena parte del JavaScript que se escribe hoy en día empieza o termina con una de estas dos funciones.
Como ves, la lista cruza fronteras de todo tipo: lenguajes, bases de datos, navegadores, protocolos en tiempo real. Ese cruce de fronteras sin fricción es exactamente la razón por la que el formato se usa tanto. No hay nada que conectar, porque JSON ya está en todos los extremos de la conexión.
Cómo lo analiza cada lenguaje
No voy a hacer tutoriales aquí, que para eso están las documentaciones oficiales, sino darte una idea honesta de cómo se siente trabajar con JSON en los lenguajes que más te vas a encontrar. La moraleja es siempre la misma, pero conviene verla repetida en varios idiomas para que quede claro: esto está resuelto, y resuelto bien, en todas partes.
JavaScript. Aquí es donde todo es más directo. JSON.parse(texto) convierte una cadena en un objeto, y JSON.stringify(objeto) hace el camino inverso. Las dos funciones son parte del estándar desde hace décadas, no necesitas importar nada, y aunque en apariencia son triviales, tienen sus matices: stringify ignora las funciones y las propiedades con valor undefined, convierte NaN e Infinity en null, y acepta un segundo argumento para filtrar o transformar qué se serializa y un tercero para el sangrado. Cuando empecé a trabajar con JSON, me parecía magia que algo tan simple funcionara tan bien.
Python. El módulo json de la librería estándar ofrece json.loads() para leer una cadena y json.dumps() para escribirla. En Python, el null de JSON se convierte en None al leer (json.loads) y None se serializa como null al escribir (json.dumps), sin que tengas que hacer nada; Python no tiene una segunda palabra null propia que distinguir. La conversión de tipos es predecible: un objeto JSON se convierte en un diccionario, un array en una lista, una cadena en un str, un número en int o float según tenga decimales. Para los casos raros hay parámetros que permiten controlar el sangrado, la codificación de caracteres no ASCII o qué hacer con los tipos que JSON no puede representar.
Java. El lenguaje no trae soporte JSON en la librería estándar, así que la gente usa librerías de terceros, y las dos grandes son Jackson y Gson. Jackson es la más extendida, con un ecosistema enorme y la capacidad de mapear directamente a clases con anotaciones; Gson es más ligera y fue durante años la opción por defecto de Android. Ambas hacen lo mismo en esencia: convertir entre cadenas JSON y objetos Java, con un esfuerzo de configuración que va desde "cero, porque las convenciones ya cuadran" hasta "un puzle, porque las clases no se parecen al JSON".
Go. Aquí la cosa cambia de sabor porque Go no tiene clases ni herencia, sino structs. El paquete encoding/json trabaja con structs y etiquetas: defines la estructura con anotaciones como json:"name" para indicar cómo se llama cada campo en JSON, y luego json.Marshal y json.Unmarshal hacen la conversión. Es tipado y seguro, pero también tiene sus manías, como que los campos que no conoces se descartan al deserializar si no lo configuras para que los conserve.
Rust. La librería más popular es serde, que no es solo un parser de JSON sino un marco genérico de serialización. La gracia de serde es que convierte JSON en estructuras de Rust con verificación de tipos en tiempo de compilación: si el JSON no encaja con tu estructura, falla al deserializar, no más tarde en tu código. Es robusto, rápido, pero exige declarar tus tipos de antemano, lo que a veces es exactamente lo que no quieres si los datos son impredecibles.
La moraleja se repite en todos los casos: parsear JSON es una operación resuelta, estándar y segura en cualquier lenguaje moderno. Si alguna vez te ves escribiendo tu propio parser, detente y pregúntate qué está pasando, porque casi seguro estás reinventando una rueda que lleva veinte años rodando.
Seguridad: lecciones caras
JSON parece inofensivo, y lo es cuando se maneja con cabeza. Pero su historia de seguridad está llena de cicatrices, y vale la pena conocerlas porque son lecciones que se pagaron caras para que tú no tengas que pagarlas.
La más famosa es el desastre de eval(). En los primeros años de JSON, la forma "fácil" de parsear era evaluar el texto directamente en JavaScript: como JSON "parecía" JavaScript, bastaba con eval("(" + texto + ")") para obtener el objeto. El problema es que eval no parsea, ejecuta. Si el texto no venía de un servidor de confianza, o si un servidor de confianza había sido comprometido, ese "JSON" podía contener código arbitrario que se ejecutaría con todos los permisos de tu página. Una función maliciosa en la respuesta, y tenías una ejecución remota de código en el navegador de todos tus usuarios. De ahí nació la regla de oro que sigue vigente: nunca, jamás, parsear JSON con eval. Un parser de verdad lee el texto, lo interpreta según la gramática y no ejecuta nada. JSON.parse, que llegó más tarde y se estandarizó, fue en parte la respuesta a este problema.
Luego está JSONP, el pariente comprometido. Antes de que el navegador permitiera hacer peticiones entre dominios con CORS, JSONP era un truco para sortear la restricción: en vez de pedir los datos y parsearlos, cargabas una etiqueta <script> cuya respuesta era una llamada a una función con los datos como argumento. Funcionaba, pero cambiaba el modelo de seguridad: estabas ejecutando código de otro dominio en tu página, porque así funcionaban los scripts. Todo el mundo sabía que era frágil, pero era lo que había, y hoy sigue siendo el ejemplo perfecto de cómo una solución de seguridad por la puerta de atrás acaba creando agujeros nuevos.
La vulnerabilidad de los arrays de nivel superior es otro episodio instructivo. En los primeros tiempos, la respuesta de una API podía ser directamente un array, algo así como [{"nombre":"Ana"},{"nombre":"Luis"}]. La amenaza era la siguiente: si un atacante lograba que el navegador de una víctima cargara esos datos como si fueran un script, por ejemplo con una etiqueta <script> que apuntara a la API, el navegador ejecutaba el contenido. Un array de nivel superior es una expresión JavaScript válida, y redefiniendo cómo se construyen los arrays se podían capturar los datos. De ahí nació la costumbre, aún muy extendida, de devolver siempre un objeto de nivel superior: en posición de sentencia, un bloque {...} no se evalúa como expresión, así que esa vía se cerraba. Eso sí, para los puristas conviene aclarar que es una buena práctica de seguridad, no una regla del estándar: el RFC 8259, que es el que está vigente, permite explícitamente que un documento JSON sea cualquier valor, incluido un array. La recomendación de envolverlo en un objeto es prudencia defensiva, no gramática.
La contaminación de prototipos es más moderna y más sutil. Muchas librerías de JavaScript, al fusionar objetos, copian las claves del JSON a los objetos del programa. Si el JSON contiene una clave llamada __proto__, constructor o prototype, y la librería no tiene cuidado, esa clave puede modificar el prototipo del objeto, afectando a todos los objetos del programa, incluidos los que no tienen nada que ver con los datos recibidos. Un atacante que controle el JSON puede entonces alterar el comportamiento de tu aplicación entera. La defensa es tratar cualquier clave que empiece por __ o que se llame prototype como sospechosa, y usar funciones de fusión que no toquen el prototipo.
Y luego están las bombas JSON. Un JSON puede ser pequeño en bytes y enorme en estructura: un documento con cientos de miles de niveles de anidación, del estilo [[[[..., cabe en un archivo diminuto y puede reventar la pila del parser; y un objeto con un número desorbitado de claves repetidas puede llenar la memoria del proceso. Algunos parsers fallan con elegancia, otros se caen. Por eso las buenas prácticas incluyen límites de tamaño de la petición, límites de profundidad de anidación y timeouts. Recibir un JSON de cien megabytes no debería ser posible ni siquiera en teoría.
La regla moderna es una lista corta y contundente: usa siempre un parser real, nunca eval; limita el tamaño de lo que aceptas; limita la profundidad si tu parser lo permite; y desconfía de las librerías que fusionan objetos sin comprobar las claves del prototipo. JSON no es peligroso por sí mismo, pero cada generación ha tenido que aprender, a veces a golpes, que el texto que llega por la red no es tu código ni tu estructura de datos: es un adversario potencial con forma de documento.
Validación y extensiones: Schema, JSON5
JSON define cómo se escribe un documento, pero no dice nada sobre qué debe contener. Esa libertad es cómoda para intercambiar datos y un dolor de cabeza cuando necesitas garantías: ¿cómo sabes que el JSON que te llega tiene los campos que esperas, con los tipos que esperas, y que no le falta nada?
Para eso existe JSON Schema. Es un estándar, con su propia sintaxis, que te permite describir la forma que debe tener un documento: qué claves son obligatorias, qué tipo tiene cada una, qué valores admite una cadena, entre qué límites debe estar un número, cuántos elementos mínimo y máximo tiene un array. Un documento JSON Schema es, a su vez, un documento JSON, lo que tiene su gracia. Con él puedes validar los datos antes de procesarlos, generar documentación, o incluso generar formularios o clientes a partir de la descripción. Las implementaciones existen para todos los lenguajes, y aunque el estándar tiene fama de ser farragoso en sus versiones más completas, la parte básica se aprende en una tarde.
Más pragmáticas son las extensiones que la gente usa de verdad en el día a día, y que no son estándares sino parches cómodos. La más conocida es JSON5, cuyo nombre juega con "JSON para humanos": añade comentarios, comas finales, claves sin comillas, comillas simples y valores como Infinity, NaN y undefined. Su objetivo es que los archivos de configuración se puedan escribir y editar a mano sin los dolores del JSON puro, y lo consigue a costa de no ser JSON. Es un dialecto, y solo lo entienden las herramientas que lo soportan.
Muy relacionado está JSONC, que no es más que "JSON con comentarios". No tiene un estándar formal que lo defina, sino que se ha ido abriendo camino como convención: muchos editores y herramientas aceptan archivos con la extensión .jsonc que son JSON normal con la posibilidad de añadir comentarios. La regla no escrita es que un JSONC debe seguir siendo JSON válido si le quitas los comentarios.
Y por completar el catálogo, está HJSON, otro dialecto orientado a configuración que permite comentarios, comas finales, claves sin comillas y un formato multilínea más amable. Cada uno de estos formatos resuelve el mismo problema de fondo: JSON es incómodo para ser escrito a mano por humanos, y la comunidad ha ido parcheando esa incomodidad una y otra vez. Ninguno ha llegado a ser un estándar, y probablemente nunca lo será, pero todos tienen su nicho y sus usuarios agradecidos. Son la prueba de que, cuando un formato es suficiente para las máquinas pero incómodo para las personas, la gente no se queda quieta: encuentra la forma de hacerlo más llevadero.
Sus límites y las quejas
JSON lleva veinticinco años ganándose la vida, y en todo ese tiempo ha acumulado una lista de quejas tan vieja como el propio formato. No son defectos graves, pero son reales, y conviene conocerlas porque explican muchas decisiones de diseño que ves en el código de otros.
La primera, y la más repetida, es la ausencia de comentarios. Un archivo de configuración sin comentarios es un archivo donde nadie puede explicar por qué se puso ese valor ni advertir de una trampa. Esa limitación, que es deliberada porque los comentarios complican el parser y abren la puerta a dialectos, es la culpable de que existan JSON5, JSONC y HJSON, y de que tantas herramientas terminen añadiendo una clave "_comentario" o un campo $schema con la documentación.
Luego está el tema de las fechas. JSON no tiene tipo fecha, así que todo el mundo reinventa la rueda con cadenas ISO-8601, y eso significa que cada API, cada librería y cada programador tiene su propia convención sobre el formato exacto, la zona horaria y el manejo del Z final. Lo que en un sistema es "2026-08-12T10:00:00Z", en otro es "12/08/2026" o un número de milisegundos desde 1970. date-fns y Joda-Time existen porque el cálculo de fechas, zonas horarias y formato es complejo en sí; lo que la falta de tipo fecha en JSON te obliga a decidir es solo cómo serializar la fecha (normalmente como cadena ISO-8601).
Los números son otra fuente de dolor. La limitación no la impone el formato: la gramática de JSON admite números de longitud arbitraria, y el redondeo a coma flotante de doble precisión (IEEE-754) lo introducen los parsers y los lenguajes que lo consumen (por ejemplo, el tipo Number de JavaScript). Un identificador de 64 bits, un valor de criptomoneda con muchos decimales, un importe bancario que necesita exactitud centavo a centavo: todo eso pierde precisión si lo metes en un JSON. Los bancos lo notan, y por eso en esos mundos el dinero se envía como cadena de texto con las unidades correctas, o se usan formatos con tipos más ricos, en lugar de fiarse del número plano. Es una limitación práctica que se aprende en cuanto un id de 20 dígitos aparece con dos ceros de más.
Tampoco hay datos binarios. Si quieres enviar una imagen, un PDF o un fragmento de audio en un JSON, tu única opción es codificarlo en base64, lo que aumenta el tamaño un tercio y complica la lectura. Es un precio que se paga por mantener el formato en texto plano, y hay casos donde ese precio es inaceptable.
Y la anidación profunda es un problema silencioso. Un JSON puede anidar objetos y arrays hasta donde quieras, pero los parsers y los lenguajes tienen límites de pila, y un documento con miles de niveles de profundidad puede tumbar un proceso. Los parsers modernos suelen tener un límite de profundidad configurable, pero el tema aparece de vez en cuando en forma de error raro y difícil de diagnosticar.
La lista se completa con las claves duplicadas, de las que ya hablamos y cuyo comportamiento depende del parser, y con el hecho de que no hay tipos nativos para muchas cosas que la gente necesita: fechas, decimales exactos, binarios, horas. Cada una de esas carencias ha dado lugar a una microconvención propia. En conjunto, son la razón por la que el mundo de los datos tiene tantas ruedas reinventadas: no es que a nadie le guste reinventarlas, es que el formato común no las trae de serie.
Las alternativas
No todo son ventajas, y hay casos en los que conviene mirar a otro lado. Las alternativas a JSON existen, y cada una ocupa un hueco concreto. Merece la pena conocerlas, aunque solo sea para saber cuándo JSON no es la respuesta.
YAML. Es el favorito de los archivos de configuración, porque usa sangría en lugar de llaves y corchetes, lo que lo hace muy legible para las personas. Pero esa legibilidad tiene un precio: la sintaxis es notoriamente traicionera. El comportamiento de las cadenas puede cambiar según el contenido, las tabulaciones y espacios conviven mal, y hay casos famosos de valores que se interpretan de forma sorprendente, como la cadena no o yes, que en algunos dialectos se convierten en booleanos. YAML es estupendo cuando lo lee una persona, y una pesadilla cuando lo edita una persona distinta que no conoce sus reglas. Para muchos, JSON es más aburrido pero más fiable.
TOML. Es la respuesta para la configuración. Pensado para ser escrito por humanos, con una sintaxis de pares clave = valor muy sencilla, tablas para agrupar y tipos básicos definidos. Es el formato de configuración de Rust y de muchas herramientas modernas. Su punto fuerte es justo el que le falta a JSON: está diseñado para que los humanos lo escriban a mano sin errores. Su punto débil es que es menos expresivo para datos anidados complejos, y que no es un estándar tan ubicuo como JSON.
Protocol Buffers. Si necesitas compacto y tipado, esta es la opción seria. Protobuf, que es de Google, define los datos mediante un archivo de esquema .proto, y a partir de ese esquema se genera código en muchos lenguajes. Los datos se serializan en binario, mucho más pequeño y rápido de parsear que JSON. A cambio, exige un paso de generación de código, un esquema previo, y renuncias a la legibilidad humana. Cuando la latencia o el tamaño importan de verdad, como en servicios de alto rendimiento o en sistemas con miles de millones de mensajes, Protobuf gana. Cuando quieres leer los datos con el ojo, no.
MessagePack y CBOR. Son los "JSON binarios". MessagePack serializa los mismos tipos básicos que JSON pero en binario, de modo que los mensajes son más compactos y rápidos de procesar, y CBOR es la versión estandarizada por el IETF con ese mismo espíritu, usado en protocolos como COSE y en dispositivos con recursos limitados. Ambos tienen la ventaja de que no necesitan un esquema previo, a diferencia de Protobuf, y de que puedes convertir entre ellos y JSON con facilidad. Son la opción cuando el tamaño importa pero no quieres montar toda la maquinaria de los esquemas.
BSON. Es la variante binaria de MongoDB, que extiende JSON con tipos adicionales: fechas, binarios, enteros de 64 bits, decimales de 128 bits. Su propósito es servir de almacenamiento interno en la base de datos y de formato de intercambio con los clientes, no sustituir a JSON en el mundo exterior. Si trabajas con MongoDB, lo usas sin pensar en ello.
La pregunta de cuándo usar cada uno se responde sola con un poco de contexto. Para APIs, intercambio entre servicios y cualquier cosa que deba ser legible, JSON sigue siendo la opción natural. Para configuración escrita a mano, TOML o YAML. Para datos masivos con esquema fijo, Protobuf. Para tamaño mínimo sin esquema, MessagePack o CBOR. Y para almacenamiento en MongoDB, BSON. No hay un ganador absoluto: hay un formato para cada fricción, y JSON es el que menos fricción añade en el caso más común.
Cierre: los formatos aburridos viven más
Han pasado más de veinticinco años desde que Douglas Crockford empezó a hablar de su notación, y JSON sigue ahí, exactamente igual que entonces. En un mundo que cambia de marco de JavaScript cada dos años, que abandona librerías por capricho y que reinventa la rueda con una frecuencia casi industrial, un formato que no ha cambiado en un cuarto de siglo es casi un monumento.
La razón de esa longevidad no es que JSON sea bonito, ni elegante, ni potente. Es que es suficientemente bueno, está en todas partes y es absurdamente simple. Suficientemente bueno porque cubre la inmensa mayoría de los casos sin fricción. En todas partes porque no hay lenguaje moderno que no lo soporte de serie, sin dependencias. Y absurdamente simple porque un documento JSON se entiende en cinco minutos y se escribe sin consultar documentación.
La lección, que suena a aburrida pero que es de las más útiles de la profesión, es que los formatos aburridos viven más. Los formatos que triunfan no son los más potentes ni los más elegantes, sino los que menos fricción añaden en el caso más común, los que puedes usar sin pensar, los que no te obligan a montar infraestructura para decir "aquí va un nombre y una edad". La próxima vez que tengas que elegir cómo intercambiar datos entre dos sistemas, o cómo guardar la configuración de un servicio, piensa si de verdad necesitas algo más que llaves, corchetes, comillas y un puñado de tipos. Lo más probable es que no, y que la respuesta correcta siga siendo la de siempre: un JSON. Sencillo, aburrido y, por eso mismo, casi imposible de matar.
