How to Convert JSON to SQL INSERT Statements
You have a JSON export and need it in a database: seed data, a migration, test fixtures for staging. Pasting values by hand doesn't scale, and most online converters want you to upload data you'd rather keep local. Here's the terminal-first path.
Option 1: The Transmute CLI (free)
Given users.json:
[
{ "name": "Alice", "age": 32 },
{ "name": "Bob", "age": 25 }
]
$ transmute users.json --output sql --table users -- Generated by Transmute INSERT INTO "users" ("name", "age") VALUES ('Alice', 32), ('Bob', 25);
The same works for CSV and YAML input — pick the format flag that matches your source:
$ transmute people.csv --output sql --table people $ transmute config.yaml --format yaml --output sql
Shaping rows before they hit the database
Because transformation happens before serialization, you filter, sort and reshape in the same command. Only adults, names only:
$ transmute users.json --pipe '[{"op":"filter","expr":"item.age > 26"},{"op":"pick","fields":["name"]}]' --output sql --table adults -- Generated by Transmute INSERT INTO "adults" ("name") VALUES ('Alice');
This is where a converter beats a one-off script: the same pipeline that cleans the data also decides what gets inserted.
Escaping and types (the part that breaks imports)
- Quotes are escaped correctly.
O'Brienbecomes'O''Brien'— the standard SQL doubling, valid in PostgreSQL, MySQL and SQLite. A string-concatenation approach produces broken statements on exactly this input. - Numbers stay numbers. Numeric-looking strings are emitted unquoted so they insert as real numeric columns; everything else is quoted.
- Nulls are real NULLs. JSON
null, missing fields and empty strings all becomeNULL— not the string"null". - Booleans become TRUE/FALSE, which PostgreSQL accepts natively; MySQL maps them to its TINYINT convention.
$ transmute mixed.json --output sql -- Generated by Transmute INSERT INTO "my_table" ("a") VALUES (1), (NULL);
Option 2: Python
import json rows = json.load(open("users.json")) cols = list(rows[0]) for row in rows: vals = ", ".join( "NULL" if row.get(c) is None else str(row[c]) if isinstance(row.get(c), (int, float)) else "'" + str(row[c]).replace("'", "''") + "'" for c in cols ) print(f'INSERT INTO users ({", ".join(cols)}) VALUES ({vals});')
Zero dependencies — but note what you're signing up for: column inference from the first row (later rows with extra keys silently drop data), manual type handling, and no batching. Fine for ten rows; error-prone for ten thousand.
Why not paste it into an online converter?
Exports headed for a database often contain customer data, tokens or internal IDs. Uploading them to a random website to get SQL back means trusting a third party with exactly the data you were about to put somewhere permanent. A CLI that runs locally has nowhere to send it.
Loading bigger files
A few thousand INSERT lines load fine through any client (psql -f seed.sql). Past tens of thousands of rows, wrap the output in a single transaction so a failure rolls back cleanly instead of leaving half a table:
$ { echo 'BEGIN;'; transmute big.json --output sql; echo 'COMMIT;'; } > seed.sql $ psql mydb -f seed.sql
Gotchas
- Column set comes from the union of keys across rows — consistent records convert cleanly, ragged ones get NULLs where fields are missing.
- Dates stay strings — quote style is correct, but cast to your column type on load if the target isn't text.
- Reserved words as column names (
order,group) are quoted with double quotes, which works in PostgreSQL/SQLite; MySQL needs ANSI_QUOTES mode or backticks.