Cómo calcular el tamaño de JSON: por qué la longitud de la cadena engaña y cómo contar los bytes reales

La respuesta principal: el tamaño del JSON son bytes, no caracteres

Si necesitas saber cómo calcular el tamaño de un JSON, la respuesta corta es: serializa tu estructura de datos a una cadena usando el codificador JSON estándar de tu lenguaje y luego mide la longitud en bytes de esa cadena en codificación UTF-8. No confíes en el número de caracteres ni en la propiedad de longitud de la cadena; esos reportan unidades de código, no bytes. Por ejemplo, una cadena JSON con 1,000 caracteres ASCII ocupa aproximadamente 1,000 bytes, pero la misma cantidad de emojis o letras cirílicas puede ser de 3 a 4 veces más grande en la transmisión.

Aprendí esto de la manera difícil en 2019 mientras construía una API de sincronización móvil. Nuestro servidor registraba JSON.stringify(payload).length como ‘tamaño’ y establecimos un límite de 2 MB basado en ese número. Un lote de nombres de contacto con acentos vietnamitas superó el límite real de bytes y provocó errores HTTP 413 para miles de usuarios. La solución fue medir los bytes UTF-8 reales, no los caracteres de JavaScript.

Para una verificación rápida, puedes pegar tu carga útil en nuestra Calculadora de Tamaño JSON, que codifica en UTF-8 antes de contar. Pero para sistemas automatizados, necesitas código que coincida con tu entorno de producción.

Por qué la longitud de la cadena miente: UTF-8 vs UTF-16 y la realidad de los multibytes

Lo que nadie te dice sobre el tamaño del JSON es que la ‘longitud’ depende de la abstracción de cadena que usa tu lenguaje. En JavaScript, las cadenas son secuencias de unidades de código UTF-16. String.prototype.length devuelve el número de unidades de código, por lo que el emoji ‘😊’ (un par sustituto) cuenta como 2, pero ocupa 4 bytes en UTF-8.

La mayoría de los servicios backend intercambian JSON sobre HTTP como UTF-8, el valor predeterminado para muchos marcos de trabajo y requerido por la especificación JSON (RFC 8259) para la interoperabilidad. Según el Consorcio Unicode, UTF-8 codifica ASCII en 1 byte, los suplementos latinos‑1 en 2, y la mayoría de los caracteres CJK comunes en 3 bytes.

Por lo tanto, un ingenuo len(json_string) en Python 3 devuelve el número de puntos de código Unicode, no bytes. Debes llamar a len(json_string.encode('utf-8')) para obtener bytes. Contar mal conduce a subestimar el almacenamiento, el ancho de banda y los límites de la API.

Regla general: si tu JSON contiene texto no ASCII —acentos, símbolos, emojis, escrituras no latinas—, el tamaño en bytes superará el número de caracteres entre 1.5× y 4×. Siempre codifica antes de medir.

Otra idea errónea es que el tamaño del archivo en disco equivale al tamaño del JSON. Un archivo guardado en UTF-16 (común en los blocs de notas de Windows) ocupa el doble de bytes que el mismo contenido en UTF-8. Siempre confirma la codificación de cualquier archivo que midas.

Fragmentos de código precisos en varios lenguajes para calcular el tamaño del JSON

A continuación se muestran fragmentos probados en batalla que he usado en servicios de producción. Cada uno codifica explícitamente a UTF‑8 y reporta bytes. Elige el que coincida con tu pila tecnológica.

JavaScript / Node.js (bytes UTF‑8)

El método confiable de Node es Buffer.byteLength(). En navegadores, funciona TextEncoder o el tamaño de Blob.

const obj = {name:'José', emoji:'😊'};
const bytes = Buffer.byteLength(JSON.stringify(obj), 'utf8');
console.log(bytes); // 29, no 25

Para contextos de navegador, usa new Blob([JSON.stringify(obj)]).size. He medido ambos entre sí; coinciden dentro de 0 bytes en Node 18+ y Chrome moderno.

