Paste a JSON array and get a Markdown table for the docs, the PR description or the incident write-up. Every key that appears in any object becomes a column, so a record that lacks a field shows an empty cell in that column instead of shifting its other values one column to the left. Set Align to left, center, right or numbers right to add the colons renderers use for column alignment.
[
{ "id": 1, "name": "Ada Lovelace", "role": "admin", "tags": ["math", "engines"] },
{ "id": 2, "name": "Grace Hopper", "role": "editor", "tags": ["cobol"] },
{ "id": 3, "name": "Linus", "role": null, "active": false }
] | id | name | role | tags | active |
| --- | ------------ | ------ | ------------------ | ------ |
| 1 | Ada Lovelace | admin | ["math","engines"] | |
| 2 | Grace Hopper | editor | ["cobol"] | |
| 3 | Linus | | | false |
The third object has no tags and adds an
active key, so the table has both columns and the
gaps show as empty cells. A null value also
renders as an empty cell.
Keys appear in the order they are first seen: all of the first object's keys, then any new ones from later objects, appended on the right.
The tool writes them into the cell as compact JSON, such as ["math","engines"]. They are not flattened into dotted columns, so the column count stays the same as the number of top-level keys.
A | in a string is escaped as \| and a newline becomes <br>, because a row has to stay on one line.
A single object becomes a one-row table. An array of plain values (strings, numbers) becomes one column called value.
Trim the objects to the fields your reader needs before
you convert. Markdown tables do not wrap well: once the
columns outgrow the page, GitHub adds a horizontal scrollbar,
and each raw row becomes a line too long to review. Cut the
fields down with
jq 'map({id, name, role})', or with the JSONPath
tester if you only want part of a larger response.
const data = [
{ id: 1, name: "Ada", tags: ["math"] },
{ id: 2, name: "Grace | Hopper", active: true },
];
// Union of keys, in the order they first appear
const keys = [...new Set(data.flatMap((o) => Object.keys(o)))];
const show = (v) =>
v === undefined || v === null ? "" : typeof v === "object" ? JSON.stringify(v) : String(v);
const cell = (v) => show(v).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
const line = (cells) => `| ${cells.join(" | ")} |`;
const md = [
line(keys.map(cell)),
line(keys.map(() => "---")),
...data.map((o) => line(keys.map((k) => cell(o[k])))),
].join("\n");
console.log(md); import json
data = json.loads('[{"id": 1, "name": "Ada", "tags": ["math"]},'
' {"id": 2, "name": "Grace | Hopper", "active": true}]')
# dict.fromkeys keeps first-seen order and drops duplicates
keys = list(dict.fromkeys(k for row in data for k in row))
def cell(v):
if v is None:
s = ""
elif isinstance(v, (dict, list)):
s = json.dumps(v, separators=(",", ":"))
elif isinstance(v, bool):
s = "true" if v else "false"
else:
s = str(v)
return s.replace("\\", "\\\\").replace("|", "\\|").replace("\n", "<br>")
def line(cells):
return "| " + " | ".join(cells) + " |"
out = [line(cell(k) for k in keys), line("---" for _ in keys)]
out += [line(cell(row.get(k)) for k in keys) for row in data]
print("\n".join(out)) The bool check matters: str(True) gives True, not the JSON spelling true.
<?php
$json = '[{"id": 1, "name": "Ada", "tags": ["math"]},
{"id": 2, "name": "Grace | Hopper", "active": true}]';
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
$keys = [];
foreach ($data as $row) {
foreach (array_keys($row) as $k) $keys[$k] = true;
}
$keys = array_keys($keys);
$cell = function ($v): string {
if ($v === null) $s = '';
elseif (is_array($v)) $s = json_encode($v, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
elseif (is_bool($v)) $s = $v ? 'true' : 'false';
else $s = (string) $v;
return str_replace(["\\", '|', "\r\n", "\n"], ["\\\\", '\|', '<br>', '<br>'], $s);
};
$line = fn(array $cells) => '| ' . implode(' | ', $cells) . ' |';
$out = [$line(array_map($cell, $keys)), $line(array_fill(0, count($keys), '---'))];
foreach ($data as $row) {
$out[] = $line(array_map(fn($k) => $cell($row[$k] ?? null), $keys));
}
echo implode("\n", $out), "\n"; Decoding with true as the second argument gives associative arrays, so nested objects and lists both go through json_encode.
This tool follows the specifications below. Read them when you need the exact normative wording, for example to settle an edge case between two implementations.
Paste the array into the JSON Input pane and the table appears on the right. Each object becomes a row and each key becomes a column; click Copy Markdown to take it.
Every key found in any object becomes a column, in the order it was first seen. Objects that lack a key get an empty cell in that column.
The tool writes them into the cell as compact JSON, for example ["a","b"] or {"x":1}. They are not flattened into separate columns, so the table has one column per top-level key.
null and missing keys both render as an empty cell. true and false are written as text.
The input failed to parse, and the line and column point to where the parser gave up. Trailing commas, single quotes and unquoted keys are the usual causes, since JSON allows none of them.
Set Align. Left, center and right put colons in the separator row of every column, so :---: centers and ---: right-aligns, which GitHub, GitLab and most other renderers honor. Align: none writes plain dashes and leaves the choice to the renderer, which usually means left. The Preview tab shows cell content only, so check alignment in the Markdown itself or after pasting.
Choose Align: numbers right. The tool checks each column, and when every non-empty cell is a number, including forms like 1,200, -3.5 and 12%, that column gets ---: so the digits line up on the right. Text columns keep plain dashes. A column with a single non-numeric value such as n/a stays unaligned, so clean those cells first.
With it on, cells are padded with spaces so the pipes line up and the raw Markdown reads as a table in a code review or a plain text editor. The rendered table is identical either way. Untick it for a compact table in a file that is diffed often, since padding means one longer value changes the width of every row in that column.
JSON to Table shows the same data as an HTML table you can copy into an email. For CSV input use CSV to Markdown Table, and to check the JSON itself first, run it through the JSON formatter.