JSONB & Semi-Structured Data

JSONB stores JSON data in a decomposed binary format that PostgreSQL can query, index, and filter directly — a genuine document store living inside a regular relational column.

JSON vs JSONB

PostgreSQL actually has two JSON types: plain JSON stores an exact text copy of what you inserted, preserving whitespace and key order; JSONB parses it into a binary structure, which is slightly slower to insert but much faster to query — and it's what almost everyone should reach for by default.

Storing and inserting JSONB

SQL storefront=#
CREATE TABLE events (
  id SERIAL PRIMARY KEY,
  payload JSONB
);

INSERT INTO events (payload) VALUES
  ('{"type": "signup", "user": {"name": "Priya", "plan": "pro"}}'),
  ('{"type": "signup", "user": {"name": "Sam", "plan": "free"}}');
SELECT * FROM events;
Output
 id |                              payload                               
----+---------------------------------------------------------------------
  1 | {"type": "signup", "user": {"name": "Priya", "plan": "pro"}}
  2 | {"type": "signup", "user": {"name": "Sam", "plan": "free"}}
(2 rows)

The -> and ->> operators

SQL storefront=#
SELECT payload->'user'->>'name' AS user_name, payload->>'type' AS event_type
FROM events;
Output
 user_name | event_type 
-----------+------------
 Priya     | signup
 Sam       | signup
(2 rows)

-> reaches into a JSON object or array and returns another JSONB value, so it can be chained (payload->'user' gives you the nested user object). ->> does the same thing but returns plain text instead — you generally want ->> at the very end of a chain, once you've drilled down to the actual value you want to compare or display.

Filtering with @>

SQL storefront=#
SELECT id FROM events WHERE payload @> '{"user": {"plan": "pro"}}';
Output
 id 
----
  1
(1 row)

@> is the "contains" operator — it checks whether the left JSONB value contains the structure on the right, at any depth. It's the natural way to filter rows by a nested field without writing out a full chain of -> operators.

Note: JSONB reorders object keys and drops whitespace and duplicate keys when it stores a value — plain JSON doesn't. If you ever need to reproduce the exact original text a client sent (for an audit log, say), that's a real reason to reach for JSON instead of JSONB. For everything you actually want to query, JSONB is almost always the right choice — and a GIN index (CREATE INDEX ON events USING GIN (payload)) can make @> lookups fast even on a huge table.