How JSON to SQL conversion works
An array of objects maps cleanly onto SQL rows: each object becomes oneINSERT row, each key becomes a column, and each value is cast to a column literal. Rather than emit one statement per object, the generator collapses the whole array into a single multi-row INSERT … VALUES (…), (…) — far fewer round trips to the database.
[
{ "id": 1, "name": "Alice", "age": 30, "active": true, "notes": null },
{ "id": 2, "name": "Bob", "age": 25, "active": false, "notes": "new user" }
]INSERT INTO my_table (id, name, age, active, notes) VALUES
(1, 'Alice', 30, TRUE, NULL),
(2, 'Bob', 25, FALSE, 'new user');Columns come from the union of all keys
Columns are taken from the keys of the first object. If a later object carries an extra key, scanning only the first row silently drops it; if an object is missing a key, that cell must become NULL so every row has the same arity as the column list. A robust generator takes the union of keys across every object.
[
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob", "team": "Support" }
]-- column union: id, name, team
INSERT INTO my_table (id, name, team) VALUES
(1, 'Alice', NULL),
(2, 'Bob', 'Support');Escaping strings, NULL, and booleans
Each JSON type maps to a distinct SQL literal. Strings are single-quoted and any inner single quote is doubled (' → '') — this is the SQL standard escape, not a backslash. Numbers are bare literals. true/false become TRUE/FALSE, and null becomes an unquoted NULL. Quoting NULL would store the literal text "NULL", a classic bug.
[
{ "msg": "O'Brien said hi", "qty": 3, "shipped": false, "ref": null }
]INSERT INTO my_table (msg, qty, shipped, ref) VALUES
('O''Brien said hi', 3, FALSE, NULL);Nested objects: JSON string or flattened columns
SQL cells are scalar, so a nested object cannot be stored as-is. The generator serializes nested objects and arrays with JSON.stringify and single-quotes the result, which drops straight into a PostgreSQL JSONB or MySQL JSON column. The alternative is to flatten nested keys into separate columns (address.city → address_city) when you query those fields often.
[
{ "id": 1, "name": "Alice", "address": { "city": "Paris", "zip": "75001" } }
]-- Strategy A: store nested object as a JSONB column (PostgreSQL)
INSERT INTO users (id, name, address) VALUES
(1, 'Alice', '{"city":"Paris","zip":"75001"}');
SELECT name FROM users WHERE address->>'city' = 'Paris';
-- Strategy B: flatten nested keys into columns
INSERT INTO users (id, name, address_city, address_zip) VALUES
(1, 'Alice', 'Paris', '75001');CREATE TABLE and dialect differences
The generator emits a commented-out CREATE TABLE above the INSERT, inferring types from the first object: strings → TEXT/VARCHAR, integers → INTEGER, floats → NUMERIC, booleans → BOOLEAN (or TINYINT(1) in MySQL, which has no native boolean), objects → JSONB/JSON/TEXT. Uncomment it to create the table first. Watch identifier quoting and reserved words like order or group.
-- CREATE TABLE my_table (
-- id INTEGER PRIMARY KEY,
-- name TEXT,
-- age INTEGER,
-- active BOOLEAN, -- MySQL: TINYINT(1)
-- meta JSONB -- MySQL: JSON | SQLite: TEXT
-- );
-- Reserved words must be quoted:
INSERT INTO orders ("order", "group") VALUES (1, 'A'); -- PostgreSQL
INSERT INTO orders (`order`, `group`) VALUES (1, 'A'); -- MySQLGenerate INSERTs in code
When you need conversion in a script or pipeline rather than the browser tool, the core is a per-value formatter. Note the order: in Python bool must be checked before int because bool is a subclass of int. For real applications, prefer the parameterized patterns — they let the driver handle escaping and prevent SQL injection.
# Python — string-building (good for seeds / migrations)
import json
def sql_value(v):
if v is None: return 'NULL'
if isinstance(v, bool): return 'TRUE' if v else 'FALSE' # before int!
if isinstance(v, (int, float)): return str(v)
if isinstance(v, (dict, list)): return "'" + json.dumps(v).replace("'", "''") + "'"
return "'" + str(v).replace("'", "''") + "'"
def json_to_inserts(table, records):
cols = ', '.join(records[0].keys())
rows = ',\n '.join('(' + ', '.join(sql_value(v) for v in r.values()) + ')' for r in records)
return f"INSERT INTO {table} ({cols}) VALUES\n {rows};"// Node.js — same logic in JavaScript
function sqlValue(v) {
if (v === null || v === undefined) return 'NULL';
if (typeof v === 'boolean') return v ? 'TRUE' : 'FALSE';
if (typeof v === 'number') return String(v);
if (typeof v === 'object') return "'" + JSON.stringify(v).replace(/'/g, "''") + "'";
return "'" + String(v).replace(/'/g, "''") + "'";
}# Python — PRODUCTION: parameterized bulk insert (PostgreSQL)
import psycopg2.extras
cols = list(data[0].keys())
rows = [[r.get(c) for c in cols] for r in data]
psycopg2.extras.execute_values(
cur, f"INSERT INTO users ({', '.join(cols)}) VALUES %s", rows, page_size=1000)For 100K+ rows, skip INSERT entirely and use PostgreSQL's COPY protocol — 10 to 100 times faster. See the full walkthrough in the Convert JSON to SQL guide. To produce a CSV for \copy first, use the JSON to CSV converter.