JSON_ARRAY_APPEND

La función JSON_ARRAY_APPEND añade uno o más valores al final de un array que se encuentra dentro de un documento JSON. Es la forma más directa de agregar elementos a arrays existentes sin necesidad de reemplazar todo el documento, y representa una de las operaciones de modificación JSON más utilizadas en el día a día del desarrollo con MySQL. Cualquier aplicación que maneje listas dinámicas (etiquetas de producto, historiales de eventos, colecciones de imágenes, registros de actividad, listas de favoritos) necesitará en algún momento añadir un nuevo elemento a un array existente, y JSON_ARRAY_APPEND es la herramienta diseñada específicamente para ello.

A diferencia de JSON_SET o JSON_REPLACE, que reemplazan un valor completo en una ruta determinada, JSON_ARRAY_APPEND preserva todos los elementos existentes del array y simplemente añade el nuevo valor al final. Esto la convierte en una operación de acumulación, ideal para patrones donde los datos crecen con el tiempo. Si necesitas insertar un elemento en una posición intermedia del array en lugar de al final, la función JSON_ARRAY_INSERT te permite especificar el índice exacto donde quieres colocar el nuevo valor.

Sintaxis

La sintaxis de JSON_ARRAY_APPEND acepta múltiples pares de ruta y valor en una sola llamada, lo que permite modificar varios arrays dentro del mismo documento de forma atómica.

JSON_ARRAY_APPEND(json_doc, ruta, valor [, ruta, valor ...])

El primer argumento es el documento JSON original sobre el que se realizará la modificación. A continuación se proporcionan uno o más pares compuestos por una ruta JSON (que indica a qué array añadir) y el valor que se quiere añadir. La función devuelve un nuevo documento JSON con las modificaciones aplicadas; el documento original no se modifica in-place, lo que significa que en un UPDATE debes asignar el resultado de vuelta a la columna.

Un aspecto importante del comportamiento de esta función es lo que ocurre cuando la ruta apunta a un valor que no es un array. Si la ruta apunta a un escalar (una cadena, un número o un booleano), MySQL convierte automáticamente ese escalar en un array de un solo elemento y luego añade el nuevo valor. Si la ruta no existe en el documento, la función devuelve el documento sin cambios, sin generar ningún error. Este segundo comportamiento es silencioso y puede causar confusión si no lo conoces, porque la operación parece ejecutarse correctamente pero el valor no se añade.

Comportamiento básico

Empecemos con el caso más simple: añadir un elemento al final de un array que constituye la raíz del documento. La ruta '$' hace referencia al documento raíz completo.

SELECT JSON_ARRAY_APPEND(
  '["rojo", "azul", "verde"]',
  '$', 'amarillo'
) AS resultado;
resultado
["rojo", "azul", "verde", "amarillo"]

El valor 'amarillo' se añadió al final del array raíz, preservando los tres elementos originales. Este es el patrón más directo, pero en la práctica es poco habitual que un documento JSON sea solo un array; lo normal es que sea un objeto con propiedades, y que el array esté anidado dentro de una de esas propiedades.

Para añadir a un array que está dentro de un objeto, usas la ruta correspondiente utilizando la notación de punto.

SELECT JSON_ARRAY_APPEND(
  '{"nombre": "iPhone", "colores": ["negro", "blanco"]}',
  '$.colores', 'titanio'
) AS resultado;
resultado
{"colores": ["negro", "blanco", "titanio"], "nombre": "iPhone"}

El valor "titanio" se añadió al final del array colores. El resto del documento permaneció completamente intacto. Este es el patrón que usarás con más frecuencia: modificar un array específico dentro de un objeto más grande sin afectar al resto de la estructura.

Añadir diferentes tipos de datos

JSON_ARRAY_APPEND no se limita a cadenas de texto. Puedes añadir valores de cualquier tipo JSON válido, incluyendo números, booleanos, objetos anidados e incluso otros arrays.

SELECT JSON_ARRAY_APPEND(
  '{"precios": [10.99, 24.50]}',
  '$.precios', 15.75
) AS resultado;
resultado
{"precios": [10.99, 24.50, 15.75]}

También puedes añadir un objeto JSON como elemento de un array, lo que permite construir arrays de objetos de forma progresiva.

SELECT JSON_ARRAY_APPEND(
  '{"pedidos": []}',
  '$.pedidos', CAST('{"id": 1, "total": 59.99}' AS JSON)
) AS resultado;
resultado
{"pedidos": [{"id": 1, "total": 59.99}]}

