How to convert a json array to a markdown table
- Step 1Get your JSON array — Copy a JSON array of objects —
[{...}, {...}]— from your editor, an API client like Insomnia/Postman, or a.jsonfile. A single object{...}also works; it becomes a one-row table. - Step 2Paste it, or upload a .json file — Paste into the input box (the character counter shows your size against the tier limit) or switch to Upload and choose a
.jsonfile. Only one file is accepted — this tool is single-input. - Step 3Press Run — Conversion runs in your browser. There are no options to set — the tool reads every object's keys, builds the column set, and renders the table immediately.
- Step 4Check the column set — Headers are the union of all keys in array order of first appearance. If you expected a key that isn't there, it means no object in the array contained it (or those entries were primitives, not objects).
- Step 5Copy or download — Copy the Markdown to your clipboard, or download it — an uploaded
data.jsonis saved asdata-table.md. Toggle the raw view to see the literal pipe syntax versus the rendered preview. - Step 6Paste into your doc — Drop the table into a README, wiki, or PR description. To restyle or convert further, send it to a sibling tool like md-to-html or md-table-repair.
How each JSON value type renders in a cell
Cells are produced with JavaScript String(value ?? ""). This is the single most important table to understand — it explains every surprising output. Verified against the jsonToMdTable implementation.
| JSON value in an object | Cell output | Notes |
|---|---|---|
String "hello" | hello | Verbatim, except any | is escaped to \| |
Number 42 or 3.14 | 42 / 3.14 | Number's text form; large numbers are not reformatted |
Boolean true / false | true / false | Lowercase literal text |
null | (empty cell) | null ?? "" yields the empty string |
| Key missing from this object | (empty cell) | Column comes from the union; absent key reads as undefined → empty |
Nested object {"a":1} | [object Object] | String({}) is [object Object] — it is NOT pretty-printed JSON |
Array [1,2,3] | 1,2,3 | String([1,2,3]) joins with commas; nesting is lost |
What the tool returns for non-table input
The function returns a plain message string (not a table, not an error dialog) for inputs it cannot turn into rows.
| Input | Returned text | Why |
|---|---|---|
| Malformed JSON (trailing comma, single quotes) | Invalid JSON input. | JSON.parse threw; the message is returned in the output panel |
Empty array [] | Empty array. | No objects to derive columns or rows from |
Array of primitives [1, 2, 3] or ["a","b"] | No object keys found. | Primitives contribute no keys, so the column set is empty |
Single object {"a":1} | A one-row table | A non-array value is wrapped as [value] before processing |
| Array of objects | Full pipe table | The intended, supported case |
Tier limits for the JSON-to-Markdown tools
Markdown-family limits from tier-limits.ts. The paste box is bounded by the character limit; uploaded files by the byte limit. JSON-to-table is a Free tool.
| Tier | Max file size (upload) | Max characters (paste) | Files per run |
|---|---|---|---|
| Free | 1 MB | 500,000 | 1 |
| Pro | 10 MB | 5,000,000 | 10 |
| Pro-media | 50 MB | 20,000,000 | 50 |
| Developer | 500 MB | unlimited | unlimited |
Cookbook
Real arrays in, real Markdown out. Each example shows the JSON you paste and the exact table the tool produces.
A clean array of uniform objects
The happy path: every object has the same keys, all primitive values. Keys become headers in first-seen order; each object is a row.
Input:
[
{"id": 1, "name": "Widget", "inStock": true},
{"id": 2, "name": "Gadget", "inStock": false}
]
Output:
| id | name | inStock |
| --- | --- | --- |
| 1 | Widget | true |
| 2 | Gadget | false |Objects with different keys (the union rule)
The column set is the union of every key seen, in order of first appearance. Objects missing a column get an empty cell — they are not skipped or padded with placeholder text.
Input:
[
{"id": 1, "name": "Alice"},
{"id": 2, "email": "bob@x.com"}
]
Output:
| id | name | email |
| --- | --- | --- |
| 1 | Alice | |
| 2 | | bob@x.com |Null and missing values become empty cells
Both an explicit null and an absent key render as empty. There is no 'null' literal in the output — useful when you want blank cells for optional fields.
Input:
[
{"sku": "A1", "price": 9.99, "discount": null},
{"sku": "B2", "price": 19.99}
]
Output:
| sku | price | discount |
| --- | --- | --- |
| A1 | 9.99 | |
| B2 | 19.99 | |A single object, no array brackets
If you paste a lone object instead of an array, the tool wraps it and produces a one-row table — handy for documenting one config record without reshaping it.
Input:
{"host": "db.internal", "port": 5432, "ssl": true}
Output:
| host | port | ssl |
| --- | --- | --- |
| db.internal | 5432 | true |A pipe inside a value is escaped
A literal | in a string would otherwise split the cell. The tool escapes it to \| so the column boundaries stay intact and the value renders as written.
Input:
[
{"name": "prod", "cmd": "cat a | grep x"}
]
Output:
| name | cmd |
| --- | --- |
| prod | cat a \| grep x |Edge cases and what actually happens
Nested object in a value renders as [object Object]
By designA value that is itself an object becomes the literal text [object Object] — the tool uses String(), not JSON.stringify. To keep nested data visible, flatten the JSON before pasting (e.g. promote address.city to a top-level city key), or document the nested structure in a separate table.
Array value renders as comma-joined text
By designA value like ["a","b","c"] becomes a,b,c because String([...]) joins with commas. Nested arrays lose their structure ([[1,2]] becomes 1,2). If the joined form is ambiguous, flatten or pre-serialize that field into a string in your source data first.
Invalid JSON
Invalid JSON inputIf JSON.parse fails — a trailing comma, single-quoted keys, an unquoted token, or a wrapping fragment — the output is the literal text Invalid JSON input.. Validate or pretty-print your JSON first; the input must be a single well-formed JSON document, not JSONL (newline-delimited).
Empty array
Empty arrayAn input of [] returns Empty array. — there are no objects to build columns or rows from. This is expected; supply at least one object.
Array of primitives
No object keys found[1, 2, 3] or ["a", "b"] returns No object keys found. because primitives have no keys to become columns. Wrap each value in an object — e.g. [{"value": 1}, {"value": 2}] — to get a one-column table.
A newline inside a string value breaks the row
Row breakOnly | is escaped — newline characters are not. A multi-line string value (a literal \n inside a cell) will split that table row across lines and corrupt alignment. Replace newlines with spaces in your source before converting, or run the result through md-table-repair.
Mixed array of objects and primitives
PreservedIf an array mixes objects and primitives ([{"id":1}, 5]), columns come only from the objects; the primitive contributes no keys and renders as an all-empty row. The table is still produced — the stray entry just has blank cells.
Very wide objects produce very wide tables
ExpectedObjects with dozens of keys create a column per key, which is hard to read in narrow doc viewers. The tool does not truncate or wrap columns. Trim keys you don't need in the source JSON, or split into multiple focused tables before converting.
Paste exceeds the character limit
413 over limitThe paste box is capped at the tier's character limit (Free 500,000). Past that, the counter flags the input and the run is blocked. Upload the .json file instead (Free allows 1 MB), or upgrade — Pro raises the cap to 5,000,000 characters / 10 MB.
Duplicate keys in one JSON object
Last value winsJSON objects can technically contain a repeated key in the text; JSON.parse keeps the last occurrence, so the column appears once and shows the final value. This is standard JSON parsing behavior, not a tool decision.
Frequently asked questions
Which keys become the columns — the first object's, or all of them?
All of them. The tool builds the union of every key across every object in the array, in order of first appearance. So if object 1 has id, name and object 2 has id, email, the columns are id, name, email. Objects missing a column get an empty cell for it. (Some older help text said 'the first object's keys' — the actual code uses the union.)
How are nested objects handled?
A nested object value renders as the literal text [object Object], because the tool coerces each value with JavaScript's String() rather than JSON.stringify. To keep nested data readable, flatten the JSON first (promote user.name to a top-level name key) before pasting.
How are array values handled?
An array value is coerced with String(), which joins elements with commas: ["a","b"] becomes a,b. Nested arrays collapse the same way. If that's ambiguous, pre-serialize the field to a clear string in your source data.
What happens to null and missing values?
Both become empty cells. null is coerced via value ?? "" to the empty string, and a key that's absent from an object reads as undefined and is also blank. The output never contains the literal word 'null' from these cases.
Are there any options or settings?
No. This tool has no options at all — no header toggle, no column picker, no delimiter or alignment setting. You paste (or upload one .json file) and press Run. If you need configuration like a header-row toggle, the sibling csv-to-md-table tool has a hasHeader option.
Can I paste a single object instead of an array?
Yes. A non-array value is wrapped in a one-element array, so a lone {...} produces a one-row table. This is convenient for documenting a single config or record without adding array brackets.
What if my JSON is invalid?
The output is the literal text Invalid JSON input.. The tool parses the whole input as one JSON document, so trailing commas, single quotes, comments, or JSONL (newline-delimited JSON) all fail. Pretty-print and validate first.
Why did I get 'No object keys found.'?
Your array contains primitives, not objects — e.g. [1, 2, 3] or ["a", "b"]. There are no keys to turn into columns. Wrap each value in an object, like [{"value": 1}], to get a single-column table.
Does it escape special Markdown characters?
It escapes pipe characters (| becomes \|) so values can't break column boundaries. It does not escape other Markdown like * or _, and it does not escape newlines — a newline inside a string value will break the row. Strip newlines from cell values before converting if you have free-text fields.
What output format is produced?
Standard GitHub-Flavored Markdown pipe-table syntax: a header row, a --- separator row, then one row per object. It renders on GitHub, GitLab, Obsidian, VS Code preview, and most CommonMark+GFM viewers. The output type is Markdown text.
Is my data uploaded anywhere?
No. The conversion runs entirely in your browser tab. JSON content is never sent to a server. The only server-side write is an anonymous usage counter for signed-in dashboard stats, which contains no content and can be turned off in account settings.
How big a file can I convert?
Free tier allows a 1 MB uploaded file or 500,000 pasted characters. Pro raises that to 10 MB / 5,000,000 characters, Pro-media to 50 MB / 20,000,000, and Developer to 500 MB / unlimited. The paste box uses the character limit; uploads use the byte limit.
What's the difference between this and the CSV-to-table tool?
csv-to-md-table takes CSV (with an optional hasHeader setting and newline-to-space cell cleaning) and uses PapaParse; this tool takes JSON and derives columns from object keys. If your table comes out misaligned after editing, md-table-repair re-aligns existing Markdown tables.
Privacy first
All Markdown processing runs locally in your browser using JavaScript. No file is ever uploaded to JAD Apps servers — only metadata counters are saved for signed-in dashboard stats.