JSON_EXTRACT

La función JSON_EXTRACT es probablemente la función JSON que más usarás en MySQL. Permite extraer uno o varios valores de un documento JSON usando expresiones JSON Path. Cada vez que necesites leer un dato almacenado dentro de una columna JSON, ya sea para mostrarlo en el resultado, filtrarlo en un WHERE o usarlo en un cálculo, JSON_EXTRACT es la herramienta fundamental. MySQL además ofrece dos operadores abreviados (-> y ->>) que simplifican la sintaxis en los casos más comunes.

Sintaxis

JSON_EXTRACT(documento_json, ruta [, ruta2, ruta3, ...])

El primer argumento es el documento JSON (una columna de tipo JSON o una cadena JSON válida). Los siguientes argumentos son una o más expresiones JSON Path que indican qué valores extraer. Si proporcionas una sola ruta, el resultado es el valor encontrado. Si proporcionas varias rutas, el resultado es un array JSON con todos los valores encontrados.

Comportamiento básico

Supongamos una tabla de productos con atributos almacenados en JSON:

CREATE TABLE productos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(200) NOT NULL,
    detalles JSON
);
 
INSERT INTO productos (nombre, detalles) VALUES
('MacBook Air M3', '{"marca": "Apple", "precio": 1399.00, "color": "Medianoche", "specs": {"ram": "16GB", "ssd": "512GB"}}'),
('Galaxy S24 Ultra', '{"marca": "Samsung", "precio": 1459.99, "color": "Titanio", "specs": {"ram": "12GB", "pantalla": "6.8\""}}'),
('ThinkPad X1', '{"marca": "Lenovo", "precio": 1549.00, "color": "Negro", "specs": {"ram": "32GB", "ssd": "1TB"}}');

Extraer un valor simple:

SELECT nombre, JSON_EXTRACT(detalles, '$.marca') AS marca
FROM productos;
nombremarca
MacBook Air M3"Apple"
Galaxy S24 Ultra"Samsung"
ThinkPad X1"Lenovo"

Observa que el resultado viene entrecomillado: "Apple" en lugar de Apple. Esto se debe a que JSON_EXTRACT devuelve un valor de tipo JSON, y las cadenas JSON llevan comillas. Esto es importante entenderlo para evitar problemas en comparaciones.

El operador -> (alias de JSON_EXTRACT)

MySQL ofrece el operador -> como una forma abreviada de JSON_EXTRACT. Las dos expresiones siguientes son exactamente equivalentes:

-- Forma larga
SELECT JSON_EXTRACT(detalles, '$.marca') FROM productos;
 
-- Forma abreviada con ->
SELECT detalles->'$.marca' FROM productos;

Ambas devuelven el mismo resultado: el valor JSON entrecomillado. El operador -> solo funciona con columnas de tabla, no con cadenas literales.

El operador ->> (extracción sin comillas)

El operador ->> combina JSON_EXTRACT con JSON_UNQUOTE. Extrae el valor y le quita las comillas JSON, devolviendo una cadena SQL normal:

SELECT nombre,
       detalles->'$.marca' AS con_comillas,
       detalles->>'$.marca' AS sin_comillas
FROM productos;
nombrecon_comillassin_comillas
MacBook Air M3"Apple"Apple
Galaxy S24 Ultra"Samsung"Samsung
ThinkPad X1"Lenovo"Lenovo

La diferencia es crucial cuando necesitas comparar valores. Si usas -> en un WHERE, debes comparar con la cadena JSON entrecomillada:

-- Con -> necesitas comillas JSON dentro de la cadena SQL
SELECT nombre FROM productos WHERE detalles->'$.marca' = '"Apple"';
 
-- Con ->> comparas directamente con la cadena SQL
SELECT nombre FROM productos WHERE detalles->>'$.marca' = 'Apple';

La segunda forma es mucho más intuitiva y menos propensa a errores. Como regla general, usa ->> cuando necesites comparar o mostrar valores de texto, y -> cuando necesites preservar el tipo JSON del resultado (por ejemplo, para pasar a otra función JSON).

Extraer valores anidados

Para acceder a valores dentro de objetos anidados, encadena los accesos con puntos en la ruta:

SELECT nombre,
       detalles->>'$.specs.ram' AS ram,
       detalles->>'$.specs.ssd' AS ssd
FROM productos;
nombreramssd
MacBook Air M316GB512GB
Galaxy S24 Ultra12GBNULL
ThinkPad X132GB1TB

El Galaxy S24 Ultra no tiene la clave ssd en sus especificaciones, por lo que la extracción devuelve NULL.

Extraer múltiples rutas a la vez

Cuando proporcionas varias rutas, JSON_EXTRACT devuelve un array JSON con todos los valores encontrados:

SELECT nombre,
       JSON_EXTRACT(detalles, '$.marca', '$.color', '$.precio') AS resumen
FROM productos;
nombreresumen
MacBook Air M3["Apple", "Medianoche", 1399.00]
Galaxy S24 Ultra["Samsung", "Titanio", 1459.99]
ThinkPad X1["Lenovo", "Negro", 1549.00]

