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.
|
| |
|---|---|---|
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) |
Storing and querying JSON data
A JSONB column
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 ->>
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
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)JSONpreserves the exact input text;JSONBstores a parsed, decomposed binary form.JSONBis 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.