Observa el uso de CAST(... AS JSON) al añadir un objeto. Sin el cast, MySQL interpretaría la cadena como un valor de texto literal en lugar de como un objeto JSON, y la almacenaría como una cadena entrecomillada dentro del array.

Modificar múltiples arrays en una sola llamada

Una de las ventajas de JSON_ARRAY_APPEND frente a llamadas encadenadas es que puedes modificar varios arrays dentro del mismo documento en una sola invocación.

SELECT JSON_ARRAY_APPEND(
  '{"tags": ["premium"], "colores": ["negro"]}',
  '$.tags', '5g',
  '$.colores', 'blanco'
) AS resultado;
resultado
{"colores": ["negro", "blanco"], "tags": ["premium", "5g"]}

Ambos arrays se modificaron en una única operación. Esto no solo es más conciso, sino también más eficiente que realizar dos actualizaciones separadas, especialmente en sentencias UPDATE donde cada operación implica una escritura en disco.

Conversión automática de escalares a arrays

Un comportamiento especial y bastante útil ocurre cuando la ruta apunta a un valor escalar en lugar de a un array. La función convierte automáticamente ese escalar en un array de un solo elemento y luego añade el nuevo valor.

SELECT JSON_ARRAY_APPEND(
  '{"color": "negro"}',
  '$.color', 'blanco'
) AS resultado;
resultado
{"color": ["negro", "blanco"]}

El valor escalar "negro" se convirtió en ["negro"] y luego se añadió "blanco", resultando en ["negro", "blanco"]. Este comportamiento es útil cuando evolucionas el esquema de tus datos: si originalmente almacenabas un solo color y ahora necesitas soportar múltiples colores, JSON_ARRAY_APPEND hace la transición de forma automática. Sin embargo, ten cuidado con este comportamiento si no es lo que esperas, porque cambia el tipo del campo de escalar a array de forma silenciosa.

Caso práctico: gestión de etiquetas de producto

En una tienda en línea, los administradores suelen necesitar añadir etiquetas a los productos de forma progresiva a medida que se definen campañas de marketing, se añaden categorías nuevas o se reorganiza el catálogo. JSON_ARRAY_APPEND permite hacer esto sin reconstruir el documento JSON completo.

CREATE TABLE productos (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nombre VARCHAR(100),
    datos JSON
);
 
INSERT INTO productos (nombre, datos) VALUES
('iPhone 15 Pro', '{"precio": 1299, "etiquetas": ["smartphone", "apple"], "imagenes": ["frontal.jpg"]}'),
('Samsung Galaxy S24', '{"precio": 899, "etiquetas": ["smartphone"], "imagenes": ["frontal.jpg", "trasera.jpg"]}'),
('MacBook Air M3', '{"precio": 1399, "etiquetas": ["portatil", "apple"], "imagenes": []}');

Para añadir la etiqueta "premium" al iPhone, usamos JSON_ARRAY_APPEND dentro de un UPDATE.

UPDATE productos
SET datos = JSON_ARRAY_APPEND(datos, '$.etiquetas', 'premium')
WHERE nombre = 'iPhone 15 Pro';

La sentencia modifica únicamente el array de etiquetas del iPhone, dejando intactos el precio, las imágenes y todos los demás campos del documento JSON. Para añadir tanto una nueva etiqueta como una nueva imagen al Samsung en una sola operación, aprovechamos los pares múltiples.

UPDATE productos
SET datos = JSON_ARRAY_APPEND(
    datos,
    '$.etiquetas', '5g',
    '$.imagenes', 'lateral.jpg'
)
WHERE nombre = 'Samsung Galaxy S24';

Después de ambas actualizaciones, el estado de la tabla refleja los cambios acumulados.

SELECT
    nombre,
    datos->>'$.etiquetas' AS etiquetas,
    datos->>'$.imagenes' AS imagenes
FROM productos;
nombreetiquetasimagenes
iPhone 15 Pro["smartphone", "apple", "premium"]["frontal.jpg"]
Samsung Galaxy S24["smartphone", "5g"]["frontal.jpg", "trasera.jpg", "lateral.jpg"]
MacBook Air M3["portatil", "apple"][]

Caso práctico: registro de actividad del usuario

Un patrón habitual en aplicaciones web es mantener un log de actividades recientes de cada usuario como un array JSON. Cada vez que el usuario realiza una acción, se añade un nuevo registro al array sin perder el historial previo. Este enfoque es más ligero que crear una tabla de log separada cuando solo necesitas las últimas acciones para mostrar en un dashboard o para análisis rápido.