Esto es útil cuando necesitas extraer varios valores en una sola operación, aunque en la práctica es más habitual extraer cada valor en su propia columna.

Caso práctico: filtrar y ordenar por valores JSON

Una de las aplicaciones más frecuentes es usar valores extraídos del JSON para filtrar y ordenar resultados:

CREATE TABLE pedidos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    cliente VARCHAR(100),
    datos JSON
);
 
INSERT INTO pedidos (cliente, datos) VALUES
('Laura M.', '{"total": 259.98, "items": 3, "envio": {"tipo": "express", "coste": 9.99}, "cupon": "VERANO10"}'),
('Carlos F.', '{"total": 89.99, "items": 1, "envio": {"tipo": "estandar", "coste": 4.99}, "cupon": null}'),
('Elena R.', '{"total": 450.00, "items": 5, "envio": {"tipo": "express", "coste": 0}, "cupon": "PREMIUM"}'),
('Miguel T.', '{"total": 134.97, "items": 2, "envio": {"tipo": "estandar", "coste": 4.99}, "cupon": null}');

Filtrar pedidos con envío express y total superior a 200:

SELECT cliente,
       datos->>'$.total' AS total,
       datos->>'$.envio.tipo' AS tipo_envio,
       datos->>'$.cupon' AS cupon
FROM pedidos
WHERE datos->>'$.envio.tipo' = 'express'
  AND CAST(datos->>'$.total' AS DECIMAL(10,2)) > 200;
clientetotaltipo_enviocupon
Laura M.259.98expressVERANO10
Elena R.450.00expressPREMIUM

Observa el uso de CAST para la comparación numérica. El operador ->> devuelve cadenas, así que para comparaciones numéricas debes convertir explícitamente el tipo.

Caso práctico: extraer elementos de arrays

JSON_EXTRACT también funciona con arrays JSON:

CREATE TABLE recetas (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(200),
    datos JSON
);
 
INSERT INTO recetas (nombre, datos) VALUES
('Paella valenciana', '{"tiempo_minutos": 45, "porciones": 4, "ingredientes": ["arroz", "judía verde", "garrofón", "pollo", "conejo", "azafrán", "tomate", "aceite"]}'),
('Tortilla española', '{"tiempo_minutos": 25, "porciones": 6, "ingredientes": ["huevos", "patatas", "cebolla", "aceite", "sal"]}');
SELECT nombre,
       datos->>'$.ingredientes[0]' AS ingrediente_principal,
       datos->>'$.ingredientes[1]' AS segundo_ingrediente,
       datos->'$.ingredientes' AS todos
FROM recetas;
nombreingrediente_principalsegundo_ingredientetodos
Paella valencianaarrozjudía verde["arroz", "judía verde", "garrofón", "pollo", "conejo", "azafrán", "tomate", "aceite"]
Tortilla españolahuevospatatas["huevos", "patatas", "cebolla", "aceite", "sal"]

Para la columna todos usamos -> (sin las comillas extra) porque queremos el array JSON completo, no una cadena.

Manejo de NULL

JSON_EXTRACT devuelve NULL en dos situaciones: cuando la columna JSON es NULL y cuando la ruta no existe en el documento:

INSERT INTO productos (nombre, detalles) VALUES
('Producto sin detalles', NULL),
('Producto parcial', '{"marca": "Genérico"}');
SELECT nombre,
       detalles->>'$.marca' AS marca,
       detalles->>'$.color' AS color
FROM productos
WHERE id > 3;
nombremarcacolor
Producto sin detallesNULLNULL
Producto parcialGenéricoNULL

En el primer caso, ambas extracciones devuelven NULL porque la columna entera es NULL. En el segundo, marca devuelve un valor pero color devuelve NULL porque esa clave no existe en el documento. Este comportamiento seguro permite trabajar con documentos de estructura variable sin riesgo de errores.

Combinación con otras funciones

Puedes usar JSON_EXTRACT dentro de expresiones y combinarlo con funciones SQL regulares:

SELECT nombre,
       UPPER(detalles->>'$.marca') AS marca_mayus,
       CONCAT(detalles->>'$.specs.ram', ' RAM') AS memoria,
       ROUND(CAST(detalles->>'$.precio' AS DECIMAL(10,2)) * 1.21, 2) AS precio_con_iva
FROM productos
WHERE detalles IS NOT NULL
  AND detalles->>'$.precio' IS NOT NULL;
nombremarca_mayusmemoriaprecio_con_iva
MacBook Air M3APPLE16GB RAM1692.69
Galaxy S24 UltraSAMSUNG12GB RAM1765.59
ThinkPad X1LENOVO32GB RAM1874.29

Los valores extraídos con ->> son cadenas SQL normales que puedes manipular con cualquier función de cadenas, fechas o números (aplicando las conversiones de tipo necesarias).

En el siguiente artículo veremos JSON_VALUE como alternativa tipada para extraer valores escalares.

Escrito por Eduardo Lázaro