Working with JSON

Modern databases can store and query JSON documents inside ordinary tables. That's useful for data with a flexible shape — product attributes that vary by category, API payloads, user preferences — while keeping the rest of the data relational. This lesson uses SQLite's JSON functions and maps them to other databases.

Storing JSON

SQL
ALTER TABLE products ADD COLUMN attributes TEXT;

UPDATE products SET attributes = '{"colour": "black", "wireless": true, "battery_hours": 24}' WHERE id = 4;
UPDATE products SET attributes = '{"colour": "white", "watts": 65}' WHERE id = 5;
UPDATE products SET attributes = '{"pages": 320, "format": "hardback", "tags": ["astronomy", "maps"]}' WHERE id = 3;

SELECT name, attributes FROM products WHERE attributes IS NOT NULL;
Result 3 rows
nameattributes
Night Sky Atlas{"pages": 320, "format": "hardback", "tags": ["astronomy", "maps"]}
Wireless Earbuds{"colour": "black", "wireless": true, "battery_hours": 24}
USB-C Charger{"colour": "white", "watts": 65}

A keyboard needs different details from a book. Rather than dozens of mostly-empty columns, a JSON column holds whatever applies to each product. PostgreSQL has dedicated json/jsonb types and MySQL a JSON type; SQLite stores JSON as text (or as binary jsonb from version 3.45).

Extracting values

SQL
ALTER TABLE products ADD COLUMN attributes TEXT;
UPDATE products SET attributes = '{"colour": "black", "wireless": true, "battery_hours": 24}' WHERE id = 4;
UPDATE products SET attributes = '{"pages": 320, "format": "hardback", "tags": ["astronomy", "maps"]}' WHERE id = 3;

SELECT name,
       json_extract(attributes, '$.colour') AS colour,
       attributes ->> '$.pages' AS pages,
       attributes -> '$.tags' AS tags_json,
       attributes ->> '$.tags[0]' AS first_tag
FROM products
WHERE id IN (3, 4);
Result 2 rows
namecolourpagestags_jsonfirst_tag
Night Sky AtlasNULL320["astronomy","maps"]astronomy
Wireless EarbudsblackNULLNULLNULL

A path like $.colour navigates the document: $ is the root, .key selects a field and [0] an array element. ->> returns a plain SQL value; -> returns JSON. Missing paths give NULL.

Filtering on JSON

SQL
ALTER TABLE products ADD COLUMN attributes TEXT;
UPDATE products SET attributes = '{"colour": "black", "wireless": true}' WHERE id = 4;
UPDATE products SET attributes = '{"colour": "white", "watts": 65}' WHERE id = 5;
UPDATE products SET attributes = '{"colour": "black", "wireless": false}' WHERE id = 6;

SELECT name FROM products WHERE attributes ->> '$.colour' = 'black';
Result 2 rows
name
Wireless Earbuds
Mechanical Keyboard

Building JSON from rows

SQL
SELECT json_object('id', id, 'name', name, 'price', price) AS product_json
FROM products
WHERE category = 'Toys';
Result 2 rows
product_json
{"id":10,"name":"Jigsaw Puzzle 1000","price":18}
{"id":11,"name":"Wooden Train Set","price":39.9}
SQL
SELECT c.name,
       json_group_array(o.id) AS order_ids
FROM customers AS c
JOIN orders AS o ON o.customer_id = c.id
GROUP BY c.id, c.name
HAVING COUNT(*) > 1;
Result 5 rows
nameorder_ids
Ana Pereira[1,3,11]
Ben Carter[2,7]
Chloé Martin[4,13]
David Müller[5,14]
George Brown[8,15]

Databases can produce JSON directly — handy for APIs. PostgreSQL uses json_build_object and json_agg; MySQL uses JSON_OBJECT and JSON_ARRAYAGG; SQL Server uses FOR JSON.

Turning JSON arrays into rows

SQL
SELECT value AS tag
FROM json_each('["astronomy", "maps", "gift"]');
Result 3 rows
tag
astronomy
maps
gift
SQL
ALTER TABLE products ADD COLUMN attributes TEXT;
UPDATE products SET attributes = '{"tags": ["astronomy", "maps"]}' WHERE id = 3;
UPDATE products SET attributes = '{"tags": ["gardening", "gift"]}' WHERE id = 1;

SELECT p.name, t.value AS tag
FROM products AS p, json_each(p.attributes, '$.tags') AS t
ORDER BY tag;
Result 4 rows
nametag
Night Sky Atlasastronomy
The Patient Gardenergardening
The Patient Gardenergift
Night Sky Atlasmaps

json_each expands an array into a table of rows, so you can join, filter and count JSON contents with normal SQL. The PostgreSQL equivalents are jsonb_array_elements and jsonb_each; MySQL and Oracle have JSON_TABLE.

Modifying JSON

SQL
ALTER TABLE products ADD COLUMN attributes TEXT;
UPDATE products SET attributes = '{"colour": "black", "wireless": true}' WHERE id = 4;

UPDATE products
SET attributes = json_set(attributes, '$.colour', 'navy', '$.warranty_years', 2)
WHERE id = 4;

SELECT attributes FROM products WHERE id = 4;
Result 1 row
attributes
{"colour":"navy","wireless":true,"warranty_years":2}

Validation

SQL
SELECT json_valid('{"a": 1}') AS good, json_valid('{a: 1}') AS bad;
Result 1 row
goodbad
10

Add CHECK (json_valid(attributes)) to make sure only valid JSON is stored.

JSON or columns?

Use real columns for data you filter, join, sort or constrain regularly — they're faster, typed and validated. Use JSON for genuinely variable or rarely queried details. If you find yourself filtering on a JSON field constantly, promote it to a column (or index it: PostgreSQL GIN indexes, or expression indexes on the extracted value).

Try it yourself

Store a preferences document for a customer and read individual settings from it.

Try it yourself
Result