CREATE TABLE sesiones (
    id INT PRIMARY KEY AUTO_INCREMENT,
    usuario VARCHAR(100),
    actividad JSON
);
 
INSERT INTO sesiones (usuario, actividad) VALUES
('María García', '{"acciones": ["login"], "paginas": ["/inicio"]}'),
('Carlos López', '{"acciones": ["login"], "paginas": ["/inicio"]}');

Cada vez que María realiza una acción, se añade tanto la acción como la página visitada al registro correspondiente.

UPDATE sesiones
SET actividad = JSON_ARRAY_APPEND(
    actividad,
    '$.acciones', 'ver_producto',
    '$.paginas', '/productos/iphone-15'
)
WHERE usuario = 'María García';

Si María continúa navegando, las nuevas acciones se siguen acumulando cronológicamente.

UPDATE sesiones
SET actividad = JSON_ARRAY_APPEND(
    actividad,
    '$.acciones', 'anadir_carrito',
    '$.paginas', '/carrito'
)
WHERE usuario = 'María García';

Al consultar la sesión de María, vemos todo su recorrido de navegación almacenado de forma compacta.

SELECT usuario, JSON_PRETTY(actividad) AS actividad
FROM sesiones
WHERE usuario = 'María García';
usuarioactividad
María García{"acciones": ["login", "ver_producto", "anadir_carrito"], "paginas": ["/inicio", "/productos/iphone-15", "/carrito"]}

Las acciones y las páginas se van acumulando cronológicamente sin perder las anteriores. Este patrón funciona bien para registros de corto plazo, como la sesión actual del usuario. Para logs de largo plazo con miles de entradas, es más recomendable usar una tabla relacional dedicada, ya que los arrays JSON muy grandes pueden degradar el rendimiento de lectura y escritura.

Caso práctico: añadir a arrays anidados

JSON_ARRAY_APPEND trabaja con rutas JSON completas, lo que significa que puedes acceder y modificar arrays que están profundamente anidados dentro de la estructura del documento. Esto es especialmente útil en documentos con esquemas complejos, como catálogos de productos con especificaciones detalladas.

SELECT JSON_ARRAY_APPEND(
  '{"producto": {"specs": {"colores_disponibles": ["negro", "blanco"]}}}',
  '$.producto.specs.colores_disponibles', 'azul'
) AS resultado;
resultado
{"producto": {"specs": {"colores_disponibles": ["negro", "blanco", "azul"]}}}

La ruta $.producto.specs.colores_disponibles navega por tres niveles de anidamiento para llegar al array objetivo. Independientemente de la profundidad, la función añade el nuevo valor al final del array encontrado.

Incluso puedes añadir a un elemento específico de un array de arrays, utilizando la notación de índice en la ruta.

SELECT JSON_ARRAY_APPEND(
  '{"matriz": [[1, 2], [3, 4], [5, 6]]}',
  '$.matriz[0]', 3
) AS resultado;
resultado
{"matriz": [[1, 2, 3], [3, 4], [5, 6]]}

El valor 3 se añadió al primer sub-array (índice 0), dejando los demás intactos. Este tipo de acceso indexado es útil cuando trabajas con estructuras tabulares almacenadas como arrays de arrays, por ejemplo matrices de datos o agrupaciones por categoría.

Manejo de NULL y rutas inexistentes

Comprender cómo se comporta JSON_ARRAY_APPEND con valores nulos y rutas que no existen es fundamental para evitar sorpresas en producción.

Cuando el documento JSON es NULL, la función devuelve NULL directamente, sin intentar realizar ninguna operación.

SELECT JSON_ARRAY_APPEND(NULL, '$', 'valor') AS resultado;
resultado
NULL

El comportamiento con rutas inexistentes es más sutil y puede causar confusión. Si la ruta especificada no existe en el documento, el valor no se añade y el documento se devuelve exactamente como estaba, sin ningún error ni advertencia.

SELECT JSON_ARRAY_APPEND(
  '{"nombre": "iPhone"}',
  '$.etiquetas', 'premium'
) AS resultado;
resultado
{"nombre": "iPhone"}

Como la ruta $.etiquetas no existe en el documento, no hay ningún array al que añadir el valor, y el documento vuelve intacto. Este comportamiento silencioso puede ser peligroso si no lo conoces, porque tu aplicación puede ejecutar la consulta, recibir un resultado exitoso y asumir que el valor fue añadido cuando en realidad no lo fue. Si necesitas crear el array cuando no existe, debes usar JSON_SET primero para crear un array vacío y luego usar JSON_ARRAY_APPEND, o bien usar JSON_SET directamente para asignar un array con el primer elemento.

