Cuando necesitas exponer una fila de una tabla como JSON o armar una estructura anidada para una API, PostgreSQL ofrece to_jsonb. Toma cualquier valor SQL, fila o array y lo convierte en jsonb sin perder los tipos. Veamos como usarla y en que se diferencia de json_build_object y row_to_json.
Que hace to_jsonb
to_jsonb(value) convierte un unico valor en jsonb. Los escalares se vuelven escalares JSON, los arrays se vuelven arrays JSON y una fila de tabla se vuelve un objeto JSON cuyas claves son los nombres de columna:
SELECT to_jsonb(42) AS num,
to_jsonb('hello'::text) AS str,
to_jsonb(ARRAY[1, 2, 3]) AS arr,
to_jsonb(true) AS flag;
El truco mas util es pasar una fila entera. Un alias de tabla dentro de to_jsonb se refiere a toda la fila:
SELECT to_jsonb(u) AS user_json
FROM users u
WHERE u.country = 'DE';
Cada fila sale como {"id": 7, "email": "a@b.de", "name": "Anna", "country": "DE", "created_at": "2026-01-10T08:00:00"}. Fijate en que created_at se serializa como cadena ISO, los numeros siguen siendo numeros y NULL se vuelve null de JSON.
Los tipos se conservan, y eso importa
to_jsonb respeta los tipos de origen. Un entero sigue siendo numero, no cadena; un booleano sigue siendo un true/false real. Eso la diferencia de concatenar texto a mano:
SELECT jsonb_typeof(to_jsonb(o.amount)) AS amount_kind,
jsonb_typeof(to_jsonb(o.status)) AS status_kind,
jsonb_typeof(to_jsonb(NULL::int)) AS null_kind
FROM orders o
LIMIT 1;
integer y numeric se vuelven numeros JSON.
text, varchar, date, timestamp se serializan como cadenas.
boolean sigue siendo booleano.
- Los arrays de PostgreSQL se vuelven arrays JSON, conservando el anidamiento.
Gotcha: un timestamp sin zona horaria se convierte sin sufijo Z, mientras que timestamptz incluye el desfase. Si el frontend espera UTC estricto, convierte antes la columna a timestamptz.
Objetos anidados y seleccion de columnas
A menudo no quieres la fila completa: quieres elegir campos o anidar datos relacionados. Hay dos caminos. El primero: construir el objeto desde la fila y quitar claves sobrantes con -:
SELECT to_jsonb(u) - 'created_at' - 'id' AS public_user
FROM users u;
El segundo, mucho mas comun en la practica, es anidar los pedidos de un usuario como un array. jsonb_agg agrega las filas de pedidos, cada una convertida en objeto con to_jsonb:
SELECT to_jsonb(u) || jsonb_build_object(
'orders',
(SELECT jsonb_agg(to_jsonb(o))
FROM orders o
WHERE o.user_id = u.id)
) AS user_with_orders
FROM users u;
El operador || fusiona dos objetos jsonb, anadiendo la clave orders al objeto del usuario. Asi se construye un arbol de cualquier profundidad.
to_jsonb frente a json_build_object
Cuando necesitas una forma explicita -- otros nombres de clave, campos calculados, un orden fijo -- usa json_build_object (o jsonb_build_object). Enumeras los pares clave-valor a mano:
SELECT jsonb_build_object(
'user_id', u.id,
'label', u.name || ' <' || u.email || '>',
'is_local', u.country = 'DE'
) AS card
FROM users u;
Como elegir:
to_jsonb(row) -- cuando quieres el objeto tal cual, con nombres de columna. Codigo minimo, la forma sigue al esquema.
jsonb_build_object(...) -- cuando necesitas una forma exacta: renombrar, calcular, seleccionar campos, orden de claves estable.
Suelen combinarse: construyes la base con to_jsonb y luego anades claves calculadas encima con || y jsonb_build_object.
to_jsonb frente a row_to_json
La funcion antigua row_to_json(row) hace casi lo mismo, pero devuelve el tipo json, no jsonb. La diferencia es de fondo. Los operadores de acceso ->, ->>, #> y #>> funcionan tanto sobre json como sobre jsonb, asi que leer una clave no es lo que los distingue. Lo que json no puede hacer es el resto de la caja de herramientas:
json guarda el texto literal: conserva el orden de claves, los duplicados y los espacios, pero no admite los operadores -, ||, @> (exclusivos de jsonb) ni la indexacion GIN.
jsonb guarda una forma binaria analizada: se pierde el orden de claves, los duplicados se colapsan, pero tienes esos operadores extra y busquedas indexadas rapidas.
SELECT row_to_json(e) AS as_json,
to_jsonb(e) AS as_jsonb,
to_jsonb(e) -> 'salary' AS salary_node
FROM employees e
WHERE e.dept = 'eng';
Regla practica: para codigo nuevo usa to_jsonb, porque jsonb se manipula con los operadores extra, se indexa con GIN y es mas rapido de consultar. Reserva row_to_json para los casos donde importa la salida textual exacta.
En MySQL el analogo mas cercano es JSON_OBJECT('k', v, ...), parecido a jsonb_build_object; no hay un "fila entera a JSON" directo, asi que enumeras las claves a mano. En ClickHouse el modelo JSON es distinto: tiene un tipo JSON y una funcion toJSONString, pero la semantica de "fila como objeto" difiere, asi que las consultas no se trasladan una a una.
Cuando necesitas exponer una fila de una tabla como JSON o armar una estructura anidada para una API, PostgreSQL ofrece
to_jsonb. Toma cualquier valor SQL, fila o array y lo convierte enjsonbsin perder los tipos. Veamos como usarla y en que se diferencia dejson_build_objectyrow_to_json.Que hace to_jsonb
to_jsonb(value)convierte un unico valor enjsonb. Los escalares se vuelven escalares JSON, los arrays se vuelven arrays JSON y una fila de tabla se vuelve un objeto JSON cuyas claves son los nombres de columna:-- Scalars and arrays keep their natural JSON shape SELECT to_jsonb(42) AS num, to_jsonb('hello'::text) AS str, to_jsonb(ARRAY[1, 2, 3]) AS arr, to_jsonb(true) AS flag;El truco mas util es pasar una fila entera. Un alias de tabla dentro de
to_jsonbse refiere a toda la fila:-- A whole row becomes one JSON object: column -> value SELECT to_jsonb(u) AS user_json FROM users u WHERE u.country = 'DE';Cada fila sale como
{"id": 7, "email": "a@b.de", "name": "Anna", "country": "DE", "created_at": "2026-01-10T08:00:00"}. Fijate en quecreated_atse serializa como cadena ISO, los numeros siguen siendo numeros yNULLse vuelvenullde JSON.Los tipos se conservan, y eso importa
to_jsonbrespeta los tipos de origen. Un entero sigue siendo numero, no cadena; un booleano sigue siendo untrue/falsereal. Eso la diferencia de concatenar texto a mano:SELECT jsonb_typeof(to_jsonb(o.amount)) AS amount_kind, -- number jsonb_typeof(to_jsonb(o.status)) AS status_kind, -- string jsonb_typeof(to_jsonb(NULL::int)) AS null_kind -- null FROM orders o LIMIT 1;integerynumericse vuelven numeros JSON.text,varchar,date,timestampse serializan como cadenas.booleansigue siendo booleano.Gotcha: un
timestampsin zona horaria se convierte sin sufijoZ, mientras quetimestamptzincluye el desfase. Si el frontend espera UTC estricto, convierte antes la columna atimestamptz.Objetos anidados y seleccion de columnas
A menudo no quieres la fila completa: quieres elegir campos o anidar datos relacionados. Hay dos caminos. El primero: construir el objeto desde la fila y quitar claves sobrantes con
-:-- Drop sensitive or noisy keys from the row object SELECT to_jsonb(u) - 'created_at' - 'id' AS public_user FROM users u;El segundo, mucho mas comun en la practica, es anidar los pedidos de un usuario como un array.
jsonb_aggagrega las filas de pedidos, cada una convertida en objeto conto_jsonb:-- Nest each user's orders as an array of JSON objects SELECT to_jsonb(u) || jsonb_build_object( 'orders', (SELECT jsonb_agg(to_jsonb(o)) FROM orders o WHERE o.user_id = u.id) ) AS user_with_orders FROM users u;El operador
||fusiona dos objetosjsonb, anadiendo la claveordersal objeto del usuario. Asi se construye un arbol de cualquier profundidad.to_jsonb frente a json_build_object
Cuando necesitas una forma explicita -- otros nombres de clave, campos calculados, un orden fijo -- usa
json_build_object(ojsonb_build_object). Enumeras los pares clave-valor a mano:-- Explicit shape: rename keys and add a computed field SELECT jsonb_build_object( 'user_id', u.id, 'label', u.name || ' <' || u.email || '>', 'is_local', u.country = 'DE' ) AS card FROM users u;Como elegir:
to_jsonb(row)-- cuando quieres el objeto tal cual, con nombres de columna. Codigo minimo, la forma sigue al esquema.jsonb_build_object(...)-- cuando necesitas una forma exacta: renombrar, calcular, seleccionar campos, orden de claves estable.Suelen combinarse: construyes la base con
to_jsonby luego anades claves calculadas encima con||yjsonb_build_object.to_jsonb frente a row_to_json
La funcion antigua
row_to_json(row)hace casi lo mismo, pero devuelve el tipojson, nojsonb. La diferencia es de fondo. Los operadores de acceso->,->>,#>y#>>funcionan tanto sobrejsoncomo sobrejsonb, asi que leer una clave no es lo que los distingue. Lo quejsonno puede hacer es el resto de la caja de herramientas:jsonguarda el texto literal: conserva el orden de claves, los duplicados y los espacios, pero no admite los operadores-,||,@>(exclusivos de jsonb) ni la indexacion GIN.jsonbguarda una forma binaria analizada: se pierde el orden de claves, los duplicados se colapsan, pero tienes esos operadores extra y busquedas indexadas rapidas.-- Both json and jsonb support -> ; the jsonb-only operators are -, ||, @> SELECT row_to_json(e) AS as_json, to_jsonb(e) AS as_jsonb, to_jsonb(e) -> 'salary' AS salary_node -- -> works on json too FROM employees e WHERE e.dept = 'eng';Regla practica: para codigo nuevo usa
to_jsonb, porquejsonbse manipula con los operadores extra, se indexa con GIN y es mas rapido de consultar. Reservarow_to_jsonpara los casos donde importa la salida textual exacta.En MySQL el analogo mas cercano es
JSON_OBJECT('k', v, ...), parecido ajsonb_build_object; no hay un "fila entera a JSON" directo, asi que enumeras las claves a mano. En ClickHouse el modelo JSON es distinto: tiene un tipoJSONy una funciontoJSONString, pero la semantica de "fila como objeto" difiere, asi que las consultas no se trasladan una a una.