Quando voce precisa expor uma linha de tabela como JSON ou montar uma estrutura aninhada para uma API, o PostgreSQL oferece to_jsonb. Ele pega qualquer valor SQL, linha ou array e o transforma em jsonb sem perder os tipos. Vamos ver como usa-lo e como ele se compara a json_build_object e row_to_json.
O que o to_jsonb faz
to_jsonb(value) converte um unico valor em jsonb. Escalares viram escalares JSON, arrays viram arrays JSON e uma linha de tabela vira um objeto JSON cujas chaves sao os nomes das colunas:
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;
O truque mais util e passar uma linha inteira. Um alias de tabela dentro de to_jsonb se refere a linha toda:
SELECT to_jsonb(u) AS user_json
FROM users u
WHERE u.country = 'DE';
Cada linha sai assim: {"id": 7, "email": "a@b.de", "name": "Anna", "country": "DE", "created_at": "2026-01-10T08:00:00"}. Repare que created_at e serializado como string ISO, os numeros continuam numeros e NULL vira null de JSON.
Os tipos sao preservados, e isso importa
O to_jsonb respeita os tipos de origem. Um inteiro continua numero, nao string; um booleano continua um true/false de verdade. Isso o distingue da concatenacao manual de texto:
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 e numeric viram numeros JSON.
text, varchar, date, timestamp sao serializados como strings.
boolean continua booleano.
- Arrays do PostgreSQL viram arrays JSON, com o aninhamento preservado.
Gotcha: um timestamp sem fuso horario e convertido sem o sufixo Z, enquanto timestamptz inclui o deslocamento. Se o frontend espera UTC estrito, converta a coluna para timestamptz antes.
Objetos aninhados e selecao de colunas
Muitas vezes voce nao quer a linha inteira -- quer escolher campos ou aninhar dados relacionados. Ha dois caminhos. O primeiro: construir o objeto a partir da linha e remover chaves indesejadas com -:
SELECT to_jsonb(u) - 'created_at' - 'id' AS public_user
FROM users u;
O segundo, bem mais comum na pratica, e aninhar os pedidos de um usuario como um array. O jsonb_agg agrega as linhas de pedidos, cada uma transformada em objeto via 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;
O operador || mescla dois objetos jsonb, adicionando a chave orders ao objeto do usuario. E assim que se constroi uma arvore de qualquer profundidade.
to_jsonb versus json_build_object
Quando voce precisa de uma forma explicita -- outros nomes de chave, campos calculados, uma ordem fixa -- use json_build_object (ou jsonb_build_object). Voce lista os pares chave-valor na mao:
SELECT jsonb_build_object(
'user_id', u.id,
'label', u.name || ' <' || u.email || '>',
'is_local', u.country = 'DE'
) AS card
FROM users u;
Como escolher:
to_jsonb(row) -- quando voce quer o objeto como esta, com os nomes das colunas. Codigo minimo, a forma segue o esquema.
jsonb_build_object(...) -- quando precisa de uma forma exata: renomear, calcular, selecionar campos, ordem de chaves estavel.
Costumam ser combinados: voce monta a base com to_jsonb e depois adiciona chaves calculadas por cima com || e jsonb_build_object.
to_jsonb versus row_to_json
A funcao antiga row_to_json(row) faz quase a mesma coisa, mas retorna o tipo json, nao jsonb. A diferenca e fundamental. Os operadores de acesso ->, ->>, #> e #>> funcionam tanto em json quanto em jsonb, entao ler uma chave nao e o que os distingue. O que o json nao consegue fazer e o resto da caixa de ferramentas:
json guarda o texto literal: preserva a ordem das chaves, duplicatas e espacos, mas nao suporta os operadores -, ||, @> (exclusivos de jsonb) nem indexacao GIN.
jsonb guarda uma forma binaria ja analisada: a ordem das chaves se perde, as duplicatas colapsam, mas voce ganha esses operadores extras e buscas 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';
Regra pratica: para codigo novo use to_jsonb, porque jsonb se manipula com os operadores extras, se indexa com GIN e e mais rapido de consultar. Deixe row_to_json para os casos em que a saida textual exata importa.
No MySQL o analogo mais proximo e JSON_OBJECT('k', v, ...), parecido com jsonb_build_object; nao existe um "linha inteira para JSON" direto, entao voce lista as chaves na mao. No ClickHouse o modelo JSON e diferente: ha um tipo JSON e uma funcao toJSONString, mas a semantica de "linha como objeto" difere, entao as consultas nao se transferem uma a uma.
Quando voce precisa expor uma linha de tabela como JSON ou montar uma estrutura aninhada para uma API, o PostgreSQL oferece
to_jsonb. Ele pega qualquer valor SQL, linha ou array e o transforma emjsonbsem perder os tipos. Vamos ver como usa-lo e como ele se compara ajson_build_objecterow_to_json.O que o to_jsonb faz
to_jsonb(value)converte um unico valor emjsonb. Escalares viram escalares JSON, arrays viram arrays JSON e uma linha de tabela vira um objeto JSON cujas chaves sao os nomes das colunas:-- 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;O truque mais util e passar uma linha inteira. Um alias de tabela dentro de
to_jsonbse refere a linha toda:-- A whole row becomes one JSON object: column -> value SELECT to_jsonb(u) AS user_json FROM users u WHERE u.country = 'DE';Cada linha sai assim:
{"id": 7, "email": "a@b.de", "name": "Anna", "country": "DE", "created_at": "2026-01-10T08:00:00"}. Repare quecreated_ate serializado como string ISO, os numeros continuam numeros eNULLviranullde JSON.Os tipos sao preservados, e isso importa
O
to_jsonbrespeita os tipos de origem. Um inteiro continua numero, nao string; um booleano continua umtrue/falsede verdade. Isso o distingue da concatenacao manual de texto: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;integerenumericviram numeros JSON.text,varchar,date,timestampsao serializados como strings.booleancontinua booleano.Gotcha: um
timestampsem fuso horario e convertido sem o sufixoZ, enquantotimestamptzinclui o deslocamento. Se o frontend espera UTC estrito, converta a coluna paratimestamptzantes.Objetos aninhados e selecao de colunas
Muitas vezes voce nao quer a linha inteira -- quer escolher campos ou aninhar dados relacionados. Ha dois caminhos. O primeiro: construir o objeto a partir da linha e remover chaves indesejadas com
-:-- Drop sensitive or noisy keys from the row object SELECT to_jsonb(u) - 'created_at' - 'id' AS public_user FROM users u;O segundo, bem mais comum na pratica, e aninhar os pedidos de um usuario como um array. O
jsonb_aggagrega as linhas de pedidos, cada uma transformada em objeto viato_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;O operador
||mescla dois objetosjsonb, adicionando a chaveordersao objeto do usuario. E assim que se constroi uma arvore de qualquer profundidade.to_jsonb versus json_build_object
Quando voce precisa de uma forma explicita -- outros nomes de chave, campos calculados, uma ordem fixa -- use
json_build_object(oujsonb_build_object). Voce lista os pares chave-valor na mao:-- 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 escolher:
to_jsonb(row)-- quando voce quer o objeto como esta, com os nomes das colunas. Codigo minimo, a forma segue o esquema.jsonb_build_object(...)-- quando precisa de uma forma exata: renomear, calcular, selecionar campos, ordem de chaves estavel.Costumam ser combinados: voce monta a base com
to_jsonbe depois adiciona chaves calculadas por cima com||ejsonb_build_object.to_jsonb versus row_to_json
A funcao antiga
row_to_json(row)faz quase a mesma coisa, mas retorna o tipojson, naojsonb. A diferenca e fundamental. Os operadores de acesso->,->>,#>e#>>funcionam tanto emjsonquanto emjsonb, entao ler uma chave nao e o que os distingue. O que ojsonnao consegue fazer e o resto da caixa de ferramentas:jsonguarda o texto literal: preserva a ordem das chaves, duplicatas e espacos, mas nao suporta os operadores-,||,@>(exclusivos de jsonb) nem indexacao GIN.jsonbguarda uma forma binaria ja analisada: a ordem das chaves se perde, as duplicatas colapsam, mas voce ganha esses operadores extras e buscas 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';Regra pratica: para codigo novo use
to_jsonb, porquejsonbse manipula com os operadores extras, se indexa com GIN e e mais rapido de consultar. Deixerow_to_jsonpara os casos em que a saida textual exata importa.No MySQL o analogo mais proximo e
JSON_OBJECT('k', v, ...), parecido comjsonb_build_object; nao existe um "linha inteira para JSON" direto, entao voce lista as chaves na mao. No ClickHouse o modelo JSON e diferente: ha um tipoJSONe uma funcaotoJSONString, mas a semantica de "linha como objeto" difere, entao as consultas nao se transferem uma a uma.