PostgreSQLJSON & JSONB

JSON & JSONB

PostgreSQL can store semi-structured JSON data directly in a column, via two types that look similar on the surface but behave very differently: JSON and JSONB.

JSON

JSONB

Storage

An exact copy of the input text

A decomposed binary representation

Whitespace & key order

Preserved exactly as written

Not preserved — keys may be reordered, whitespace removed

Duplicate keys

All duplicates kept; last one wins when queried

Only the last duplicate key is kept at storage time

Query performance

Slower — re-parses the text on every operation

Faster — already parsed into an efficient internal format

Indexing (e.g. GIN)

Not supported

Supported

Insert speed

Slightly faster (no parsing on write)

Slightly slower (parses on write)

Tip
Use `JSONB` by default. The only real reason to reach for plain `JSON` is when you specifically need to preserve the exact original text — formatting, key order, whitespace, duplicate keys — byte for byte. For everything else, including any data you plan to query, filter, or index by its contents, `JSONB` is the better choice.
Storing and querying JSON data

A JSONB column

SQL
CREATE TABLE events (
    event_id SERIAL PRIMARY KEY,
    payload  JSONB NOT NULL
);

INSERT INTO events (payload) VALUES
    ('{"type": "click", "user": {"id": 42, "name": "Ada"}, "tags": ["ui", "button"]}');
-> and ->>: getting fields

-> extracts a field as JSON (or JSONB); ->> extracts it as text. This distinction matters as soon as you want to compare the extracted value to a plain string or number, or chain further JSON operators.

-> vs ->>

SQL
SELECT payload -> 'type' FROM events;
-- "click"          (a JSONB value — still quoted, still JSON)

SELECT payload ->> 'type' FROM events;
-- click             (a plain text value — no quotes)

SELECT payload -> 'user' ->> 'name' FROM events;
-- Ada
"click"
click
Ada
#> and #>>: nested paths

For nested lookups more than one level deep, #> and #>> take an array of keys/indexes describing the path, which reads more clearly than chaining several -> operators.

Path operators

SQL
SELECT payload #> '{user, name}' FROM events;
-- "Ada"

SELECT payload #>> '{user, name}' FROM events;
-- Ada

SELECT payload #>> '{tags, 0}' FROM events;
-- ui   (first element of the "tags" array)
Note
`JSONB`'s decomposed storage is what made PostgreSQL a genuine competitor to document databases for many workloads — you get fast, indexable JSON queries (via GIN indexes on JSONB columns) while still keeping the full power of relational SQL: joins, transactions, constraints, and foreign keys, all in the same database. The **JSONB Operations** page goes much deeper into the operator set — containment (`@>`), key existence (`?`), and more — for building real queries against JSON data.
  • JSON preserves the exact input text; JSONB stores a parsed, decomposed binary form.

  • JSONB is faster to query, supports indexing, and is the right default for most use cases.

  • -> gets a field as JSON; ->> gets it as text.

  • #> and #>> do the same thing but follow a multi-step path given as an array of keys.

  • See the JSONB Operations page for the full operator reference, including containment and key-existence checks.