JSON_REPLACE
La función JSON_REPLACE modifica valores que ya existen dentro de un documento JSON sin crear claves nuevas. Si la ruta especificada apunta a un valor existente en el documento, JSON_REPLACE lo sustituye por el nuevo valor proporcionado. Si la ruta no existe, la función la ignora silenciosamente sin generar errores ni modificar el documento. Este comportamiento protector convierte a JSON_REPLACE en la opción más segura cuando necesitas actualizar datos existentes con la garantía de que no se agregarán campos inesperados por error.
En el ecosistema de funciones de modificación JSON de MySQL, JSON_REPLACE ocupa un lugar muy específico. Mientras que JSON_INSERT solo agrega claves nuevas y nunca modifica las existentes, y JSON_SET hace ambas cosas sin distinción, JSON_REPLACE se limita exclusivamente a modificar lo que ya existe. Esta separación de responsabilidades no es casual: permite escribir sentencias UPDATE cuya intención sea inequívoca. Cuando un desarrollador ve JSON_REPLACE en el código, sabe inmediatamente que esa operación nunca alterará la estructura del documento, solo actualizará valores dentro de la estructura existente. En sistemas donde la integridad del esquema JSON es crítica, como configuraciones de aplicación o datos financieros, esta garantía resulta invaluable.
Sintaxis
La sintaxis de JSON_REPLACE sigue el mismo patrón que las demás funciones de modificación JSON de MySQL: un documento seguido de uno o más pares de ruta y valor.
JSON_REPLACE(documento_json, ruta, valor [, ruta2, valor2, ...])El primer argumento es el documento JSON original sobre el que se aplicarán los reemplazos. Los argumentos siguientes van en pares: cada par consta de una expresión JSON Path que identifica la ubicación del valor a reemplazar, y el nuevo valor que ocupará esa posición. Puedes especificar tantos pares como necesites en una sola llamada, lo que permite actualizar múltiples campos del documento en una única operación. La función devuelve un nuevo documento JSON con los reemplazos aplicados, sin modificar el documento original.
Cuando proporcionas múltiples pares ruta-valor, MySQL los procesa de izquierda a derecha. Esto significa que si el segundo par depende del resultado del primero, la segunda operación se aplica sobre el documento ya modificado por la primera. En la práctica, esto rara vez importa salvo en escenarios muy específicos donde dos rutas apuntan a la misma ubicación.
Comportamiento básico
La regla fundamental que gobierna el comportamiento de JSON_REPLACE es sencilla: solo modifica claves existentes y nunca crea nuevas. Esta regla se aplica de forma consistente en todos los niveles de anidamiento del documento.
SELECT JSON_REPLACE(
'{"nombre": "Auriculares BT", "precio": 79.99, "stock": 50}',
'$.precio', 69.99,
'$.color', 'Negro'
) AS resultado;| resultado |
|---|
| {"nombre": "Auriculares BT", "precio": 69.99, "stock": 50} |
En este ejemplo, la clave precio existía en el documento original, por lo que su valor cambió de 79.99 a 69.99. Sin embargo, la clave color no existía en el documento, así que la operación de reemplazo para esa ruta se ignoró sin generar ningún error ni advertencia. El documento resultante tiene exactamente las mismas claves que el original; solo ha cambiado el valor de precio.
Este comportamiento es precisamente lo que distingue a JSON_REPLACE de sus funciones hermanas. Con JSON_INSERT, la situación sería la inversa: precio no se modificaría (porque ya existe) y color sí se agregaría (porque no existe). Con JSON_SET, ambas operaciones tendrían efecto: precio se actualizaría y color se agregaría. Elegir la función correcta según tu intención es una práctica fundamental para escribir código SQL robusto y predecible.
Caso práctico: actualizar precios de productos
El escenario más habitual para JSON_REPLACE es la actualización de valores conocidos dentro de documentos JSON existentes en una tabla. Imaginemos una tienda de electrónica que almacena información de producto en formato JSON para poder manejar campos variables según la categoría del artículo.
CREATE TABLE productos (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(200) NOT NULL,
datos JSON
);
INSERT INTO productos (nombre, datos) VALUES
('Monitor 4K 27"', '{"marca": "LG", "precio": 349.99, "stock": 45, "activo": true}'),
('Teclado mecanico', '{"marca": "Keychron", "precio": 129.99, "stock": 120, "activo": true}'),
('Raton gaming', '{"marca": "Logitech", "precio": 79.99, "stock": 200, "activo": true}');Tras una venta del monitor, necesitamos actualizar tanto el precio (que ha bajado por una promoción) como el stock (que se redujo en una unidad). Con JSON_REPLACE, podemos actualizar ambos campos en una sola sentencia con la seguridad de que no se creará ninguna clave nueva por accidente.
UPDATE productos
SET datos = JSON_REPLACE(
datos,
'$.precio', 299.99,
'$.stock', 44
)
WHERE id = 1;
SELECT nombre, datos->>'$.precio' AS precio, datos->>'$.stock' AS stock
FROM productos
WHERE id = 1;| nombre | precio | stock |
|---|---|---|
| Monitor 4K 27" | 299.99 | 44 |
Solo los valores especificados cambiaron. La marca LG y el estado activo permanecen intactos porque no fueron incluidos en la llamada a JSON_REPLACE. Esta capacidad de actualizar selectivamente ciertos campos del documento sin tocar el resto es una de las ventajas principales de almacenar datos en formato JSON: no necesitas conocer ni especificar todos los campos del documento para modificar solo los que te interesan.
Caso práctico: actualizar valores en objetos anidados
Los documentos JSON en aplicaciones reales raramente son planos. Lo habitual es trabajar con objetos que contienen otros objetos anidados a varios niveles de profundidad. JSON_REPLACE maneja estas estructuras sin ningún problema, ya que acepta rutas de cualquier profundidad siguiendo la sintaxis estándar de JSON Path.
INSERT INTO productos (nombre, datos) VALUES
('Portatil ASUS', '{
"marca": "ASUS",
"precio": 1299.00,
"specs": {
"procesador": "Intel i7-13700H",
"ram": "16GB",
"ssd": "512GB"
},
"valoracion": {"media": 4.3, "total": 156}
}');Supongamos que el portátil ha recibido una mejora de hardware y también han llegado más reseñas de clientes. Necesitamos actualizar la RAM y el SSD dentro del objeto specs, así como la media y el total dentro del objeto valoracion. Cada una de estas actualizaciones requiere una ruta que atraviese dos niveles de profundidad.
UPDATE productos
SET datos = JSON_REPLACE(
datos,
'$.specs.ram', '32GB',
'$.specs.ssd', '1TB',
'$.valoracion.media', 4.5,
'$.valoracion.total', 178
)
WHERE nombre = 'Portatil ASUS';
SELECT
datos->>'$.specs.ram' AS ram,
datos->>'$.specs.ssd' AS ssd,
datos->>'$.valoracion.media' AS valoracion,
datos->>'$.valoracion.total' AS resenas
FROM productos
WHERE nombre = 'Portatil ASUS';| ram | ssd | valoracion | resenas |
|---|---|---|---|
| 32GB | 1TB | 4.5 | 178 |
Cada ruta anidada se actualiza independientemente de las demás. Si alguna de las rutas no existiera en el documento (por ejemplo, si escribiéramos $.specs.gpu y esa clave no estuviera definida), esa operación particular se omitiría sin afectar al resto de las actualizaciones. El procesador sigue siendo "Intel i7-13700H" porque no lo incluimos en la lista de reemplazos.
Caso práctico: actualizaciones masivas
Donde JSON_REPLACE realmente brilla es en las actualizaciones masivas que afectan a muchas filas simultáneamente. En lugar de actualizar cada registro individualmente, puedes aplicar la misma transformación a todas las filas que cumplan una condición determinada. Esto es mucho más eficiente que realizar múltiples actualizaciones individuales, tanto en términos de rendimiento como de legibilidad del código.
Un caso típico es desactivar todos los productos que se han quedado sin stock. En una tabla donde el estado activo/inactivo se almacena dentro del JSON, JSON_REPLACE permite cambiar ese campo en todas las filas afectadas con una sola sentencia.
UPDATE productos
SET datos = JSON_REPLACE(datos, '$.activo', FALSE)
WHERE CAST(datos->>'$.stock' AS UNSIGNED) = 0;Otro escenario frecuente es aplicar un descuento porcentual a todos los productos activos. En este caso, el nuevo valor de cada producto depende de su valor actual, lo que requiere extraer el precio del JSON, realizar el cálculo y escribir el resultado de vuelta en el documento.
UPDATE productos
SET datos = JSON_REPLACE(
datos,
'$.precio',
ROUND(CAST(datos->>'$.precio' AS DECIMAL(10,2)) * 0.85, 2)
)
WHERE datos->>'$.activo' = 'true';
SELECT nombre,
datos->>'$.precio' AS precio_nuevo,
datos->>'$.activo' AS activo
FROM productos;| nombre | precio_nuevo | activo |
|---|---|---|
| Monitor 4K 27" | 254.99 | true |
| Teclado mecanico | 110.49 | true |
| Raton gaming | 67.99 | true |
| Portatil ASUS | 1104.15 | true |
Observa la cadena de operaciones que MySQL ejecuta para cada fila: primero extrae el valor de $.precio con el operador ->>, que devuelve una cadena de texto. Luego, CAST convierte esa cadena a un valor DECIMAL para poder realizar la multiplicación. El resultado numérico se redondea con ROUND y finalmente JSON_REPLACE escribe el nuevo precio en el documento. Todo esto ocurre de forma atómica para cada fila dentro de la transacción del UPDATE.
Reemplazar con diferentes tipos de valores
Una de las características flexibles de JSON_REPLACE es que acepta cualquier tipo de valor JSON como reemplazo, incluyendo objetos completos, arrays, números, cadenas, booleanos y null. Esto significa que puedes reemplazar un valor escalar por un objeto complejo, o viceversa, siempre que la clave ya exista en el documento.
UPDATE productos
SET datos = JSON_REPLACE(
datos,
'$.specs', JSON_OBJECT(
'procesador', 'Intel i9-14900HX',
'ram', '64GB DDR5',
'ssd', '2TB NVMe',
'gpu', 'RTX 4080'
)
)
WHERE nombre = 'Portatil ASUS';
SELECT datos->>'$.specs' AS specs
FROM productos
WHERE nombre = 'Portatil ASUS';| specs |
|---|
| {"gpu": "RTX 4080", "ram": "64GB DDR5", "ssd": "2TB NVMe", "procesador": "Intel i9-14900HX"} |
Es fundamental entender que esta operación reemplaza el objeto specs completo. El objeto anterior con sus tres claves (procesador, ram, ssd) desaparece y es sustituido íntegramente por el nuevo objeto con cuatro claves. Si tu intención era solo agregar la clave gpu manteniendo las claves existentes, la operación correcta sería usar JSON_SET con la ruta $.specs.gpu para insertar solo la nueva clave sin afectar las demás.
También puedes usar JSON_OBJECT y JSON_ARRAY para construir los nuevos valores de forma dinámica dentro de la propia sentencia SQL, lo que evita tener que componer cadenas JSON manualmente y reduce el riesgo de errores de formato.
Reemplazar elementos de arrays
Además de trabajar con objetos, JSON_REPLACE permite modificar elementos individuales de un array accediendo a ellos por su índice numérico. Los índices de los arrays JSON comienzan en 0, al igual que en la mayoría de los lenguajes de programación.
SELECT JSON_REPLACE(
'{"colores": ["rojo", "azul", "verde"]}',
'$.colores[1]', 'amarillo'
) AS resultado;| resultado |
|---|
| {"colores": ["rojo", "amarillo", "verde"]} |
El segundo elemento del array (índice 1) cambió de "azul" a "amarillo", mientras que los demás elementos permanecen intactos. Este patrón es útil cuando conoces la posición exacta del elemento que deseas modificar.
Sin embargo, si especificas un índice que no existe en el array, la operación se ignora siguiendo la regla fundamental de JSON_REPLACE: no crear nada nuevo. Un array con dos elementos solo tiene los índices 0 y 1. Intentar reemplazar el índice 5 no tendrá ningún efecto.
SELECT JSON_REPLACE(
'{"colores": ["rojo", "azul"]}',
'$.colores[5]', 'inexistente'
) AS resultado;| resultado |
|---|
| {"colores": ["rojo", "azul"]} |
El array solo tiene dos elementos (índices 0 y 1), así que la operación sobre el índice 5 no tiene efecto y el documento se devuelve sin modificaciones. Si lo que necesitas es agregar un nuevo elemento al final del array, deberías usar JSON_ARRAY_APPEND en su lugar.
Reemplazar valores con expresiones calculadas
En muchas situaciones de producción, el nuevo valor no es un literal fijo sino el resultado de una expresión calculada a partir de los datos existentes o de otras columnas de la tabla. JSON_REPLACE acepta cualquier expresión SQL válida como valor de reemplazo, lo que abre la puerta a transformaciones sofisticadas.
Por ejemplo, puedes concatenar información de la tabla con datos del propio documento JSON para crear descripciones enriquecidas.
UPDATE productos
SET datos = JSON_REPLACE(
datos,
'$.precio',
ROUND(CAST(datos->>'$.precio' AS DECIMAL(10,2)) * 1.10, 2)
)
WHERE nombre = 'Raton gaming';
SELECT nombre, datos->>'$.precio' AS precio
FROM productos
WHERE nombre = 'Raton gaming';| nombre | precio |
|---|---|
| Raton gaming | 74.79 |
En este ejemplo, aplicamos una subida del 10% al precio del ratón. El patrón de leer un valor del JSON, transformarlo con funciones SQL y escribirlo de vuelta con JSON_REPLACE es extremadamente común en aplicaciones que gestionan datos dinámicos en formato JSON.
También puedes utilizar funciones de fecha y hora para registrar marcas temporales de actualización, o funciones de cadena para normalizar valores de texto almacenados en el documento.
Manejo de NULL
El comportamiento de JSON_REPLACE con valores NULL sigue dos reglas distintas según dónde aparezca el null: en el documento o en el valor de reemplazo.
Cuando el documento JSON original es NULL, JSON_REPLACE devuelve NULL directamente. No existe un documento sobre el que aplicar reemplazos, por lo que la función no puede hacer nada.
SELECT JSON_REPLACE(NULL, '$.clave', 'valor') AS resultado;| resultado |
|---|
| NULL |
Cuando el documento existe pero el nuevo valor proporcionado es NULL, el comportamiento es diferente y puede resultar sorprendente. En lugar de eliminar la clave, JSON_REPLACE establece su valor al literal null de JSON. La clave sigue existiendo en el documento, pero ahora contiene un valor nulo explícito.
SELECT JSON_REPLACE(
'{"nombre": "Test", "descripcion": "Original"}',
'$.descripcion', NULL
) AS resultado;| resultado |
|---|
| {"nombre": "Test", "descripcion": null} |
La clave descripcion no ha desaparecido del documento; simplemente ahora tiene el valor null. Si tu intención es eliminar completamente una clave del documento, debes usar JSON_REMOVE en su lugar. Esta distinción entre "valor nulo" y "clave inexistente" es semánticamente importante en muchas aplicaciones: un campo con valor null podría significar "el usuario explícitamente dejó este campo vacío", mientras que la ausencia de la clave podría significar "este campo no aplica para este registro".
Combinación con otras funciones JSON
En sentencias UPDATE de cierta complejidad, es habitual necesitar tanto reemplazar valores existentes como agregar claves nuevas al mismo tiempo. Puedes lograr esto encadenando JSON_REPLACE con JSON_INSERT, pasando el resultado de una función como entrada de la otra.
UPDATE productos
SET datos = JSON_INSERT(
JSON_REPLACE(datos, '$.precio', 279.99),
'$.ultima_modificacion', CURRENT_TIMESTAMP
)
WHERE id = 1;
SELECT datos->>'$.precio' AS precio,
datos->>'$.ultima_modificacion' AS modificado
FROM productos
WHERE id = 1;| precio | modificado |
|---|---|
| 279.99 | 2026-03-24 10:30:00 |
En esta sentencia, primero JSON_REPLACE actualiza el precio (que es una clave existente) y devuelve el documento modificado. Ese documento intermedio se pasa a JSON_INSERT, que agrega la fecha de modificación (que es una clave nueva). El resultado final contiene ambos cambios.
Aunque este patrón de encadenamiento funciona correctamente, en la práctica es más sencillo usar JSON_SET para operaciones que mezclan actualizaciones e inserciones. Sin embargo, hay escenarios donde el encadenamiento explícito de JSON_REPLACE y JSON_INSERT es preferible: cuando quieres que el código documente claramente la intención de cada operación, o cuando necesitas aplicar lógica diferente a las actualizaciones frente a las inserciones.
También puedes combinar JSON_REPLACE con funciones de inspección como JSON_CONTAINS_PATH para condicionar los reemplazos. Por ejemplo, solo actualizar un campo si efectivamente existe en el documento, lo que puede ser útil cuando trabajas con documentos de estructura heterogénea.
Errores comunes
El error más frecuente al trabajar con JSON_REPLACE es confundirlo con JSON_SET. Muchos desarrolladores asumen que JSON_REPLACE creará la clave si no existe, y se sorprenden al ver que el documento no cambió. Si tu intención es "actualizar si existe, crear si no existe", la función correcta es JSON_SET. Recuerda la regla: JSON_REPLACE nunca crea, solo modifica.
Otro error habitual es intentar reemplazar una clave dentro de un objeto anidado sin especificar la ruta completa. Si tienes un documento con la estructura \{"datos": \{"precio": 100\}\} y escribes JSON_REPLACE(col, '$.precio', 200), la operación no tendrá efecto porque $.precio no existe en el nivel raíz. La ruta correcta sería $.datos.precio.
Un tercer error es no tener en cuenta que JSON_REPLACE con valor NULL no elimina la clave. Si necesitas limpiar un campo del documento, recuerda que JSON_REPLACE(doc, '$.campo', NULL) dejará la clave con valor null, no la eliminará. Para eliminarla, usa JSON_REMOVE.
Finalmente, un error sutil pero peligroso es asumir que JSON_REPLACE preserva el tipo de dato original. Si un campo contiene el número 100 y lo reemplazas con la cadena '100', el tipo cambiará de INTEGER a STRING dentro del JSON, lo que podría romper lógica que dependa de JSON_TYPE o de comparaciones numéricas.
Cuándo usar JSON_REPLACE
Elige JSON_REPLACE cuando necesites actualizar valores existentes con la garantía absoluta de que la estructura del documento no cambiará. Los escenarios más adecuados incluyen la actualización de precios, stocks y estados en catálogos de productos; la modificación de configuraciones donde las claves están predefinidas y no deben aparecer nuevas; la actualización masiva de un campo específico en muchas filas de una tabla; y cualquier situación donde agregar claves accidentalmente podría causar problemas en la lógica de la aplicación que consume los datos JSON.
Si necesitas más flexibilidad y no te importa que la función pueda crear claves nuevas además de modificar las existentes, utiliza JSON_SET. Y si solo quieres agregar claves nuevas sin riesgo de modificar las existentes, JSON_INSERT es la opción correcta. Comprender estas tres funciones y cuándo usar cada una es fundamental para trabajar de forma segura y predecible con datos JSON en MySQL.
En el siguiente artículo veremos JSON_SET, la función que combina el comportamiento de inserción y reemplazo en una sola operación.
Escrito por Eduardo Lázaro
