JSON_SET

La función JSON_SET es la más flexible de las tres funciones de modificación de JSON en MySQL. Combina el comportamiento de JSON_INSERT y JSON_REPLACE: si la ruta existe, reemplaza el valor; si no existe, lo crea. Esto la convierte en la opción más práctica cuando no te importa si la clave es nueva o ya tenía un valor previo, que es el caso más habitual en la mayoría de aplicaciones.

Sintaxis

JSON_SET(documento_json, ruta, valor [, ruta2, valor2, ...])

El primer argumento es el documento JSON original. Los siguientes argumentos van en pares: una ruta JSON Path y el valor que quieres establecer en esa ruta. Puedes especificar múltiples pares para modificar o crear varios valores a la vez. La función devuelve el documento con todos los cambios aplicados.

Comportamiento básico

La clave de JSON_SET es que funciona tanto para claves existentes como para nuevas:

SELECT JSON_SET(
    '{"nombre": "Monitor 4K", "precio": 349.99}',
    '$.precio', 299.99,
    '$.stock', 75
) AS resultado;
resultado
{"nombre": "Monitor 4K", "precio": 299.99, "stock": 75}

La clave precio existía con valor 349.99, así que se reemplazó por 299.99. La clave stock no existía, así que se creó con valor 75. JSON_SET no distingue entre ambos casos: simplemente establece el valor en la ruta indicada.

Comparación: JSON_INSERT vs JSON_REPLACE vs JSON_SET

Estas tres funciones son idénticas en su sintaxis pero difieren en cuándo actúan. Es fundamental entender la diferencia para elegir la correcta:

SET @doc = '{"nombre": "Teclado", "precio": 89.99}';
 
SELECT
    JSON_INSERT(@doc, '$.precio', 999, '$.stock', 50)  AS con_insert,
    JSON_REPLACE(@doc, '$.precio', 999, '$.stock', 50) AS con_replace,
    JSON_SET(@doc, '$.precio', 999, '$.stock', 50)     AS con_set;
con_insertcon_replacecon_set
{"nombre": "Teclado", "precio": 89.99, "stock": 50}{"nombre": "Teclado", "precio": 999}{"nombre": "Teclado", "precio": 999, "stock": 50}

La tabla resume el comportamiento de cada función:

FunciónClave existeClave no existe
JSON_INSERTNo modificaCrea la clave
JSON_REPLACEModifica el valorNo hace nada
JSON_SETModifica el valorCrea la clave

JSON_SET es la más permisiva: siempre establece el valor, sin importar si la clave existía o no. Por eso es la más usada en la práctica.

Caso práctico: gestionar preferencias de usuario

Un escenario ideal para JSON_SET es la gestión de configuraciones, donde el usuario puede cambiar ajustes existentes o activar nuevos:

CREATE TABLE preferencias (
    usuario_id INT PRIMARY KEY,
    config JSON NOT NULL DEFAULT ('{}')
);
 
INSERT INTO preferencias VALUES
(1, '{"tema": "oscuro", "idioma": "es", "notificaciones": true}'),
(2, '{"tema": "claro", "idioma": "en"}'),
(3, '{}');

Actualizar la configuración del usuario 1 (cambiar tema existente y agregar nuevo ajuste):

UPDATE preferencias
SET config = JSON_SET(
    config,
    '$.tema', 'claro',
    '$.articulos_por_pagina', 25,
    '$.sidebar_colapsada', FALSE
)
WHERE usuario_id = 1;
 
SELECT config
FROM preferencias
WHERE usuario_id = 1;
config
{"tema": "claro", "idioma": "es", "notificaciones": true, "articulos_por_pagina": 25, "sidebar_colapsada": false}

El tema cambió de "oscuro" a "claro" (reemplazo), y se añadieron los nuevos ajustes articulos_por_pagina y sidebar_colapsada (inserción). Todo en una sola operación.

Caso práctico: actualizar datos de producto en e-commerce

En una tienda online, las actualizaciones de productos suelen mezclar cambios a datos existentes con información nueva:

CREATE TABLE catalogo (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(200) NOT NULL,
    datos JSON
);
 
INSERT INTO catalogo (nombre, datos) VALUES
('Samsung Galaxy S24', '{
    "marca": "Samsung",
    "precio": 899.99,
    "stock": 150,
    "especificaciones": {
        "pantalla": "6.2 pulgadas",
        "procesador": "Exynos 2400",
        "ram": "8GB"
    }
}');
-- Actualizar precio, reducir stock, cambiar procesador y agregar información nueva
UPDATE catalogo
SET datos = JSON_SET(
    datos,
    '$.precio', 849.99,
    '$.stock', 148,
    '$.especificaciones.procesador', 'Snapdragon 8 Gen 3',
    '$.especificaciones.almacenamiento', '256GB',
    '$.envio_gratuito', TRUE,
    '$.ultima_actualizacion', NOW()
)
WHERE id = 1;
 