SELECT JSON_SET(
  '{"nombre": "iPhone"}',
  '$.etiquetas', JSON_ARRAY('premium')
) AS resultado;
resultado
{"etiquetas": ["premium"], "nombre": "iPhone"}

De esta forma, JSON_ARRAY crea el array inicial y JSON_SET lo coloca en el documento. A partir de ese momento, puedes usar JSON_ARRAY_APPEND para seguir añadiendo elementos.

Evitar duplicados al añadir

Un patrón extremadamente común en aplicaciones reales es querer añadir un valor a un array solo si no existe previamente. Los arrays JSON no tienen ninguna restricción de unicidad, por lo que JSON_ARRAY_APPEND añadirá gustosamente el mismo valor varias veces. Para simular el comportamiento de un conjunto (set), puedes combinar la función con JSON_CONTAINS como condición previa.

UPDATE productos
SET datos = JSON_ARRAY_APPEND(datos, '$.etiquetas', '5g')
WHERE nombre = 'iPhone 15 Pro'
  AND NOT JSON_CONTAINS(datos->'$.etiquetas', '"5g"');

Esta consulta solo ejecuta la actualización si la etiqueta "5g" no está ya presente en el array. La condición NOT JSON_CONTAINS(...) actúa como un filtro que previene duplicados. Si ejecutas esta sentencia múltiples veces, solo tendrá efecto la primera vez.

Limitar el tamaño del array

Otro patrón útil es verificar la longitud del array antes de añadir un nuevo elemento, para evitar que los arrays crezcan indefinidamente. Esto es especialmente relevante en registros de actividad o historiales donde solo quieres conservar las últimas N entradas.

UPDATE productos
SET datos = JSON_ARRAY_APPEND(datos, '$.etiquetas', 'nueva_etiqueta')
WHERE id = 1
  AND JSON_LENGTH(datos, '$.etiquetas') < 10;

Esta sentencia solo añade la etiqueta si el array tiene menos de 10 elementos. Para un control más sofisticado donde además eliminas el primer elemento cuando se alcanza el límite (patrón de buffer circular), necesitarías combinar JSON_ARRAY_APPEND con JSON_REMOVE para eliminar el elemento en la posición $[0].

Errores comunes

El error más habitual al usar JSON_ARRAY_APPEND es olvidar que la ruta debe existir en el documento. Si intentas añadir a un array que no existe, la función no falla pero tampoco modifica nada, lo que puede resultar desconcertante. La solución es usar JSON_SET para crear la ruta antes de intentar añadir.

Otro error frecuente es no usar CAST(... AS JSON) al añadir objetos o arrays como nuevos elementos. Sin el cast, MySQL trata el valor como una cadena literal y la almacena entrecomillada dentro del array, produciendo un resultado inesperado.

-- Sin CAST: la cadena se almacena como texto literal
SELECT JSON_ARRAY_APPEND('{"items": []}', '$.items', '{"id": 1}') AS sin_cast;
sin_cast
{"items": ["{"id": 1}"]}

El resultado contiene la cadena "{\"id\": 1}" como texto, no como un objeto JSON. La forma correcta es usar CAST.

SELECT JSON_ARRAY_APPEND('{"items": []}', '$.items', CAST('{"id": 1}' AS JSON)) AS con_cast;
con_cast
{"items": [{"id": 1}]}

Ahora el objeto se almacena correctamente como un sub-objeto dentro del array.

Cuándo usar JSON_ARRAY_APPEND

Usa JSON_ARRAY_APPEND siempre que necesites añadir elementos al final de un array JSON existente. Los escenarios más habituales incluyen agregar etiquetas o categorías a productos, acumular entradas en logs de actividad, añadir imágenes a galerías almacenadas en JSON, registrar eventos cronológicos como inicios de sesión o cambios de estado, y construir listas de favoritos o historiales de navegación.

Si necesitas insertar en una posición específica del array, usa JSON_ARRAY_INSERT. Si necesitas reemplazar un elemento existente, JSON_SET o JSON_REPLACE son más apropiados. Y si necesitas eliminar un elemento del array, JSON_REMOVE es la función complementaria.

En el siguiente artículo veremos JSON_ARRAY_INSERT para insertar elementos en posiciones específicas.

Escrito por Eduardo Lázaro