sqlpostgresqljsonbjson

to_jsonb in PostgreSQL: Turn a Whole Row into a JSON Object

How to_jsonb converts any value, row or array into jsonb while preserving types, and how it differs from json_build_object and row_to_json.

3 min. skaitymoReferencesql · postgresql · jsonb · json · row_to_json
Šis straipsnis šiuo metu yra rusų kalba — vertimas į anglų kalbą rengiamas.

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 стал честным JSON null — не пустой строкой и не словом «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 остаётся булевым.
  • Массивы PostgreSQL разворачиваются в JSON-массивы, и вложенность не теряется.

А вот и грабли, на которые наступают с завидной регулярностью: 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, но семантика «строка как объект» работает иначе, так что переносить запросы один в один не выйдет — придётся переписывать под местные правила.

Praktikuokitės su realiomis užduotimis

Spręskite užduotis SQL treniruoklyje su momentiniu vertinimu ir užuominomis.

Atverti treniruoklį