SELECT nombre,
       datos->>'$.precio' AS precio,
       datos->>'$.stock' AS stock,
       datos->>'$.especificaciones.procesador' AS cpu,
       datos->>'$.especificaciones.almacenamiento' AS storage,
       datos->>'$.envio_gratuito' AS envio_gratis
FROM catalogo
WHERE id = 1;
nombrepreciostockcpustorageenvio_gratis
Samsung Galaxy S24849.99148Snapdragon 8 Gen 3256GBtrue

En una sola sentencia hemos actualizado datos existentes (precio, stock, procesador) y agregado datos nuevos (almacenamiento, envio_gratuito, ultima_actualizacion), incluyendo modificaciones dentro de objetos anidados.

Caso práctico: construir un documento JSON progresivamente

A veces es necesario construir un documento JSON paso a paso, empezando desde un objeto vacío:

INSERT INTO preferencias VALUES (4, '{}');
 
UPDATE preferencias
SET config = JSON_SET(config, '$.tema', 'sistema')
WHERE usuario_id = 4;
 
UPDATE preferencias
SET config = JSON_SET(config, '$.idioma', 'es', '$.moneda', 'EUR')
WHERE usuario_id = 4;
 
UPDATE preferencias
SET config = JSON_SET(
    config,
    '$.notificaciones', JSON_OBJECT('email', TRUE, 'push', FALSE, 'sms', FALSE)
)
WHERE usuario_id = 4;
 
SELECT JSON_PRETTY(config) AS config
FROM preferencias
WHERE usuario_id = 4;
config
{ "tema": "sistema", "idioma": "es", "moneda": "EUR", "notificaciones": { "email": true, "push": false, "sms": false } }

Cada UPDATE añade o modifica claves sin afectar a las demás. El documento crece orgánicamente con cada operación.

Establecer valores complejos

JSON_SET acepta cualquier tipo de valor JSON, incluyendo objetos y arrays construidos con las funciones correspondientes:

UPDATE catalogo
SET datos = JSON_SET(
    datos,
    '$.colores_disponibles', JSON_ARRAY('Negro', 'Violeta', 'Crema', 'Ámbar'),
    '$.dimensiones', JSON_OBJECT(
        'alto', 147.0,
        'ancho', 70.6,
        'grosor', 7.6,
        'unidad', 'mm'
    )
)
WHERE id = 1;
 
SELECT datos->'$.colores_disponibles' AS colores,
       datos->'$.dimensiones' AS dimensiones
FROM catalogo
WHERE id = 1;
coloresdimensiones
["Negro", "Violeta", "Crema", "Ámbar"]{"alto": 147.0, "ancho": 70.6, "grosor": 7.6, "unidad": "mm"}

Manejo de NULL

Cuando el documento original es NULL, JSON_SET devuelve NULL:

SELECT JSON_SET(NULL, '$.clave', 'valor') AS resultado;
resultado
NULL

Si quieres garantizar que siempre hay un documento válido, puedes usar COALESCE:

UPDATE preferencias
SET config = JSON_SET(
    COALESCE(config, '{}'),
    '$.tema', 'oscuro'
)
WHERE usuario_id = 4;

Cuando el valor a establecer es NULL, se escribe como el literal null de JSON:

SELECT JSON_SET('{"a": 1}', '$.b', NULL) AS resultado;
resultado
{"a": 1, "b": null}

Combinación con otras funciones

JSON_SET funciona especialmente bien con valores calculados a partir del propio documento o de otras fuentes:

-- Aplicar un 10% de descuento y registrar el precio original
UPDATE catalogo
SET datos = JSON_SET(
    datos,
    '$.precio_original', CAST(datos->>'$.precio' AS DECIMAL(10,2)),
    '$.precio', ROUND(CAST(datos->>'$.precio' AS DECIMAL(10,2)) * 0.9, 2),
    '$.en_oferta', TRUE
)
WHERE id = 1;
 
SELECT
    datos->>'$.precio_original' AS original,
    datos->>'$.precio' AS con_descuento,
    datos->>'$.en_oferta' AS oferta
FROM catalogo
WHERE id = 1;
originalcon_descuentooferta
849.99764.99true

El precio original se guarda en una clave nueva antes de aplicar el descuento. Como JSON_SET procesa los pares de izquierda a derecha, el valor de $.precio_original se toma del documento antes de que $.precio sea modificado.

En el siguiente artículo veremos JSON_REMOVE para eliminar valores de un documento JSON.

Escrito por Eduardo Lázaro