SQLite no tiene un tipo JSON — y no pasa nada
SQLite no tiene una columna JSON dedicada. Los documentos JSON se guardan en una columna TEXT normal, y un conjunto de funciones integradas —conocidas en conjunto como la extensión JSON1— se encarga de parsearlos, consultarlos y modificarlos. La extensión JSON1 viene incluida en cualquier build moderno de SQLite, así que no hay nada que instalar.
La idea es sencilla: guardas el documento como texto y usas funciones para mirar dentro.
Dos filas, cada una con un documento JSON en una columna de texto. Ahora toca ver cómo meternos dentro de esos documentos para sacar lo que nos interesa.
Extraer campos con json_extract y el operador ->>
json_extract(columna, ruta) saca un valor de un documento JSON. La ruta JSON empieza con $ (la raíz) y usa .campo para las claves de objeto e [i] para los índices de array.
Escribir json_extract(data, '$.name') en cada consulta cansa rapidísimo, así que SQLite te ofrece dos operadores para acortar el código:
->devuelve un valor codificado como JSON (las cadenas vienen con sus comillas).->>devuelve un valor SQL (texto o número, sin comillas).
name_json regresa como "Ada" (sigue siendo JSON), mientras que name_text viene como Ada. Usa ->> cuando vayas a comparar o mostrar el valor. Usa -> cuando el resultado lo vayas a pasar a otra función JSON.
Filtrar por campos JSON
Una vez que puedes extraer, puedes filtrar. La expresión va en el WHERE como cualquier otra condición:
Esto funciona, pero en una tabla de cualquier tamaño se vuelve lento: hay que parsear cada fila para evaluar el predicado. Lo solucionaremos con un índice en un momento.
Construir JSON: json_object y json_array
En el sentido contrario, puedes construir JSON dentro de una consulta:
json_object('k1', v1, 'k2', v2, ...) arma un objeto y json_array(v1, v2, ...) arma un arreglo. Vienen muy bien para montar respuestas de API directamente desde SQL, y además se pueden anidar sin problema:
Modificar JSON: json_set, json_insert y json_replace
Hay tres funciones muy parecidas entre sí que modifican un documento JSON y devuelven la versión nueva:
json_set(doc, path, value)— asigna un valor en la ruta indicada, creándola si no existe y sobrescribiéndola si ya estaba.json_insert(doc, path, value)— solo inserta cuando la ruta aún no existe.json_replace(doc, path, value)— solo actualiza cuando la ruta ya existe.
Estas funciones no modifican el documento original: te devuelven uno nuevo, que normalmente vuelves a guardar con un UPDATE:
Ten en cuenta que json_set admite varios pares ruta/valor en una sola llamada. Para eliminar una clave, usa json_remove(doc, path).
Expandir arrays con json_each
json_each es una función con valor de tabla: recibe un array (u objeto) JSON y devuelve una fila por cada elemento. Eso convierte el típico "encontrar usuarios con la etiqueta admin" — incómodo en SQL puro — en un simple JOIN:
Cada fila de users se cruza con los elementos de su array tags. json_each expone columnas útiles como key, value, type y fullkey. Su pariente json_tree recorre el documento entero de forma recursiva, incluyendo todos los nodos anidados — muy práctico para buscar dentro de documentos cuya forma no conoces de antemano.
Cómo indexar un campo JSON en SQLite
La consulta de arriba con WHERE data ->> '$.active' = 1 funciona, pero obliga a SQLite a parsear cada fila para evaluar el predicado. Para los campos que consultas a menudo, lo mejor es crear un índice por expresión:
El índice tiene que usar exactamente la misma expresión que tu consulta. Si en el índice pones json_extract(data, '$.email') pero en la consulta usas data ->> '$.email', SQLite no las considera equivalentes y el índice se queda sin usar — elige una forma y mantenla en todo el código.
Para campos que consultas constantemente, queda más limpio usar una columna generada:
email se ve como una columna normal a ojos de quien escribe las consultas, pero se mantiene sincronizada con el JSON de forma automática.
Validar JSON en SQLite
json_valid(texto) devuelve 1 si el texto se parsea como JSON válido, y 0 si no. Combínalo con una restricción CHECK para rechazar datos mal formados al momento de escribir:
El primer INSERT pasa sin problemas; el segundo falla con un error de restricción. Sin esa comprobación, el JSON malformado se queda tranquilamente en la tabla hasta que, meses después, alguna llamada a json_extract revienta.
JSON vs JSONB en SQLite
Desde SQLite 3.45 existe una representación binaria llamada JSONB: los mismos datos, pero pre-parseados a una forma binaria compacta para que las funciones no tengan que volver a parsear en cada llamada. La familia de funciones jsonb_* (jsonb_extract, jsonb_set, jsonb_object, ...) devuelve JSONB en lugar de texto, y las columnas JSONB se pueden consultar con los mismos operadores.
Usa JSON en texto plano cuando quieras que tus documentos sean legibles en los volcados y fáciles de inspeccionar. Pásate a JSONB cuando una tabla sea grande, se consulte mucho y el coste de parseo aparezca de verdad en el perfilado. No cambies por defecto: la legibilidad del JSON plano vale mucho a la hora de depurar.
Cuándo conviene usar JSON en SQLite
Las columnas JSON brillan cuando:
- La forma del dato varía entre filas (piensa en payloads de eventos, logs de auditoría, webhooks de integraciones).
- Estás cacheando la respuesta de una API externa y quieres conservarla tal cual.
- Es un campo que casi nunca consultas ni filtras.
Y son mala idea cuando:
- Recurres a JSON para esquivar el diseño del esquema. Si todas las filas tienen los mismos campos, eso son columnas.
- Necesitas filtrar o hacer joins por un valor con frecuencia. Una columna real con su índice le va a ganar siempre a una búsqueda por ruta JSON.
- Lo que pedía el caso eran claves foráneas. JSON no te da integridad relacional.
El punto dulce está en combinar las dos cosas: columnas escalares para los campos que mandan en las consultas y restricciones, y al lado una columna JSON para esa larga cola de datos variables.
Siguiente: búsqueda de texto completo
JSON te da flexibilidad por el lado del almacenamiento. La siguiente página cubre FTS5, el motor de búsqueda de texto completo de SQLite, que te ofrece búsquedas reales con ranking y resaltado, muy por encima de lo que puede hacer LIKE.
Preguntas frecuentes
¿Cómo guarda SQLite los datos JSON?
SQLite no tiene un tipo JSON propio: el JSON se guarda como TEXT normal y corriente. La extensión JSON1 (compilada por defecto desde la 3.38) aporta funciones como json_extract, json_set o json_each que parsean ese texto y operan sobre él. Desde la 3.45 también existe el formato binario JSONB, pensado para accesos repetidos más rápidos.
¿Cómo consulto una columna JSON en SQLite?
Usa json_extract(columna, '$.ruta') o el operador abreviado ->>. Por ejemplo, SELECT data ->> '$.name' FROM users te devuelve el campo name del documento JSON guardado en data. Las rutas usan $ para la raíz, .campo para claves de objeto y [i] para índices de array.
¿Se puede indexar un campo JSON en SQLite?
Sí: crea un índice de expresión sobre la ruta extraída, así: CREATE INDEX idx_user_email ON users(json_extract(data, '$.email')). Las consultas que usen esa misma expresión en su WHERE tirarán del índice. Si un campo se consulta mucho, otra opción más limpia es declarar una columna generada e indexarla.
¿Qué diferencia hay entre -> y ->> en SQLite?
-> devuelve un valor JSON (sigue codificado como JSON, así que las cadenas vienen entre comillas), mientras que ->> devuelve un valor SQL plano (texto o número, sin comillas). Usa ->> cuando quieras el valor crudo para mostrarlo o compararlo, y -> cuando vayas a encadenar más operaciones JSON encima.