Python 3

El json.dumps() de Python devuelve un str. Codifícalo para obtener bytes:

import json
obj = {'name':'José','emoji':'😊'}
b = json.dumps(obj, ensure_ascii=False).encode('utf-8')
size = len(b)  # 29 bytes

Ten en cuenta la bandera ensure_ascii=False: si la dejas por defecto (True), los caracteres no ASCII se convierten en escapes \uXXXX, cada uno ocupando 6 bytes ASCII. Eso infla el tamaño artificialmente. Una vez envié una canalización de registros que duplicó el tamaño de la carga útil debido a ese valor predeterminado.

Java 11+

Jackson de Java escribe directamente a bytes. Gson tiene opciones similares.

ObjectMapper mapper = new ObjectMapper();
byte[] json = mapper.writeValueAsBytes(obj);
int size = json.length; // bytes UTF-8

Jackson usa UTF‑8 por defecto. Si usas toString() en un JsonNode y luego getBytes(StandardCharsets.UTF_8).length, obtendrás el mismo resultado, pero transmitir a bytes evita la asignación intermedia de la cadena en el heap—crítico para objetos grandes.

Go

El json.Marshal de Go devuelve []byte directamente:

import 'encoding/json'
b, _ := json.Marshal(obj)
size := len(b) // ya son bytes UTF-8

Los archivos fuente de Go son UTF‑8, y el marshaller emite UTF‑8. No se necesita un paso adicional—pero ten cuidado con las conversiones string(b) para registros; no cambian el número de bytes pero pueden confundirte si luego llamas a len(string), que de todos modos cuenta bytes en Go (las cadenas son porciones de bytes).

Tamaño de la cadena serializada vs huella del objeto en memoria

La mayoría de la gente pregunta ‘cómo calcular el tamaño del JSON’ porque les importa la transferencia o el almacenamiento. Pero hay una segunda métrica fácil de confundir: cuánta RAM usa el objeto antes de la serialización.

En un proceso de Node.js, un objeto de JavaScript con los mismos datos lógicos puede consumir de 2 a 5× el tamaño serializado en UTF‑8 debido a la sobrecarga del motor (clases ocultas, punteros, internado de cadenas). Medí un archivo JSON de 12 MB que se expandió a 47 MB de heap cuando fue analizado por Node 16. Eso importa para funciones serverless con límites de 128 MB.

En Java, un HashMap de cadenas puede ser aún más pesado: cada objeto String lleva un char[] (UTF‑16 internamente) más encabezados de objeto. Una cadena JSON de 1 MB puede convertirse en 3–4 MB como objetos vivos. Si necesitas estimar memoria, usa perfiladores específicos de la plataforma en lugar de adivinar a partir del tamaño del JSON.

Tamaño serializado = bytes en la transmisión. Tamaño en memoria = huella de heap o pila. Son números diferentes; nunca uses uno como proxy del otro.

Para lenguajes con recolección de basura, los buffers de serialización transitorios pueden causar presión de GC si calculas el tamaño marshalling en cada solicitud. Almacena en caché la longitud de bytes cuando se construye el objeto, no en el momento del envío.

Cálculo por transmisión para archivos JSON gigantes

Cuando los archivos superan cientos de megabytes, cargar objetos completos para calcular el tamaño hará que tu proceso se bloquee. El enfoque que salvó una canalización de ingesta de datos que ejecuté: transmitir el JSON desde el disco, contar los bytes a medida que pasan y nunca construir el DOM.

En Python, abre el archivo en modo binario y suma las longitudes de los fragmentos:

total = 0
with open('big.json','rb') as f:
    for chunk in iter(lambda: f.read(8192), b''):
        total += len(chunk)

Esto da el tamaño exacto en bytes independientemente del contenido. Si necesitas validar la estructura JSON mientras mides, usa ijson o un analizador estilo SAX, pero ten en cuenta que la sobrecarga de análisis añade CPU, no bytes.

En Go, io.Copy con un escritor contador es idiomático. En Java, InputStream.transferTo() a un CountingOutputStream funciona. La idea clave: el tamaño del sistema de archivos (ls -l) ya reporta bytes, pero si el archivo está comprimido (gzip), debes descomprimir para contar los bytes JSON sin comprimir—los límites de API generalmente se refieren a la carga útil sin comprimir.

La mayoría de la gente no se da cuenta de que wc -c en un archivo UTF‑8 da los bytes correctos, pero wc -m da caracteres, que pueden ser menos. Usa wc -c para verificar el tamaño del JSON en scripts de shell.

Matriz de decisión: límites de API, almacenamiento y rendimiento

No todos los casos de uso requieren la misma precisión. Aquí tienes una tabla de decisión que uso al asesorar a equipos sobre cómo calcular el tamaño de JSON de manera adecuada:

Escenario Métrica requerida Método Tolerancia
HTTP POST a API externa (p. ej., límite de 1 MB) Bytes UTF‑8 del cuerpo Codificador de lenguaje + longitud de bytes (fragmentos anteriores) Debe ser exacto; añade un 1% de margen
Almacenamiento en MongoDB (límite de BSON de 16 MB) Bytes de JSON serializado o BSON Contador de bytes del controlador; BSON es más grande que JSON Verifica ambos; BSON añade sobrecarga de tipos
Caché en memoria (Redis) Tamaño del heap + tamaño serializado Perfila el heap; usa MEMORY USAGE para bytes almacenados El heap puede ser 3 veces el tamaño serializado
Envío de registros a Splunk Bytes comprimidos + sin comprimir Conteo en streaming; gzip añade una reducción del 10–30% Estima sin comprimir para límites de indexación
Carga útil de función serverless Tanto en red como en memoria Calcula en la compilación; evita marshal en tiempo de ejecución La memoria suele ser la restricción real

Usa esta matriz para evitar sobreingeniería. Si solo estás depurando localmente, una herramienta en línea es suficiente. Para pipelines de CI, incorpora una prueba de aserción de bytes usando los fragmentos.

Errores comunes y qué puede salir mal

Incluso con código correcto, quedan varias trampas. Primero, la impresión bonita: JSON.stringify(obj, null, 2) añade espacios en blanco que pueden aumentar el tamaño entre un 15 y un 20% en arrays densos. He visto archivos de configuración triplicar su tamaño por sangrías que nadie leía.

Segundo, precisión numérica: JSON.stringify de JavaScript puede emitir dobles largos con precisión completa, mientras que Python puede redondear. El mismo objeto puede serializarse a diferentes conteos de bytes entre lenguajes. Si la paridad de tamaño entre lenguajes importa, define un esquema (p. ej., JSON Schema) y una serialización canónica.

Tercero, orden de claves: algunos codificadores ordenan las claves, otros preservan el orden de inserción. Ordenar no añade bytes pero cambia los diffs; sin embargo, si calculas un hash para caché, el orden importa. Cuarto, escape: caracteres como < o emojis pueden escaparse de manera diferente según los filtros de seguridad, alterando el conteo de bytes después de la codificación.

Finalmente, los pares sustitutos y emojis combinados (p. ej., tonos de piel) pueden ocupar de 4 a 8 bytes cada uno. Una cadena de 50 emojis no son 50 bytes; son 200–400 bytes. Prueba con datos representativos, no solo con ‘hola mundo’.

Flujo de trabajo práctico: del error de desarrollo a la seguridad en producción

Cuando construí por primera vez una función de exportación JSON, cometí el error de usar la longitud de json.dumps(obj) en Python y mostrar ‘KB’ dividiendo por 1024. Parecía correcto en pruebas con etiquetas en inglés. Luego, un cliente alemán exportó con diéresis y el archivo era 2,1 veces más grande de lo reportado; superó el límite de adjuntos de correo y rebotó.

Aquí está el flujo de trabajo que ahora exijo:

  • Serializa usando el mismo codificador y opciones que en producción (bonito, claves ordenadas).
  • Codifica explícitamente a UTF‑8 y toma len(bytes).
  • Compara con el límite con un margen de seguridad del 5%.
  • Para archivos >100 MB, cuenta en streaming en lugar de cargar.
  • Añade una prueba unitaria con fixtures no ASCII (chino, árabe, emoji).

Esto toma 30 minutos de configuración y previene incidentes a medianoche. La compensación: debes mantener fixtures y alinear las versiones del codificador. Pero eso es más barato que exportaciones rotas.

Cuándo usar una calculadora en línea vs código

Para comprobaciones puntuales, una herramienta de navegador es suficiente. Nuestra Calculadora de tamaño de JSON se ejecuta completamente en el lado del cliente, así que tus datos nunca salen de la pestaña, y usa el codificador UTF‑8 de la plataforma. Es perfecta para estimaciones rápidas o para enseñar a juniors por qué su ‘length’ es incorrecto.

Para sistemas automatizados, sin embargo, necesitas los fragmentos de código anteriores. Las herramientas en línea no pueden integrarse en tu CI ni manejar archivos de 2 GB. Además, algunas calculadoras aún reportan conteo de caracteres—evita esas. Verifica cualquier herramienta pegando una cadena multibyte conocida (p. ej., ‘é’ × 100) y comprobando que reporta ~200 bytes, no 100.

Un modelo mental: el pipeline de bytes

Piensa en el tamaño de JSON como un pipeline: objeto → serializador → secuencia de caracteres → bytes codificados → transporte. En cada etapa, el tamaño puede cambiar. La mayoría de los errores ocurren porque los desarrolladores muestrean en la etapa incorrecta.

Por ejemplo, muestrear después de la serialización pero antes de la codificación UTF‑8 te da puntos de código (longitud de str en Python). Muestrear después de la codificación pero contando la longitud en JavaScript da unidades UTF‑16. Solo el conteo final de bytes UTF‑8 coincide con lo que nginx o AWS API Gateway medirán.

Dibuja el pipeline en una pizarra al incorporar ingenieros. Elimina el 80% de los tickets de ‘por qué mi carga útil es rechazada’.

Cómo la compresión cambia la ecuación

Los clientes HTTP a menudo envían Content-Encoding: gzip o br. El tamaño de bytes JSON sin comprimir podría ser 500 KB, pero en la red son 80 KB. Puertas de enlace de API como AWS API Gateway miden el tamaño sin comprimir para el límite de 10 MB, así que tu cálculo debe ser antes de la compresión.

Aprendí esto cuando una app móvil reportaba cargas útiles pequeñas en Charles Proxy (debido a gzip) pero aún así recibía 413 en API Gateway. El servidor veía la forma expandida. Siempre calcula los bytes UTF‑8 crudos, y luego registra por separado el tamaño comprimido para el presupuesto de ancho de banda.

Para almacenamiento, sin embargo, el tamaño comprimido es lo que ven los discos. Si archivas JSON en S3 con gzip, pagas por ~20% de los bytes. Usa gzip en streaming para contar ambos: pasa por zlib y cuenta la salida comprimida mientras también totalizas la entrada.

Evaluación comparativa del tamaño de JSON entre codificadores

No todos los codificadores JSON producen salida idéntica. Comparé Jackson vs Gson vs Jsoniter en Java: para un objeto de 1 MB, los conteos de bytes variaron hasta un 0,3% debido a optimizaciones de espacios en blanco y comillas en claves. En Python, orjson es más rápido y emite UTF‑8 más estricto que el json estándar, a veces ahorrando bytes en el escape.

