Geonode logo
Geonode Team

Geonode Team

Actualizado: 7 de octubre de 2026

Publicado: 2 de septiembre de 2026

Cómo leer un archivo JSON en JavaScript

«Leer un archivo JSON en JavaScript» significa tres cosas diferentes dependiendo de dónde se ejecute el código, y los métodos no son compatibles entre sí. En Node se lee desde el disco. En un navegador, se recupera a través de la red o se acepta un archivo elegido por el usuario. Y en ambos casos, ahora existe una sintaxis de importación estándar que la mayoría de la gente aún no ha adoptado. Esta guía abarca todas estas situaciones, además del manejo de errores que convierte un fallo confuso en uno evidente.

Una breve nota sobre por qué una empresa de proxies escribe esto: somos Geonode, y el error JSON más habitual que nos comunican nuestros clientes es «Unexpected token '<'» en los datos que han obtenido. Esto significa que la respuesta era HTML —una página de error, una redirección de inicio de sesión o una página de bloqueo— y que el analizador indica correctamente que no se le ha proporcionado JSON. Antes de modificar ningún código, registra los primeros 200 caracteres de lo que has recibido. Casi todo lo demás que se explica en este artículo da por hecho que el archivo es realmente JSON, y esa suposición es la que falla con mayor frecuencia.

En Node: Lectura desde el disco

Tres métodos, y uno de ellos es la solución más moderna.

**fs/promises

, la vía estándar:**

import { readFile } from "node:fs/promises";

const raw = await readFile("./data.json", "utf8");
const data = JSON.parse(raw);

La documentación de Node es muy clara respecto al argumento de codificación, y es importante: sin una codificación, readFile

«devuelve una promesa que se cumple con un objeto <Buffer>

que contiene el contenido del archivo»; con una, «se cumple con un <string>

». JSON.parse

acepta un Buffer convirtiéndolo a una cadena, por lo que omitir la codificación suele funcionar y supone realizar una conversión adicional sin motivo. Pasa "utf8"

.

También admite un AbortSignal

a través de la opción signal

, «lo que te permite abortar una operación readFile

en curso», algo útil cuando una lectura forma parte de una solicitud que puede cancelarse.

Sincrónico, para el código de inicio:

import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("./config.json", "utf8"));

El bloqueo está bien antes de que el servidor empiece a servir. No está bien dentro de un gestor de solicitudes, donde paraliza el bucle de eventos para todas las demás conexiones. Esa distinción es la regla fundamental.

**require

, solo en CommonJS:**

const data = require("./data.json");

Es conciso y tiene dos propiedades que la gente suele olvidar. Almacena en caché, por lo que un segundo require

de la misma ruta devuelve el mismo objeto sin volver a leer el archivo, lo que significa que editar el archivo en tiempo de ejecución no tiene ningún efecto. Además, no está disponible en los módulos ES.

Importación de atributos: el método estándar

La sintaxis a la que la mayoría de la gente aún no se ha adaptado, y la que se debe utilizar en el código nuevo.

import data from "./data.json" with { type: "json" };

O de forma dinámica:

const data = await import("./data.json", { with: { type: "json" } });

MDN lo registra como Baseline 2025, disponible desde abril de 2025 en los navegadores más recientes, y los entornos de ejecución que no son navegadores, como Node y Deno, se alinean con la semántica de los navegadores para los módulos JSON.

El atributo ``type: "json"`

no es meramente decorativo. MDN explica que «valida que un módulo se sirva con el tipo MIME ``application/json

», y que si el archivo «se sirve con cualquier tipo de medio distinto de ``application/json

`, la importación fallará».

Merece la pena citar el razonamiento de seguridad, ya que explica por qué el atributo es obligatorio y no opcional:

Si, por alguna razón (por ejemplo, si el servidor ha sido secuestrado o es falso), el tipo de medio en la respuesta del servidor se establece en text/javascript

(para código fuente de JavaScript), entonces el archivo se analizaría y se ejecutaría como código. Si el archivo «JSON» contuviera realmente código malicioso, la declaración import

ejecutaría involuntariamente código externo, lo que supondría una grave amenaza.

Una nota sobre la migración: una propuesta anterior utilizaba la palabra clave «assert

» en lugar de «with

». MDN señala esto como un cambio que rompe la compatibilidad: las implementaciones que utilizan «assert

» «ya no son compatibles». Si encuentras «assert { type: "json" }

» en código antiguo o en un tutorial, es necesario actualizarlo.

En el navegador: recuperación de JSON

El caso más habitual, y aquel que esconde una trampa.

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

La comprobación «res.ok» no es opcional, y omitirla es la causa directa del error que se menciona en la introducción de este artículo. fetch no rechaza los códigos de estado de error HTTP: los códigos 403, 404 y 500 se resuelven con normalidad. Al llamar a .json(), se intenta analizar una página de error y se obtiene un error de sintaxis relacionado con un carácter «<» que no tiene nada que ver con tu JSON.

Para una versión que falle de forma útil:

async function fetchJson(url) {
  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)}`);
  }
  return res.json();
}

Dos líneas de comprobación convierten un error de análisis opaco en un mensaje que indica el estado, el tipo de contenido y lo que realmente se ha recibido.

Ten en cuenta también que Response.json() no acepta una función «reviver». Si necesitas una —para la conversión de fechas o para manejar números enteros grandes—, utiliza res.text() seguido de JSON.parse. Hemos tratado los «revivers» y el problema de la precisión en nuestra guía sobre JSON.parse.

En el navegador: un archivo elegido por el usuario

Para un archivo seleccionado del equipo del usuario, utiliza la API de archivos.

<input type="file" id="picker" accept="application/json">
document.getElementById("picker").addEventListener("change", async e => {
  const file = e.target.files[0];
  if (!file) return;
  try {
    const data = JSON.parse(await file.text());
    console.log(data);
  } catch (err) {
    console.error(`Could not parse ${file.name}: ${err.message}`);
  }
});

File.text() devuelve una promesa que se resuelve con el contenido en forma de cadena, lo cual es considerablemente más limpio que el antiguo FileReader con sus controladores de eventos. FileReader sigue siendo lo que necesitas si quieres eventos de progreso en un archivo muy grande.

Hay dos cosas que debes recordar. Los navegadores no pueden leer rutas locales arbitrarias: el usuario debe seleccionar el archivo, y eso es una restricción de seguridad deliberada, más que una limitación que se pueda eludir. Además, una extensión .json no garantiza nada sobre el contenido, por lo que try /catch es lo que realmente realiza el trabajo.

La función de arrastrar y soltar utiliza los mismos objetos File , obtenidos de event.dataTransfer.files .

Gestión de errores que te aporta información

El hábito que marca la diferencia entre un problema de cinco minutos y uno de una hora.

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

Lo importante es el fragmento. JSON.parse Los errores indican una posición y un carácter; la entrada real te explica el motivo. Tres firmas cubren la mayoría de los casos:

Unexpected token '<' — el contenido es HTML. Una página de error, una redirección de inicio de sesión o un listado de directorios.

Unexpected end of JSON input — la entrada está vacía o truncada. Una respuesta 204, un archivo que no se ha podido escribir por completo o una solicitud de obtención que te has olvidado de esperar.

Unexpected token '}' en una posición plausible — JSON con un formato realmente incorrecto, a menudo una coma al final. JSON las prohíbe, aunque JavaScript las permita.

En la lectura de archivos, distingue entre el error de lectura y el error de análisis. ENOENT significa que el archivo no existe, lo cual es un problema distinto al de un contenido no válido y merece un mensaje diferente.

Validación de lo que lees

El análisis sintáctico se ha realizado con éxito. Esto solo indica que la sintaxis era válida, pero no dice nada en absoluto sobre si los datos tienen la estructura que espera tu código; y la diferencia entre ambos aspectos es precisamente donde se esconden una proporción sorprendente de fallos en producción.

El análisis sintáctico y la validación son pasos independientes. JSON.parse

devolverá sin problemas {"user": {"nmae": "Ada"}}

, aunque haya un error ortográfico en la clave, o un campo price

que contenga la cadena "n/a"

cuando se esperaba un número. Tu código fallará entonces en algún punto posterior, a varias funciones de distancia del problema real, con un error que indica un síntoma en lugar de la causa.

Para cualquier cosa que escape a tu control, valida el dato según un esquema. Hay varias bibliotecas que gestionan esto bien, y el patrón es el mismo independientemente de cuál elijas:

const Config = z.object({
  port: z.number().int().min(1).max(65535),
  host: z.string(),
  retries: z.number().int().default(3),
  features: z.array(z.string()).optional(),
});

const config = Config.parse(JSON.parse(await readFile("./config.json", "utf8")));

Ahora el error indica el campo, el tipo esperado y lo que se ha encontrado —lo cual marca la diferencia entre una corrección de cinco minutos y una tarde entera.

En casos sencillos, unas cuantas aserciones no cuestan nada:

const data = JSON.parse(raw);
if (!Array.isArray(data.items)) throw new Error("items must be an array");
if (data.items.length === 0) throw new Error("items is empty — check the source");

Esa segunda comprobación vale más de lo que parece. Un array vacío es JSON válido, se analiza correctamente y, muy a menudo, es un síntoma más que un resultado legítimo: una API que no ha devuelto nada porque un filtro era incorrecto, o un rastreo que ha tenido éxito en una página que había cambiado.

Desconfía de los números que no hayas generado tú mismo. Los números JSON se convierten en valores de tipo «double» de JavaScript, por lo que los enteros superiores a Number.MAX_SAFE_INTEGER

pierden precisión de forma silenciosa, sin que se produzca ningún error en ningún momento. Los identificadores suelen ser las víctimas habituales: dos registros distintos de una base de datos pueden analizarse y dar el mismo valor. Si un campo es un identificador en lugar de una cantidad, debería ser una cadena en el JSON; y si no controlas al generador, el argumento ``context.source`

` del recuperador te proporciona los dígitos originales.

Y valida en el punto de entrada, una sola vez. Comprobar la estructura de los datos en el momento en que entran en tu programa significa que todo lo que viene después puede dar por hecho que son correctos. Realizar comprobaciones preventivas en veinte lugares supone tener que actualizar veinte lugares y no haber un único punto donde quede registrado el contrato.

Archivos grandes: «

JSON.parse

» es un proceso síncrono y necesita tener todo el documento en memoria. Ambas cosas se convierten en un problema a medida que los archivos crecen.

Orientación aproximada. Si es inferior a un megabyte, ni te lo plantees. Entre uno y diez, evalúa la situación —especialmente en el hilo principal de un navegador, donde el análisis bloquea la visualización y provoca tirones visibles—. Por encima de diez megabytes, o de aproximadamente una décima parte de tu memoria disponible, busca otra solución.

Sácalo del hilo principal. En un navegador, un Web Worker analiza el documento sin que se cuelgue la interfaz. En Node, un hilo de trabajo hace lo mismo con el bucle de eventos.

Utiliza JSON delimitado por saltos de línea. Se trata de una solución estructural, más que de un truco. Un documento JSON por línea significa que procesas un archivo de cualquier tamaño con un consumo de memoria constante, cada línea se analiza de forma independiente y un archivo truncado sigue proporcionando todos los registros completos:

import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";

const rl = createInterface({ input: createReadStream("./data.jsonl") });
for await (const line of rl) {
  if (line.trim()) handle(JSON.parse(line));
}

Si controlas el formato, este es el mejor diseño para cualquier cosa que se vaya añadiendo con el tiempo —y la razón por la que un trabajo de recopilación que se cuelga deja un archivo utilizable en lugar de uno imposible de analizar.

Utiliza un analizador de flujo continuo cuando el formato sea una única matriz grande que no puedas modificar. Varias bibliotecas emiten los valores a medida que llegan, en lugar de construir el árbol completo.

O pide menos. 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.

Escribir JSON de nuevo

La otra cara de la moneda, en pocas palabras, ya que suele ser la siguiente pregunta.

import { writeFile } from "node:fs/promises";
await writeFile("./out.json", JSON.stringify(data, null, 2), "utf8");

Los argumentos «null, 2

» generan una salida con sangría, lo cual es más importante de lo que parece: un archivo que vaya a ser leído por una persona o comparado en un sistema de control de versiones debe estar formateado, mientras que uno que se vaya a transmitir no debe estarlo.

Hay tres cosas que no sobreviven a ``JSON.stringify`

y provocan una pérdida silenciosa de datos en lugar de errores. Los valores ``undefined

y las funciones se eliminan por completo de los objetos y se convierten en ``null

dentro de los arrays. Los objetos ``Date

se convierten en cadenas ISO, por lo que no vuelven a ser fechas sin un «reviver». Y ``BigInt

