JSON: Guía Técnica Completa
JSON (JavaScript Object Notation) es un formato de intercambio de datos ligero y legible por humanos, especificado en RFC 8259 y ECMA-404. Aunque se originó como un subconjunto de JavaScript, JSON es hoy en día analizado universalmente por todos los lenguajes de programación y se ha convertido en el formato dominante para APIs REST, archivos de configuración, almacenes de documentos NoSQL y comunicación entre servicios.
Gramática y Tipos de Datos
La gramática completa de JSON es notablemente pequeña. Un texto JSON es un único valor, que es uno de:
| Tipo | Ejemplo | Notas |
|---|---|---|
string |
"hola mundo" |
Entre comillas dobles, codificado en UTF-8 |
number |
42, 3.14, -1e10 |
Sin NaN ni Infinity |
object |
{"clave": "valor"} |
Pares clave-valor sin orden |
array |
[1, 2, 3] |
Secuencia ordenada de valores |
true |
true |
Literal booleano |
false |
false |
Literal booleano |
null |
null |
Ausencia de valor |
Los objetos son cero o más pares clave-valor separados por comas, entre {}. Las claves deben ser strings. Las claves duplicadas producen comportamiento indefinido — la mayoría de los parsers usan silenciosamente el último valor. Los arrays son cero o más valores separados por comas, entre []. Se permiten valores de tipos mixtos.
Detalles de Codificación de Strings
Los strings JSON son secuencias de puntos de código Unicode entre comillas dobles. Las secuencias de escape permitidas son:
\" — comilla doble (U+0022)
\\ — barra inversa (U+005C)
\/ — barra diagonal (U+002F) — escape opcional
\b — retroceso (U+0008)
\f — avance de página (U+000C)
\n — nueva línea (U+000A)
\r — retorno de carro (U+000D)
\t — tabulador (U+0009)
\uXXXX — punto de código Unicode (4 dígitos hexadecimales)
Para caracteres fuera del Plano Multilingüe Básico (U+10000 y superiores), JSON usa pares sustitutos: dos secuencias \uXXXX que codifican un par sustituto UTF-16. Por ejemplo, U+1F600 (😀) se codifica como 😀. RFC 8259 requiere codificación UTF-8 para archivos/streams; el BOM está explícitamente prohibido.
Precisión de Números
Los números JSON no tienen límite de precisión explícito, pero IEEE 754 de doble precisión (la implementación común) pierde precisión más allá de 2⁵³ − 1 (9.007.199.254.740.991). Los enteros grandes de sistemas financieros o IDs de 64 bits deben transmitirse como strings para evitar redondeo silencioso.
Procesamiento JSON con Python
import json
from pathlib import Path
# Parsear string JSON → objeto Python
data = json.loads('{"nombre": "Alicia", "puntuaciones": [98, 87, 95], "activo": true}')
# data es un dict; true → True, null → None
print(data['nombre']) # Alicia
print(data['puntuaciones'][0]) # 98
print(data.get('activo')) # True
# Serializar objeto Python → string JSON
salida = json.dumps(data, indent=2, ensure_ascii=False, sort_keys=True)
# ensure_ascii=False permite que caracteres no-ASCII pasen sin escaping
# E/S de archivos
Path('datos.json').write_text(
json.dumps(data, ensure_ascii=False, indent=2),
encoding='utf-8'
)
cargado = json.loads(Path('datos.json').read_text(encoding='utf-8'))
# Codificador personalizado para tipos que json no maneja nativamente
import datetime, decimal
class CodificadorExtendido(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime.date, datetime.datetime)):
return obj.isoformat()
if isinstance(obj, decimal.Decimal):
return str(obj)
return super().default(obj)
resultado = json.dumps(
{'fecha': datetime.date.today(), 'precio': decimal.Decimal('9.99')},
cls=CodificadorExtendido
)
# '{"fecha": "2025-04-26", "precio": "9.99"}'
NDJSON / JSON Lines — Streaming de Grandes Conjuntos de Datos
NDJSON (JSON delimitado por nueva línea) almacena un valor JSON completo por línea, separados por \n. Esto permite el procesamiento en stream sin cargar todo el conjunto de datos en memoria — ideal para logs, flujos de eventos y exportaciones de datos de varios gigabytes.
import json
def leer_ndjson(ruta: str):
"""Generador: produce un objeto parseado por línea."""
with open(ruta, encoding='utf-8') as f:
for n_linea, linea in enumerate(f, 1):
linea = linea.strip()
if not linea:
continue
try:
yield json.loads(linea)
except json.JSONDecodeError as e:
print(f"Línea {n_linea}: error de parseo — {e}")
def escribir_ndjson(ruta: str, registros):
"""Escribe un iterable de objetos en NDJSON."""
with open(ruta, 'w', encoding='utf-8') as f:
for registro in registros:
f.write(json.dumps(registro, ensure_ascii=False) + '\n')
# Filtrar usuarios activos de un archivo de 10 millones de líneas sin agotar RAM
def usuarios_activos(ruta: str):
for registro in leer_ndjson(ruta):
if registro.get('estado') == 'activo':
yield registro
escribir_ndjson('activos.ndjson', usuarios_activos('todos.ndjson'))
Validación con JSON Schema
import jsonschema
esquema_usuario = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["id", "nombre", "email"],
"properties": {
"id": {"type": "integer", "minimum": 1},
"nombre": {"type": "string", "minLength": 1, "maxLength": 100},
"email": {"type": "string", "format": "email"},
"roles": {
"type": "array",
"items": {"type": "string", "enum": ["admin", "usuario", "invitado"]},
"uniqueItems": True
}
},
"additionalProperties": False
}
usuario_valido = {"id": 1, "nombre": "Alicia", "email": "alicia@ejemplo.com", "roles": ["admin"]}
jsonschema.validate(usuario_valido, esquema_usuario) # pasa silenciosamente
try:
jsonschema.validate({"id": "no-es-entero"}, esquema_usuario)
except jsonschema.ValidationError as e:
print(e.message)
JSON por Línea de Comandos con jq
# Imprimir con formato
cat datos.json | jq .
# Extraer campo
jq '.nombre' usuario.json
# Filtrar array
jq '[.usuarios[] | select(.activo == true)]' datos.json
# Transformar: construir nuevo objeto
jq '{id: .id, etiqueta: .nombre, contacto: .email}' usuario.json
# Calcular promedio
jq '[.puntuaciones[]] | add / length' datos.json
# Procesar NDJSON línea a línea
cat registros.ndjson | jq -c 'select(.estado == "error")'
# Combinar múltiples archivos JSON en un array
jq -s '.' archivo1.json archivo2.json
JSON vs Otros Formatos de Datos
| Formato | Legible | Tipado | Comentarios | Streaming | Esquema | Mejor para |
|---|---|---|---|---|---|---|
| JSON | Bueno | Parcial | No | NDJSON | JSON Schema | APIs, config, web |
| JSON5 | Excelente | Parcial | Sí | No | No | Config editada manualmente |
| YAML | Excelente | Sí | Sí | No | No | Config, Kubernetes |
| TOML | Excelente | Sí | Sí | No | No | Config de aplicaciones |
| XML | Verboso | No | Sí | SAX | XSD/DTD | Empresarial, SOAP |
| CSV | Bueno | No | No | Sí | No | Datos tabulares |
| Protocol Buffers | No (binario) | Sí | En .proto | Sí | .proto | RPC de alto rendimiento |
Errores Comunes
- No hay comas finales.
{"a": 1, "b": 2,}es JSON inválido. Usa JSON5 o HJSON si las necesitas. - No hay comentarios. JSON no tiene sintaxis de comentarios. Soluciones: una clave
"_comment"dedicada, JSON5 o YAML. - Las claves duplicadas tienen comportamiento indefinido. RFC 8259 dice que los parsers DEBERÍAN reportar un error pero la mayoría usan silenciosamente el último valor.
- Los enteros grandes pierden precisión en JS.
JSON.parse()de JavaScript usa doubles IEEE 754, redondeando silenciosamente enteros mayores de 2⁵³. Usa strings para IDs de 64 bits en el servidor. - El BOM UTF-8 rompe parsers. RFC 8259 lo prohíbe explícitamente. Asegúrate de que los editores guarden JSON como UTF-8 sin BOM.
Conversiones relacionadas
Conversiones de documento que siguen este tema: