PostgreSQL tiene soporte nativo de JSON desde hace años, pero con dos tipos distintos: JSON y JSONB. La diferencia no es trivial y elige uno u otro condiciona bastante lo que puedes hacer con esa columna. Además, hay que saber cuándo tiene sentido guardar algo en JSON dentro de una base de datos relacional y cuándo es simplemente añadir complejidad innecesaria.
JSON vs JSONB: qué cambia por dentro
JSON guarda el texto tal cual lo recibes. Si insertas {"b": 1, "a": 2}, lo guarda exactamente así, preservando el orden de las claves y los espacios en blanco. Es más rápido para insertar porque no procesa nada, pero cada vez que consultas, tiene que parsear el texto de nuevo.
JSONB descompone el JSON en una representación binaria interna al insertar. Esto hace que la inserción sea algo más lenta, pero las consultas son bastante más rápidas porque no hay que parsear. Además, JSONB normaliza el JSON: elimina espacios redundantes, reordena las claves alfabéticamente y elimina claves duplicadas (solo queda la última).
La regla práctica es sencilla: usa JSONB salvo que necesites preservar exactamente el formato del JSON original. JSONB soporta indexación con GIN, tiene más operadores disponibles y es más eficiente para queries.
Los operadores esenciales
-- Obtener valor por clave (devuelve JSONB)
SELECT metadata -> 'categoria' FROM productos;
-- Obtener valor como texto (devuelve text)
SELECT metadata ->> 'nombre' FROM productos;
-- Acceso por path anidado
SELECT metadata #> '{direccion, ciudad}' FROM clientes;
SELECT metadata #>> '{direccion, ciudad}' FROM clientes; -- como texto
-- Contains: el JSONB izquierdo contiene al derecho?
SELECT * FROM productos WHERE metadata @> '{"activo": true}';
-- Key exists: existe la clave?
SELECT * FROM productos WHERE metadata ? 'precio_especial';
-- Cualquiera de estas claves existe?
SELECT * FROM productos WHERE metadata ?| ARRAY['descuento', 'oferta'];
-- Todas estas claves existen?
SELECT * FROM productos WHERE metadata ?& ARRAY['nombre', 'precio'];
Indexar JSONB con GIN
La gran ventaja de JSONB sobre JSON es que puedes crear un índice GIN que cubra toda la columna. Esto hace que los operadores @> y ? sean rápidos incluso en tablas con millones de filas.
-- Indice GIN sobre la columna completa
CREATE INDEX idx_metadata_gin ON productos USING GIN (metadata);
-- Ahora estas queries usan el indice:
SELECT * FROM productos WHERE metadata @> '{"categoria": "electronica"}';
SELECT * FROM productos WHERE metadata ? 'precio_especial';
Si solo necesitas buscar por una ruta concreta dentro del JSON, también puedes crear un índice sobre una expresión extraída:
-- Indice B-tree sobre un campo especifico del JSON CREATE INDEX idx_metadata_categoria ON productos ((metadata ->> 'categoria')); -- Esta query usa el indice anterior SELECT * FROM productos WHERE metadata ->> 'categoria' = 'electronica';
Funciones de desestructuración
Cuando necesitas convertir un objeto JSON en filas o acceder a arrays, estas funciones son muy útiles:
-- Expandir objeto JSONB a pares clave-valor SELECT clave, valor FROM productos, jsonb_each(metadata); -- Expandir array JSONB a filas individuales SELECT jsonb_array_elements(tags) AS tag FROM articulos; -- Expandir a texto directamente SELECT jsonb_array_elements_text(tags) AS tag FROM articulos;
Cuándo tiene sentido usar JSONB
JSONB es una buena opción cuando:
- Los datos tienen estructura variable: no todos los registros tienen los mismos campos (por ejemplo, metadatos de productos de diferentes categorías).
- La estructura puede cambiar con el tiempo y no quieres alterar el esquema con cada cambio.
- Recibes datos de APIs externas con estructuras distintas y quieres guardarlos sin transformarlos.
No tiene sentido usar JSONB cuando:
- Los datos tienen estructura fija y conocida: usa columnas relacionales normales, que son más eficientes y más fáciles de consultar.
- Necesitas restricciones de integridad (NOT NULL, CHECK, FK) sobre los campos internos del JSON: los constraints de PostgreSQL no funcionan sobre campos dentro de un JSONB.
- Las consultas siempre filtran o agregan por los mismos campos: una columna relacional con índice B-tree supera a un campo dentro de JSONB.
El soporte de JSONB en PostgreSQL está directamente relacionado con su uso como almacén de embeddings y datos de IA. Si te interesa esa dirección, el artículo sobre pgvector para búsqueda vectorial de esta misma serie va un paso más allá en ese sentido. Y para el análisis de datos semi-estructurados con Python, pandas y Polars ofrecen una forma muy cómoda de trabajar con este tipo de datos una vez extraídos de la base de datos.
Imagen: Pexels / Nemuel Sereti