` lanza un error directamente; el enfoque estándar es serializar los enteros grandes como cadenas.

Para añadir elementos, escribe JSON delimitado por saltos de línea en lugar de reescribir un array:

import { appendFile } from "node:fs/promises";
await appendFile("./log.jsonl", JSON.stringify(record) + "\n", "utf8");

Añadir elementos a un array JSON requiere leerlo, analizarlo, añadir elementos y reescribir todo el archivo, lo cual es costoso y corrompe el archivo si el proceso se interrumpe a mitad de la escritura.

Preguntas relacionadas

¿Cómo se lee un archivo JSON en Node.js?

const data = JSON.parse(await readFile("./data.json", "utf8")) utilizando node:fs/promises. O bien, utiliza la sintaxis de importación estándar: import data from "./data.json" with { type: "json" }, que es la norma de referencia a partir de 2025 y funciona tanto en Node como en los navegadores.

¿Puede JavaScript leer un archivo local en el navegador?

No por ruta. Los navegadores no pueden abrir archivos locales arbitrarios, lo cual es una restricción de seguridad deliberada. El usuario debe seleccionar un archivo mediante un <input type="file"> o arrastrar y soltar; a continuación, File.text() te muestra el contenido.

¿Qué hace import ... with { type: "json" }?

Importa un archivo JSON como módulo al tiempo que comprueba que el servidor lo haya enviado con el tipo MIME application/json. Sin esa comprobación, un archivo enviado como text/javascript se analizaría y se ejecutaría como código, lo cual es el problema de seguridad que este atributo pretende evitar.

¿Por qué aparece el mensaje «Unexpected token '<'» al leer JSON?

Porque el contenido comienza por <, lo que significa que has recibido HTML en lugar de JSON —normalmente una página de error o una redirección de inicio de sesión—. Con fetch, la causa es casi siempre que falta una comprobación de res.ok, ya que fetch no rechaza los estados de error HTTP.

¿Debería usar «require» o «import» para JSON?

Utiliza «import ... with { type: "json" }» en el código nuevo, ya que es el estándar y funciona en módulos ES. «require» es exclusivo de CommonJS y almacena el resultado en caché, por lo que un archivo editado en tiempo de ejecución no se volverá a leer. Ninguna de las dos opciones es adecuada para archivos que cambian mientras se ejecuta el programa; para esos casos, utiliza «readFile».

¿Cómo puedo leer un archivo JSON muy grande?

Traslada el análisis fuera del hilo principal utilizando un «worker», o reestructura los datos como JSON delimitado por saltos de línea para que cada línea se analice de forma independiente en memoria constante. Para un array grande que no se modifique, utiliza un analizador de flujo continuo. Y plantéate si puedes solicitar menos datos desde el principio.

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

Response.json() lee el cuerpo de una solicitud y lo analiza en un solo paso, y no admite una función de reactivación. JSON.parse trabaja con una cadena que ya tienes y sí la admite. Si necesitas una función de reactivación —para fechas o números enteros grandes—, utiliza res.text() seguido de JSON.parse.

¿Cómo gestiono un archivo JSON que podría no existir?

Detecta el error de lectura por separado del error de análisis. En Node, un código de error ENOENT significa que el archivo no existe, lo que normalmente requiere un valor por defecto en lugar de un error, mientras que un SyntaxError significa que el archivo existe pero su contenido es incorrecto.

Conclusión

El método depende del entorno, y la solución actual es más uniforme de lo que solía ser. «import data from "./data.json" with { type: "json" }» funciona tanto en Node como en los navegadores, es «Baseline» a partir de 2025 e incluye una comprobación del tipo MIME que existe por motivos de seguridad reales y no solo por formalidad.

Para los archivos que cambian mientras se ejecuta el programa, léelos explícitamente: readFile con una codificación "utf8" en Node, fetch con una comprobación res.ok en un navegador, y File.text() para algo que haya elegido el usuario. Esa comprobación res.ok es la línea de mayor valor de este artículo, ya que omitirla es la causa directa del error JSON más común que existe.

Cuando algo falle, registra los primeros 200 caracteres de la entrada antes de tocar ningún código. «Unexpected token '<'» significa HTML, «Unexpected end of JSON input» significa que está vacío o truncado, y ambos se resuelven analizando lo que realmente ha llegado, en lugar de basarse en conjeturas sobre el analizador sintáctico.

Y si los archivos se están volviendo demasiado grandes, la solución estructural es utilizar JSON delimitado por saltos de línea en lugar de recurrir a un equipo más potente. Un documento por línea se procesa en memoria constante, se añade de forma segura y sobrevive a una escritura interrumpida con todos los registros completos intactos.