Tento článek je momentálně v ruštině — anglický překlad se připravuje.
to_jsonb — это функция PostgreSQL, которая берёт любое SQL-значение и превращает его в jsonb: скаляр, массив или целую строку таблицы. Чаще всего её зовут, когда строку нужно отдать наружу как JSON — в ответ API, в очередь, в лог — или собрать из нескольких таблиц вложенный документ прямо в запросе. Вместо ручной склейки текста с вечной охотой за кавычками вы один раз вызываете to_jsonb и забываете про экранирование: типы при этом не теряются. Ниже разберём, как ею пользоваться на практике и почему в новом коде она почти всегда выигрывает у json_build_object и устаревшего row_to_json.
Что делает to_jsonb
to_jsonb(value) конвертирует одно значение в jsonb, и форма результата идёт строго за формой входа. Скаляры становятся JSON-скалярами, массивы — JSON-массивами, а строка таблицы превращается в объект, ключами которого служат имена колонок:
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;
Но настоящая сила — в другом фокусе: передать целую строку. Псевдоним таблицы внутри to_jsonb ссылается не на колонку, а на всю запись разом:
SELECT to_jsonb(u) AS user_json
FROM users u
WHERE u.country = 'DE';
На выходе по каждой строке получаем готовый объект: {"id": 7, "email": "a@b.de", "name": "Anna", "country": "DE", "created_at": "2026-01-10T08:00:00"}. Обратите внимание на детали: created_at уехал в ISO-строку, числа остались числами, а NULL стал честным JSON null — не пустой строкой и не словом «null» в кавычках.
Типы сохраняются, и это решает
Главное, за что любят to_jsonb, — она уважает исходные типы. Целое остаётся числом, а не оборачивается в строку; булево — это настоящие true/false, а не текст. Именно здесь ручная конкатенация обычно и спотыкается:
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 и numeric становятся JSON-числами.
text, varchar, date, timestamp сериализуются в строки.
boolean остаётся булевым.
- Массивы PostgreSQL разворачиваются в JSON-массивы, и вложенность не теряется.
А вот и грабли, на которые наступают с завидной регулярностью: timestamp без таймзоны конвертируется без суффикса Z, а timestamptz — уже со смещением. Фронтенд, который слепо считает любую дату за UTC, на первом же значении уедет на часы в сторону. Хотите строгий UTC — приводите колонку к timestamptz заранее, а не уповайте на клиент.
Вложенные объекты и выбор колонок
Целая строка нужна не всегда — часто хочется выкинуть пару полей или подцепить связанные данные. Здесь два пути. Первый, самый прямой: собрать объект из строки, а лишние ключи отрезать оператором -:
SELECT to_jsonb(u) - 'created_at' - 'id' AS public_user
FROM users u;
Второй встречается в боевом коде куда чаще — вложить заказы пользователя массивом прямо внутрь его объекта. jsonb_agg собирает строки заказов, каждую из которых 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;
Оператор || сливает два jsonb-объекта в один, дописывая к пользователю ключ orders. Повторяя приём, можно собрать дерево любой глубины прямо в запросе, не таская сырые данные в приложение.
to_jsonb против json_build_object
Когда форма ответа должна быть строго своя — другие имена ключей, вычисляемые поля, фиксированный порядок, — на сцену выходит json_build_object (или его бинарный брат jsonb_build_object). Пары ключ-значение здесь вы выписываете руками:
SELECT jsonb_build_object(
'user_id', u.id,
'label', u.name || ' <' || u.email || '>',
'is_local', u.country = 'DE'
) AS card
FROM users u;
Как выбирать между ними — по сути в две строки:
to_jsonb(row) — когда нужен объект «как в таблице», с именами колонок. Минимум кода, форма следует за схемой.
jsonb_build_object(...) — когда форма важнее схемы: переименование, вычисления, отбор полей, стабильный порядок ключей.
На деле их обычно скрещивают: каркас лепят через to_jsonb, а сверху доклеивают вычисленные ключи через || и jsonb_build_object. Выходит и быстро, и под полным контролем.
to_jsonb против row_to_json
Старушка row_to_json(row) делает почти то же самое, но возвращает тип json, а не jsonb. Разница тут не косметическая, а в самом способе хранения:
json — это текст как есть: сохраняет порядок ключей, дубликаты и пробелы, но не знает операторов -, ||, @> и не индексируется через GIN.
jsonb — это разобранное бинарное дерево: порядок ключей теряется, дубликаты схлопываются, зато в руках все операторы и быстрый поиск по индексу.
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';
Правило на каждый день простое: в новом коде берите to_jsonb. Его можно крутить операторами, индексировать и выбирать быстрее, а row_to_json оставьте на те редкие случаи, где важен буквальный текстовый вывод — порядок ключей и пробелы один в один.
В MySQL ближайший родственник — JSON_OBJECT('k', v, ...), по духу близкий к jsonb_build_object; готового «строка целиком в JSON» там нет, ключи приходится перечислять вручную. В ClickHouse модель JSON другая: есть отдельный тип JSON и функция toJSONString, но семантика «строка как объект» работает иначе, так что переносить запросы один в один не выйдет — придётся переписывать под местные правила.
to_jsonb— это функция PostgreSQL, которая берёт любое SQL-значение и превращает его вjsonb: скаляр, массив или целую строку таблицы. Чаще всего её зовут, когда строку нужно отдать наружу как JSON — в ответ API, в очередь, в лог — или собрать из нескольких таблиц вложенный документ прямо в запросе. Вместо ручной склейки текста с вечной охотой за кавычками вы один раз вызываетеto_jsonbи забываете про экранирование: типы при этом не теряются. Ниже разберём, как ею пользоваться на практике и почему в новом коде она почти всегда выигрывает уjson_build_objectи устаревшегоrow_to_json.Что делает to_jsonb
to_jsonb(value)конвертирует одно значение вjsonb, и форма результата идёт строго за формой входа. Скаляры становятся JSON-скалярами, массивы — JSON-массивами, а строка таблицы превращается в объект, ключами которого служат имена колонок:-- 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;Но настоящая сила — в другом фокусе: передать целую строку. Псевдоним таблицы внутри
to_jsonbссылается не на колонку, а на всю запись разом:-- A whole row becomes one JSON object: column -> value SELECT to_jsonb(u) AS user_json FROM users u WHERE u.country = 'DE';На выходе по каждой строке получаем готовый объект:
{"id": 7, "email": "a@b.de", "name": "Anna", "country": "DE", "created_at": "2026-01-10T08:00:00"}. Обратите внимание на детали:created_atуехал в ISO-строку, числа остались числами, аNULLстал честным JSONnull— не пустой строкой и не словом «null» в кавычках.Типы сохраняются, и это решает
Главное, за что любят
to_jsonb, — она уважает исходные типы. Целое остаётся числом, а не оборачивается в строку; булево — это настоящиеtrue/false, а не текст. Именно здесь ручная конкатенация обычно и спотыкается: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;Коротко, во что что превращается:
integerиnumericстановятся JSON-числами.text,varchar,date,timestampсериализуются в строки.booleanостаётся булевым.А вот и грабли, на которые наступают с завидной регулярностью:
timestampбез таймзоны конвертируется без суффиксаZ, аtimestamptz— уже со смещением. Фронтенд, который слепо считает любую дату за UTC, на первом же значении уедет на часы в сторону. Хотите строгий UTC — приводите колонку кtimestamptzзаранее, а не уповайте на клиент.Вложенные объекты и выбор колонок
Целая строка нужна не всегда — часто хочется выкинуть пару полей или подцепить связанные данные. Здесь два пути. Первый, самый прямой: собрать объект из строки, а лишние ключи отрезать оператором
-:-- Drop sensitive or noisy keys from the row object SELECT to_jsonb(u) - 'created_at' - 'id' AS public_user FROM users u;Второй встречается в боевом коде куда чаще — вложить заказы пользователя массивом прямо внутрь его объекта.
jsonb_aggсобирает строки заказов, каждую из которыхto_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;Оператор
||сливает дваjsonb-объекта в один, дописывая к пользователю ключorders. Повторяя приём, можно собрать дерево любой глубины прямо в запросе, не таская сырые данные в приложение.to_jsonb против json_build_object
Когда форма ответа должна быть строго своя — другие имена ключей, вычисляемые поля, фиксированный порядок, — на сцену выходит
json_build_object(или его бинарный братjsonb_build_object). Пары ключ-значение здесь вы выписываете руками:-- 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;Как выбирать между ними — по сути в две строки:
to_jsonb(row)— когда нужен объект «как в таблице», с именами колонок. Минимум кода, форма следует за схемой.jsonb_build_object(...)— когда форма важнее схемы: переименование, вычисления, отбор полей, стабильный порядок ключей.На деле их обычно скрещивают: каркас лепят через
to_jsonb, а сверху доклеивают вычисленные ключи через||иjsonb_build_object. Выходит и быстро, и под полным контролем.to_jsonb против row_to_json
Старушка
row_to_json(row)делает почти то же самое, но возвращает типjson, а неjsonb. Разница тут не косметическая, а в самом способе хранения:json— это текст как есть: сохраняет порядок ключей, дубликаты и пробелы, но не знает операторов-,||,@>и не индексируется через GIN.jsonb— это разобранное бинарное дерево: порядок ключей теряется, дубликаты схлопываются, зато в руках все операторы и быстрый поиск по индексу.-- row_to_json returns json; to_jsonb returns jsonb SELECT row_to_json(e) AS as_json, to_jsonb(e) AS as_jsonb, to_jsonb(e) -> 'salary' AS salary_node -- works only on jsonb FROM employees e WHERE e.dept = 'eng';Правило на каждый день простое: в новом коде берите
to_jsonb. Его можно крутить операторами, индексировать и выбирать быстрее, аrow_to_jsonоставьте на те редкие случаи, где важен буквальный текстовый вывод — порядок ключей и пробелы один в один.В MySQL ближайший родственник —
JSON_OBJECT('k', v, ...), по духу близкий кjsonb_build_object; готового «строка целиком в JSON» там нет, ключи приходится перечислять вручную. В ClickHouse модель JSON другая: есть отдельный типJSONи функцияtoJSONString, но семантика «строка как объект» работает иначе, так что переносить запросы один в один не выйдет — придётся переписывать под местные правила.