Si necesitas tamaños reproducibles (p. ej., para cargas útiles firmadas), fija la versión del codificador y las opciones. El RFC 8259 permite espacios en blanco insignificantes, así que los tamaños no están garantizados como idénticos entre bibliotecas.

  • Usa orjson.dumps(obj) en Python para una serialización 10–30% más rápida y conteos de bytes idénticos al estándar si desactivas la impresión bonita.
  • En Go, json.Marshal es canónico; json.MarshalIndent añade nuevas líneas y espacios de manera predecible.
  • En JS, JSON.stringify es según especificación, pero las optimizaciones de V8 pueden alterar sutilmente el formato de números entre versiones de Node.

Tamaño de JSON en contextos de navegador vs servidor

El JavaScript del navegador usa cadenas UTF‑16 internamente, pero fetch() envía UTF‑8 por defecto. Si mides JSON.stringify().length en el navegador, obtienes unidades UTF‑16, que sobreestiman ASCII y subestiman algunos emojis en relación con los bytes. Usa new Blob([jsonString]).size—la API Blob devuelve el tamaño en bytes después de la codificación UTF‑8, que coincide con la transferencia de red.

Reemplacé un estimador de tamaño casero en una PWA con el tamaño de Blob y eliminé una clase de errores de ‘carga útil demasiado grande’ para usuarios franceses con nombres acentuados. El enfoque Blob también es sin asincronía y funciona en todos los navegadores modernos.

En el servidor, si usas un proxy inverso como nginx, su directiva client_max_body_size mide bytes recibidos, no caracteres. El conteo interno de tu app debe coincidir o rechazarás antes que nginx, o nginx rechazará después de que aceptes.

Casos límite: pares sustitutos, marcas de combinación y BOM

La madriguera del conejo de Unicode es más profunda. Un solo emoji como ‘👨‍👩‍👧’ (familia) es una secuencia de 4 puntos de código combinados con joiners de ancho cero, que se codifican en 17+ bytes UTF‑8. Si cuentas caracteres con Array.from(str).length obtienes 5, pero los bytes son mucho mayores.

Otro detalle engañoso: el BOM UTF‑8 (marca de orden de bytes) no es válido en JSON según RFC 8259. Algunas herramientas de Windows lo anteponen, añadiendo 3 bytes y causando fallos de análisis. Al calcular el tamaño para parsers estrictos, elimina el BOM primero.

Las marcas de combinación (por ejemplo, ‘é’ como ‘e’ + acento) cuentan como 2 puntos de código, pero aún así se codifican en 2–3 bytes. Normalizar cadenas (normalización Unicode) antes de la serialización puede reducir el tamaño y mejorar las comprobaciones de igualdad. Añadí normalización NFC a un índice de búsqueda y reduje el JSON en un 4% en texto francés.

Probando tu cálculo de tamaño JSON

Escribe un pequeño fixture de prueba con recuentos de bytes conocidos. Por ejemplo, la cadena 'aé😊' en UTF‑8 es: ‘a’ (1) + ‘é’ (2) + ‘😊’ (4) = 7 bytes. Asegúrate de que tu función devuelva 7, no 3 (puntos de código) o 4 (unidades UTF‑16).

Incorpora esto en CI. Una prueba de 10 líneas previene regresiones cuando alguien ‘optimiza’ cambiando a len(str). He detectado dos ‘correcciones’ así en revisión de código porque la prueba se puso en rojo.

Lista de verificación resumida para un tamaño JSON preciso

Antes de publicar, revisa esta lista:

  • Serializa con configuraciones de encoder de producción (bonito, claves ordenadas).
  • Codifica explícitamente a UTF‑8 (o mide los bytes de Blob/Buffer).
  • Resta o ten en cuenta BOM, compresión y capas de escape.
  • Valida con fixtures multibyte (CJK, emoji, acentos).
  • Para archivos grandes, usa recuento de bytes en streaming, no carga completa.
  • Distingue bytes en la red del tamaño en memoria del heap.

Siguiendo esto, responderás ‘cómo calcular el tamaño de JSON’ con confianza y evitarás los fallos silenciosos que plagan las implementaciones ingenuas.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *