Which JSON shape becomes which table
“json to markdown” and “json to markdown table” usually mean one of four shapes, and this page treats them differently on purpose. An array of objects, the usual API list, becomes a table whose columns are keys. An array of plain values (strings, numbers, booleans, or null) becomes a single column named Value. An array of arrays becomes rows, and the first inner array is the header, so a matrix that already includes labels keeps those labels. A nested object inside one of those cells stays in the cell: dotted pairs such as city: Taipei when flatten is on, or JSON in backticks when it is off. It does not grow new columns. One object becomes two columns, Key and Value. A top-level string, number, boolean, or null becomes a Value column with one data row. An empty array or an empty object does not invent a row. The status line says there is nothing to copy.
Column order for objects is the order keys are first seen, not alphabetical order. The converter walks the first object from top to bottom, then appends any new key from the next object. A later object that omits a key gets a blank cell. A later object that adds a key adds a column at the end. That is the stable order you can describe to someone else: it matches the file, not a sort. If the array mixes objects with plain values or with inner arrays, the page does not guess a schema. Each item becomes one row under Value, and the status line says the array was mixed.
Numbers, booleans, and null are formatted after JSON.parse. null is the word null, so it is not the same as a missing key. true and false stay words. A number is JavaScript’s default string for that number: a trailing “.0” disappears, and scientific notation such as 1e2 becomes 100. Integers larger than 9007199254740991 cannot be exact in ordinary JSON.parse, and this page does not swap in another parser, so those ids should be quoted strings in the source if you need every digit. Strings are copied as text. A newline, a carriage return, or a Unicode line separator in a string is written as a break tag inside the cell, because a pipe table is one line per row. A pipe character is escaped.
Nested data, lists, and JSON Lines
API objects are rarely flat. Flatten nested keys, on by default, walks objects and arrays and builds names like profile.city and tags.0. The index is a decimal segment, not brackets, so it stays a single Markdown header cell. An empty nested object still keeps its key, with a blank value, so you can see that the field was present. Turn flatten off when you would rather read the nested JSON than spread it across columns. The cell then holds JSON.stringify of that value, wrapped in backticks. If the JSON itself contains a backtick, the fence grows until it no longer collides. Pipes inside that JSON are escaped the same way as any other cell, so the table does not gain a surprise column. The small object below is flatten off and pretty-print off, which is not the default you get from Sample JSON.
| Key | Value |
| --- | --- |
| profile | `{"city":"Taipei","note":"a\|b"}` |Output as list instead of table is for notes and for prompts that read better as bullets than as a wide pipe row. A single object lists each key in bold, and nests child objects and arrays underneath. An array of objects numbers each item, then nests that item’s keys. Flatten does not apply in list mode, and pretty-print does not either. The switches are disabled while the list is on so the screen does not pretend they change the bullets. An empty string in a list is written as (empty). In a table, an empty string stays an empty cell, which is different from null. The list below is the converter with the list switch on. Sample JSON does not flip that switch for you.
Object
{
"name": "Mei",
"profile": { "city": "Taipei", "tags": ["editor"] },
"active": false
}Nested list
- **name:** Mei
- **profile:**
- **city:** Taipei
- **tags:**
- editor
- **active:** falseModels that emit structured output often return one JSON object, or a fenced block you can paste after you remove the fence. Paste the JSON only. A leading ```json line is not JSON, and the error will point at it. JSON Lines is the other shape this page accepts: a log or an export where each line is one value. The whole text is parsed first. Only when that fails, and every non-empty line parses on its own, are the lines treated as records. Blank lines are skipped. The status line reports the record count. A pretty-printed array is one value, not JSON Lines, because the bracket lines are not JSON by themselves. Comments and trailing commas are syntax errors. The two-line log below is JSON Lines, pretty-print off.
{"id":"usr_1","name":"Mei","active":true}
{"id":"usr_2","name":"Omar","active":false,"note":null}Read 2 JSON Lines records because the paste is not a single JSON value.
| id | name | active | note | | --- | --- | --- | --- | | usr_1 | Mei | true | | | usr_2 | Omar | false | null |
A worked example from an API array
Sample JSON is this array. The first user has a nested profile, two tags, a boolean, a fractional score, and a note that contains a pipe. The second user has a null score, no tags, and an extra retired flag. With flatten on, list off, and pretty-print on, the columns run id, name, active, score, profile.city, profile.tags.0, profile.tags.1, note, then retired. Mei’s retired cell is blank because that key is absent. Omar’s tag cells are blank because those keys are absent. His score is the word null because the key is present and the value is null. The pipe in the note is escaped. Pretty-print pads the raw columns; Preview does not need that padding.
JSON
[
{
"id": "usr_1",
"name": "Mei Lin",
"active": true,
"score": 12.5,
"profile": { "city": "Taipei", "tags": ["editor", "zh"] },
"note": "Uses | pipes"
},
{
"id": "usr_2",
"name": "Omar",
"active": false,
"score": null,
"profile": { "city": "Amman" },
"retired": false
}
]Markdown
| id | name | active | score | profile.city | profile.tags.0 | profile.tags.1 | note | retired | | ----- | ------- | ------ | ----- | ------------ | -------------- | -------------- | ------------- | ------- | | usr_1 | Mei Lin | true | 12.5 | Taipei | editor | zh | Uses \| pipes | | | usr_2 | Omar | false | null | Amman | | | | false |
A broken payload should say where it broke. This fragment is missing a value after the colon. The converter does not invent one. The message under the fragment is the same string the tool shows for that paste, including the line and column.
{
"sku": }Invalid JSON at line 2, column 10: Unexpected token '}', "{ "sku": }" is not valid JSON
Limits, hand conversion, and the extension
Copying keys into a Markdown table by hand is fine for a three-field object. It slips when the second record has a key the first one lacked, when a string contains a pipe or a newline, or when a nested array should become tags.0 and tags.1 rather than the text [object Object]. This page does that bookkeeping in the browser. It refuses a paste longer than 5,000,000 characters, and the file input refuses a file larger than that many bytes, so a multi-megabyte log does not have to be parsed to show you an error. Under the cap, tables still stop at 1,000 rows and 100 columns. Lists stop at 1,000 top-level items. The status line names the cut. A wide flatten, dozens of dotted keys, hits the column cap the same way. Those status counts are the rows and columns actually written, after the cap. A payload that is valid JSON but nested thousands of levels deep does not become a table either. The converter stops and says the value is too deep for the browser call stack, instead of letting the page throw.
The result is text you can put in a GitHub issue, an Obsidian note, or a prompt. It is not a schema validator and it does not call the remote API for you. Paste the body you already have. When the job is a docs diff or one public fetch instead of a payload you already hold, that split is the developer use case. If that body is HTML from a response you copied out of devtools, use HTML to Markdown instead. If you need the pipe table as HTML, continue in Markdown to HTML. If the prompt budget matters, paste the Markdown into the Token Counter. That counter is explicit about OpenAI encodings. A delimited spreadsheet export is CSV to Markdown. Per-column alignment and a grid you type into live on the Markdown Table Generator. The tools hub is the index when you are not sure which input you have.
If the body is an HTML error page and you want the words without tags, HTML to Text is the page for that. This page stays on JSON values.
The Chrome extension converts the document in a tab you already opened, including pages that require your session. It writes Markdown from that page. It does not parse a JSON file you downloaded, and this tool does not see cookies or a logged-in response. If the JSON only exists behind a login, fetch or copy it yourself, then paste it here. Do not expect the URL tool to turn an authenticated JSON endpoint into this table. After the table exists, Preview on this page uses the same Markdown renderer as the other tools, so a broken pipe is visible before you commit the file.
| Job | This page | By hand | CSV to Markdown |
|---|---|---|---|
| API array of objects | Union of keys | Retype each field | Only if you flatten first |
| Nested object | a.b.c columns, or JSON in a cell | Easy to drop a key | No nesting |
| JSON Lines log | One record per line | Join the lines yourself | Wrong parser |
| Runs in the browser | Yes | Yes | Yes |
The extension clips web pages. It does not open a JSON file.