JSON to SQL DDL
Generate CREATE TABLE DDL from sample JSON for four SQL dialects, with three honest shapes for nested data — entirely on this device.
The whole string is one identifier — it is quoted, never split on a dot.
Child table keeps one row per object; JSON column keeps the object whole; Flat columns prefix the parent key.
Off by default: a string that looks like a date is still text until you say otherwise.
The CREATE TABLE script appears here as you type.
Press ? for shortcuts · This page writes the schema, not the rows. To load data, use CSV to SQL Converter
Do it offline:$ sqlite3 app.db < schema.sql
How to use it
- Paste a JSON object or drop a .json file, and open a tab for any sample whose fields differ.
- Pick the dialect and type the table name — the whole string is one identifier, never split at a dot.
- Decide what nesting becomes: child tables joined by a foreign key, a JSON column, or columns folded into the parent.
- Copy the script or download schema.sql, then apply it with the sqlite3 line below; rows are a separate step.
A sample response is not a schema, but it is usually all you have when a table has to exist today. Paste one JSON object — or ten, if responses differ — and read back a CREATE TABLE script for PostgreSQL, MySQL, SQLite or SQL Server, with nesting turned into child tables, JSON columns or prefixed flat columns. Conversion happens in this browser tab; no sample is uploaded.
Examples
{"id":1,"email":"ada@example.com","signup_date":"2026-04-01","credits":120,"active":true}CREATE TABLE "accounts" (
"id" INTEGER NOT NULL,
"email" TEXT NOT NULL,
"signup_date" TEXT NOT NULL,
"credits" INTEGER NOT NULL,
"active" BOOLEAN NOT NULL,
PRIMARY KEY ("id")
);
A flat object becomes one table. The key named id is taken as the primary key, so no column is invented; signup_date stays TEXT until date typing is switched on.
{"id":7,"customer":{"name":"Ana","city":"Lisbon"},"items":[{"sku":"A1","qty":2},{"sku":"B2","qty":1}]}CREATE TABLE "orders" (
"id" INTEGER NOT NULL,
PRIMARY KEY ("id")
);
CREATE TABLE "customer" (
"orders_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"city" TEXT NOT NULL,
FOREIGN KEY ("orders_id") REFERENCES "orders" ("id")
);
CREATE TABLE "items" (
"orders_id" INTEGER NOT NULL,
"ordinal" INTEGER NOT NULL,
"sku" TEXT NOT NULL,
"qty" INTEGER NOT NULL,
FOREIGN KEY ("orders_id") REFERENCES "orders" ("id")
);
Three tables for one object: nested customer and the items array each get a table keyed back to orders. JSON arrays are ordered and rows are not, so items also carries an ordinal. Parents print first, so the script applies top to bottom.
{"order":3,"münchen":true,"user id":1,"user-id":2,"big":9223372036854775807}CREATE TABLE "events" (
"order" INTEGER NOT NULL,
"mnchen" BOOLEAN NOT NULL,
"user_id" INTEGER NOT NULL,
"user_id_2" INTEGER NOT NULL,
"big" BIGINT NOT NULL
);
The awkward-keys case. order is reserved in PostgreSQL and survives because identifiers are quoted; münchen lands as mnchen; keys that sanitise alike take a suffix. The big value passes 2^31−1, hence BIGINT, and 2^53, hence the warning.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
One JSON, four scripts
Identifiers travel in double quotes for PostgreSQL and SQLite, backticks for MySQL, brackets for SQL Server. Fractional numbers are DOUBLE PRECISION, DOUBLE, REAL or FLOAT in turn, and only two of the four have a JSON column — elsewhere it is TEXT or NVARCHAR(MAX), with a note saying so.
Nesting has three honest shapes
A nested object can be its own table joined by a foreign key, a JSON column, or prefixed columns such as profile_city merged into the parent. Arrays are never flattened — no column count fits an unbounded list — so they follow their own setting: a child table with an ordinal, or JSON.
A key to point at comes first
A table without a detected id gains a generated one the moment a child needs something to reference. Ask for that key outright and you get the dialect's clause: GENERATED ALWAYS AS IDENTITY, AUTO_INCREMENT, INTEGER PRIMARY KEY AUTOINCREMENT, IDENTITY(1,1). Ask for none and nesting falls back to JSON.
Inferred widths are the floor, not the ceiling
A whole number past 2^31−1 widens to BIGINT, and past 2^53 it also raises a warning: that is where a JSON number stops surviving a parse-and-print round trip. Fractional numbers get the dialect's double, the wrong type for money — swap in DECIMAL first.
Renamed identifiers are always named out loud
Sanitising to letters, digits and underscores, prefixing a leading digit, suffixing after a collision, truncating past 63 bytes in PostgreSQL, 64 in MySQL, 128 in SQL Server — every rewrite prints a note quoting the original key.
Some fields say nothing about themselves
A field null in every sample, an array always [], an object always {}: none describes a type, and SQL has no zero-column table. Each becomes a nullable text or JSON column with a note — the point where a second sample beats any inference. Columns are the union of the keys across your tabs, and a key missing from one drops NOT NULL.
Common errors
Input is larger than the 10 MB limit for this tool.- A refusal, never a silent trim: a schema missing its last third of columns still looks finished. One record types a table as well as a million.
Unexpected 'i' at line 1, column 2 — probable cause: property names must be double-quoted.- {id: 2} is a JavaScript object literal; JSON requires quoted keys. The position marks the first unreadable character, and the editor jumps there.
The samples are not JSON objects — column names come from keys, so a table needs an object. Wrap the value in an object, or paste a sample that has keys.- A bare 42 has no keys, and keys are what columns are named after. An array of objects is fine: the table comes from the elements inside it.
The samples describe no keys, so there is nothing to name columns after — a table needs at least one column. Paste a sample with keys, or switch the primary-key option to a generated key.- An empty {} is valid JSON and an illegal table in all four dialects. A generated key gives the table one legal column to start from.
The table "my_table" would need 1029 columns, past the 1024-column limit for this tool — split the sample, or set nested objects to a JSON column.- A sample that wide is usually a map keyed by id, which belongs in rows. Setting nesting to a JSON column collapses most of the width at once.
FAQ
Why does one JSON sample produce a different script per dialect?
Because the vendors disagree about spelling, not meaning. The quote character around identifiers, the floating-point type name, whether a JSON column exists and how a generated key is declared all change per target, so the script is rebuilt for the one you selected.
Where are the INSERT statements for my rows?
There are none here on purpose: this page emits schema, and a sample is a shape rather than a dataset. With the real records in hand, /csv-to-sql turns CSV or JSON rows into batched INSERT statements for the same four targets.
Can I paste a JSON Schema document instead of a sample?
Not yet — a schema pasted here is read as ordinary JSON, so you would get columns named type, properties and required. Feed it one representative record instead, and set the nesting policy to match the shape you want.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Generate again — above 2 MB this is the explicit Run.
- ⌘/Ctrl + S
- Download the script as schema.sql.
- ⌘/Ctrl + C
- Copy the script when nothing is selected.
- ⌘/Ctrl + Shift + C
- Copy even while a selection exists.
- t
- Cycle the theme.
- ? or ⌘/Ctrl + /
- Show the shortcut help overlay.
- Esc
- Close the topmost overlay.