Geonode logo
Geonode Team

Geonode Team

Actualizado: 7 de octubre de 2026

Publicado: 2 de septiembre de 2026

JSON.parse() en JavaScript: una guía completa

`JSON.parse()` convierte una cadena en un valor de JavaScript. Esa es toda la API, y se aprende en unos diez segundos. Lo interesante es todo lo que la rodea: la función «reviver», la pérdida de precisión numérica que pasa desapercibida en producción, el caso especial de «`__proto__`» y la reciente incorporación al estándar que por fin permite ver el texto original. Esta guía lo abarca todo, incluyendo los modos de fallo y no solo el caso ideal.

Una aclaración sobre por qué una empresa de proxies escribe sobre JSON.parse. Somos Geonode y vendemos proxies, y el SyntaxError más habitual que nos comunican nuestros clientes no tiene nada que ver con JSON en absoluto: es Unexpected token '<', lo que significa que la respuesta fue HTML en lugar de JSON, es decir, que la API devolvió una página bloqueada, una redirección de inicio de sesión o una página de error. Así que aquí va una advertencia sincera: si aparece el error «JSON.parse» en los datos que has obtenido, registra el cuerpo de la respuesta sin procesar antes de cambiar nada más. Nueve de cada diez veces, el analizador funciona perfectamente e informa con precisión de que se te ha enviado una página web. Comprar proxies solo soluciona eso en el caso concreto en el que te hayan bloqueado; no sirve de nada si la URL es incorrecta, el token ha caducado o hay un límite de frecuencia que deberías respetar. Imprime primero la respuesta.

Una vez aclarado esto, pasemos a la API propiamente dicha.

Conceptos básicos y los errores con los que te encontrarás realmente

const data = JSON.parse('{"name": "Ada", "born": 1815}');
// { name: "Ada", born: 1815 }

Dos parámetros: el texto y una función «reviver» opcional. Eso es todo.

Lanza un error «SyntaxError

» cuando la entrada incumple la sintaxis de JSON, y esta es más estricta que la sintaxis de los literales de objetos de JavaScript en aspectos que suelen pillar desprevenidos a los usuarios. Hay cuatro casos que explican casi todos los errores.

Comillas simples. MDN lo deja claro: «Las cadenas JSON deben estar delimitadas por comillas dobles (no simples)». Válido en JavaScript, inválido en JSON.

JSON.parse("{'name': 'Ada'}");  // SyntaxError
JSON.parse('{"name": "Ada"}');  // fine

Comas finales. Válidas en JavaScript moderno, inválidas en JSON:

JSON.parse("[1, 2, 3, 4, ]");  // SyntaxError

Claves sin comillas. {name: "Ada"}

es un literal de objeto válido, pero no es JSON. Las claves deben ser cadenas entre comillas.

La respuesta no era JSON. La descrita anteriormente. Unexpected token '<'

significa que el cuerpo comenzaba con <

, lo que indica HTML. Unexpected end of JSON input

suele significar un cuerpo vacío: un 204, una respuesta truncada o una llamada fetch que se te ha olvidado esperar.

Envuélvelo siempre y registra lo que realmente has recibido:

function parseOrThrow(text, url) {
  try {
    return JSON.parse(text);
  } catch (err) {
    throw new Error(
      `Failed to parse JSON from ${url}: ${err.message}. ` +
      `First 200 chars: ${text.slice(0, 200)}`
    );
  }
}

La parte «text.slice(0, 200)

» es la que importa. Un simple «SyntaxError

» te indica que el análisis ha fallado; los primeros 200 caracteres te dicen por qué, y normalmente la respuesta es visible de inmediato.

La función «reviver» y para qué sirve

El segundo argumento transforma los valores a medida que se analizan:

const data = JSON.parse(text, (key, value) => {
  if (key === "created") return new Date(value);
  return value;
});

Hay tres comportamientos que conviene conocer con precisión.

Se ejecuta en profundidad. Las propiedades anidadas se visitan antes que sus propiedades parentales, y la llamada final utiliza una cadena vacía como clave para el valor raíz. Así pues, cuando tu «reviver» detecta un objeto, sus hijos ya han sido procesados.

**Si devuelve «undefined

», se elimina la propiedad.** MDN: «Si la función «reviver

» devuelve «undefined

» (o no devuelve ningún valor), la propiedad se elimina del objeto». Esto es fácil de provocar por accidente: un «reviver» con una rama condicional que se sale del final devuelve «undefined

» y elimina claves de forma silenciosa. Devuelve siempre «value

» explícitamente como valor por defecto.

La raíz se puede sustituir por completo. «Si devuelves otro valor desde reviver

, ese valor sustituirá por completo al valor analizado originalmente. Esto se aplica incluso al valor raíz.»

El uso clásico es la recuperación de fechas, ya que JSON no tiene tipo de fecha:

const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;

const data = JSON.parse(text, (key, value) =>
  typeof value === "string" && ISO_DATE.test(value)
    ? new Date(value)
    : value
);

Ten cuidado con la coincidencia de patrones. Un reviver que convierta cualquier cosa con forma de fecha convertirá cadenas que querías mantener como cadenas —números de versión, identificadores, contenido de usuario que por casualidad se parezca a una marca de tiempo—. Es preferible realizar la coincidencia en el nombre de la clave cuando conozcas el esquema.

Ten en cuenta también el coste: el reviver se invoca una vez por cada valor del documento. En cargas útiles de gran tamaño, esto supone un factor real que afecta al rendimiento, y a menudo resulta más eficiente analizar el contenido sin formato y transformar posteriormente solo los campos que te interesen.

Acceso al texto fuente: context.source y JSON.rawJSON

Esta es la novedad más importante de los últimos tiempos y muchos desarrolladores aún no la conocen.

La propuesta de acceso al texto fuente de JSON.parse ha alcanzado la fase 4 del proceso del TC39, lo que significa que ha sido aprobada para su inclusión en el estándar. Resuelve un problema que la propia propuesta señala directamente: «La transformación entre valores de ECMAScript y texto JSON conlleva pérdidas».

El reviver recibe ahora un tercer argumento para los valores primitivos. MDN describe context.source

como «la cadena JSON original que representa este valor»; la propuesta es más precisa y lo denomina «texto fuente», «incluida la puntuación pero excluidos los espacios en blanco insignificantes al principio y al final», junto con index

, input

y keys

.

La importancia de esto queda patente con un ejemplo:

const text = '{"id": 9007199254740993}';

JSON.parse(text).id;
// 9007199254740992  — wrong, silently

JSON.parse(text, (key, value, context) =>
  key === "id" ? BigInt(context.source) : value
).id;
// 9007199254740993n  — correct

Sin acceso a la fuente, el número ya se ha convertido a un tipo «double» de JavaScript en el momento en que tu reviver lo ve. La precisión se ha perdido antes de que puedas intervenir. context.source

te proporciona los dígitos originales.

La propuesta también añade JSON.rawJSON()

, que te permite proporcionar texto JSON sin procesar que JSON.stringify

emite sin modificaciones — completando así el ciclo para que un BigInt leído desde JSON pueda volver a escribirse sin alteraciones.

JSON.stringify({ id: JSON.rawJSON("9007199254740993") });
// '{"id":9007199254740993}'

Comprueba la compatibilidad con tus entornos de destino antes de confiar en ello, pero esta es ahora la respuesta correcta al problema de los números grandes, en lugar de una solución provisional.

La precisión numérica es el error que acabarás lanzando

Merece una sección propia porque falla de forma silenciosa y los síntomas aparecen lejos de la causa.

Los números JSON se convierten en números de JavaScript, que son números de tipo «double» según el estándar IEEE 754. Los números enteros superiores a Number.MAX_SAFE_INTEGER —9.007.199.254.740.991— no pueden representarse todos con exactitud. MDN lo explica claramente: los números «pueden perder precisión en el proceso».

Lo peligroso es que no se produce ningún error. Se obtiene un número. Simplemente, no es el número que se envió.

JSON.parse('{"id": 12345678901234567890}').id;
// 12345678901234567000

Dónde se nota esto en la práctica:

  • Identificadores de bases de datos. Las claves primarias de enteros de 64 bits superan el rango seguro. Dos registros distintos pueden analizarse y dar como resultado el mismo número de JavaScript.
  • ID de tipo «Snowflake». Utilizados por varias plataformas grandes, suelen superar el límite.
  • Importes financieros en unidades menores. Grandes sumas en céntimos o satoshis.
  • Marcas de tiempo en nanosegundos. Cualquier valor de época en nanosegundos desde 1970 ya se encuentra fuera del rango seguro.

Tres medidas de mitigación, por orden de preferencia:

Solicitar cadenas de caracteres. Si controlas la API, serializa los identificadores grandes como cadenas de caracteres. Esta es la solución más limpia, funciona en todas partes y no cuesta nada. MDN recomienda precisamente esto: «Una forma de transferir números grandes sin pérdida de precisión es serializarlos como cadenas de caracteres y recuperarlos como BigInts».

Utilizar «context.source» con un reviver. Como en el caso anterior, cuando no controles al productor y tu entorno lo admita.

Utiliza una biblioteca JSON compatible con BigInt. Para entornos más antiguos, hay varios analizadores que lo admiten. Implica una dependencia y una cierta pérdida de rendimiento.

Lo que no funciona: comprobar si el número «parece correcto» tras el análisis. Para entonces, la información ya se ha perdido, y un valor erróneo es indistinguible de uno correcto.

«__proto__» y la contaminación del prototipo

MDN señala el único caso en el que JSON y JavaScript difieren en su significado: «El único caso en el que un fragmento de texto JSON representa un valor diferente al de la misma expresión de JavaScript es cuando se trata de la clave «\"__proto__\"».

En un literal de objeto de JavaScript, __proto__ establece el prototipo. En JSON.parse, crea una propiedad propia ordinaria:

const fromLiteral = { __proto__: { admin: true } };
fromLiteral.admin;             // true — prototype was set

const fromJson = JSON.parse('{"__proto__": {"admin": true}}');
fromJson.admin;                // undefined — plain own property
Object.hasOwn(fromJson, "__proto__");  // true

Por lo tanto, JSON.parse en sí mismo es seguro aquí; se trata de un comportamiento deliberado y correcto.

El peligro radica en lo que ocurre a continuación. Las vulnerabilidades por contaminación de prototipos se producen casi siempre cuando los datos analizados se fusionan con otro objeto mediante código que no filtra las claves peligrosas:

// Unsafe: a naive deep merge can walk into Object.prototype
function merge(target, source) {
  for (const key in source) {
    if (typeof source[key] === "object") {
      merge(target[key] ?? (target[key] = {}), source[key]);
    } else {
      target[key] = source[key];
    }
  }
}

Si se introduce una carga útil que contenga __proto__, se puede modificar Object.prototype para todo el programa. Medidas de defensa:

Filtra las claves peligrosas de forma explícita — __proto__, constructor, prototype — en cualquier fusión o asignación que maneje datos no fiables.

Utiliza Object.create(null) para los objetos que contengan claves no fiables, de modo que no haya ningún prototipo que se pueda contaminar.

Utiliza Map cuando realmente estés creando un almacén de clave-valor en lugar de un objeto estructurado.

Valida según un esquema. La respuesta general, y la que también detecta otros problemas. El análisis sintáctico y la validación son pasos independientes y ambos son necesarios.

JSON.parse frente a eval frente a Response.json()

Nunca utilices eval. Ejecuta código arbitrario, es más lento para este fin y acepta datos que no son JSON. No hay ningún caso en el que eval sea la herramienta adecuada para analizar JSON.

Response.json() es lo que necesitas cuando trabajas con fetch. Lee el cuerpo y lo analiza en un solo paso:

const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();

La comprobación res.ok es la parte que la gente suele omitir, y omitirla es la causa directa del error Unexpected token '<' mencionado en la introducción. fetch no rechaza los códigos de estado de error HTTP: un 403 o un 500 se resuelven con normalidad, y luego .json() intenta analizar una página de error. Comprueba primero el estado y, si quieres ser minucioso, consulta content-type:

const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status} from ${url}`);
const type = res.headers.get("content-type") ?? "";
if (!type.includes("application/json")) {
  const body = await res.text();
  throw new Error(`Expected JSON, got ${type}: ${body.slice(0, 200)}`);
}
const data = await res.json();

Ten en cuenta que Response.json() no admite un «reviver». Si necesitas uno, utiliza res.text() seguido de JSON.parse.

Las bibliotecas también difieren en este aspecto: algunas analizan automáticamente y lanzan una excepción ante códigos de estado que no sean 2xx, lo que cambia el lugar donde debe situarse el manejo de errores. Hemos comparado el comportamiento en axios vs fetch.

Analizar archivos JSON de gran tamaño sin que se cuelgue la página

JSON.parse es un proceso sincrónico y bloqueante. En el hilo principal, el análisis de un documento de gran tamaño bloquea la interfaz mientras dura la operación; esta es una causa habitual y fácil de diagnosticar de los tirones en la interfaz.

Orientación general: si es inferior a un megabyte, ni te lo plantees. Entre uno y diez, haz pruebas en tu dispositivo de destino más lento. Por encima de diez, busca otra solución.

Las opciones, en orden creciente de esfuerzo:

Trasládalo a un Web Worker. La solución real más sencilla. Analiza el documento fuera del hilo principal y envía el resultado de vuelta. Ten en cuenta que transferir el resultado conlleva su propio coste de clonación estructurada, por lo que esto resulta más útil cuando el worker también se encarga del procesamiento posterior.

Solicita menos datos. Paginación, selección de campos, un punto final más específico. Casi siempre es la respuesta correcta y casi siempre se pasa por alto porque requiere hablar con quien sea el responsable de la API.

Utilizar un analizador de streaming. Existen bibliotecas que emiten los valores a medida que llegan, en lugar de construir el árbol completo. Merece la pena cuando los documentos son realmente grandes o cuando solo se necesita parte de la carga útil.

Utilizar JSON delimitado por saltos de línea. En el caso de colecciones grandes, un documento JSON por línea resulta mucho más fácil de procesar de forma incremental, ya que cada línea se analiza de forma independiente y un flujo truncado sigue proporcionando registros completos. Si controlas el formato, este suele ser el mejor diseño; las ventajas e inconvenientes frente a otros formatos se tratan en nuestra comparación entre JSON y CSV.

Cuándo «JSON.parse» no es la herramienta adecuada

Cuando los datos de entrada no son JSON. JSON5, JSONC y los archivos de configuración con comentarios y comas finales requieren sus propios analizadores sintácticos. «JSON.parse» los rechazará correctamente y no debes intentar eliminar los comentarios con una expresión regular, ya que ese camino te llevará a crear un analizador sintáctico que no tenías intención de escribir.

Cuando necesitas validación, no solo análisis sintáctico. Un análisis sintáctico satisfactorio te indica que la sintaxis era válida. No dice nada sobre si los campos obligatorios existen o tienen los tipos correctos. Primero analiza y luego valida; una biblioteca de validación de esquemas es la herramienta adecuada para el segundo paso y try/catch no lo es.

Cuando los datos deben realizar un ciclo de ida y vuelta sin pérdidas. Los números enteros grandes, las fechas, undefined, las funciones, Map, Set, NaN, Infinity… ninguno de ellos sale intacto del JSON. Si el ciclo de ida y vuelta sin pérdidas es un requisito, utiliza deliberadamente context.source y JSON.rawJSON, o bien utiliza un formato diseñado para ello.

Cuando analizas la cadena en cada renderizado. Analizar la misma cadena repetidamente en una ruta de acceso frecuente es un puro desperdicio. Analízala una vez y guárdala en caché.

Cuando la cadena procede de una consulta que no has comprobado. El caso con el que empezamos, que repetimos porque es el más habitual de todos. Si JSON.parse lanza un error con los datos recuperados, el fallo está en la fuente. Comprueba el código de estado, comprueba el tipo de contenido, registra el cuerpo. El analizador te está diciendo la verdad.

Preguntas frecuentes

¿Qué hace JSON.parse?

Convierte una cadena con formato JSON en un valor de JavaScript: objeto, matriz, cadena, número, valor booleano o nulo. Admite una función «reviver» opcional que puede transformar cada valor a medida que se analiza. Lanza un error «SyntaxError» si la entrada no es JSON válido.

¿Por qué JSON.parse muestra el mensaje «Unexpected token '<'»?

Porque la cadena comienza con <, lo que significa que has recibido HTML en lugar de JSON —normalmente una página de error, una redirección de inicio de sesión o una página bloqueada—. El analizador funciona correctamente; el problema está en la solicitud. Registra los primeros 200 caracteres del cuerpo de la respuesta y, por lo general, la causa resultará obvia.

¿Cómo analizo JSON con números grandes en JavaScript?

Utiliza el argumento «context.source» del reviver para leer los dígitos originales y construir un «BigInt», ya que, para cuando el valor llega a tu reviver, ya ha perdido precisión. Mejor aún, si controlas la API, serializa los identificadores grandes como cadenas.

¿Qué es la función «reviver» en JSON.parse?

Un segundo argumento opcional que se invoca para cada par clave-valor, en profundidad, y que termina con la raíz bajo una clave de cadena vacía. Lo que devuelva sustituirá al valor; si devuelve «undefined», se elimina la propiedad. Su función habitual es convertir cadenas a tipos más ricos, como «Date».

¿Es seguro JSON.parse?

En cuanto a la ejecución de código, sí: a diferencia de eval, nunca ejecuta nada. También gestiona __proto__ de forma segura, creando una propiedad propia simple en lugar de establecer el prototipo. El riesgo reside en lo que se haga después: fusionar datos analizados no fiables con otros objetos sin filtrar __proto__ y constructor es lo que provoca la contaminación del prototipo.

¿Cuál es la diferencia entre JSON.parse y Response.json()?

Response.json() lee el cuerpo de una respuesta de «fetch» y lo analiza en un solo paso, y no acepta un «reviver». JSON.parse funciona con una cadena que ya tienes. Ten en cuenta que fetch no rechaza los errores HTTP, así que comprueba res.ok antes de llamar a .json() o acabarás analizando una página de error.

¿Puede JSON.parse gestionar comentarios o comas finales?

No. Ambos son JSON no válido y ambos lanzan un error de «SyntaxError». Si tu entrada los contiene, se trata de JSON5 o JSONC, y necesita un analizador para ese formato en lugar de una expresión regular que los elimine.

¿JSON.parse bloquea el hilo principal?

Sí, es sincrónico. Para documentos de menos de un megabyte esto es irrelevante; para cargas útiles grandes, provoca un bloqueo perceptible. Traslada el análisis a un Web Worker, solicita menos datos o utiliza un analizador de streaming.

Conclusión: «

JSON.parse» tiene una firma de dos parámetros y una profundidad sorprendente. Lo que hay que tener en cuenta son aquellos casos en los que el error pasa desapercibido, en lugar de los que se manifiestan de forma evidente.

La precisión numérica es el problema más grave: los números enteros grandes se corrompen de forma silenciosa, no se lanza ningún error y el valor incorrecto es indistinguible del correcto hasta que algo más adelante falla. La solución consiste en utilizar identificadores codificados como cadenas en el código fuente o el argumento context.source del «reviver», que ahora se encuentra en la fase 4 y forma parte del estándar.

El reviver merece un uso más extendido del que tiene, sobre todo para las fechas, con la salvedad de que, si se olvida devolver un value en la ruta predeterminada, se eliminan propiedades de forma silenciosa. Y la contaminación de prototipos no es en absoluto un problema de JSON.parse —la función gestiona __proto__ correctamente—, pero sí lo es para lo que sea que fusione el resultado, lo cual es lo suficientemente relevante como para que importe.

Todo lo demás se reduce a un hábito: cuando falle el análisis de los datos que has obtenido, registra el cuerpo sin procesar antes de cambiar ningún código. El mensaje de error casi siempre contiene la respuesta, y suele ser que, para empezar, nunca recibiste JSON.