Skip to main content

How to Convert a JSON to Markdown Table for READMEs and Issues

Turn a JSON array of objects into a clean Markdown table where keys become headers. Worked example, alignment separators, pipe escaping, and nested fields.

Published By Li Lei
#json #markdown #developer-tools #documentation

How to Convert a JSON to Markdown Table for READMEs and Issues

A JSON array of objects and a Markdown table describe the same thing: rows of records with named fields. The only difference is punctuation. JSON wraps each record in braces and quotes; Markdown stacks them between pipes. So when you have an API response, a config dump, or a query result sitting in your clipboard as JSON, getting it into a GitHub README, a pull request, or an issue is mostly a mechanical translation. This post walks through that translation by hand so you understand exactly what a tool is doing, then shows where the edge cases hide.

The one rule that does all the work

Object keys become column headers. That is the whole trick.

Take an array of objects. Read the first object, list its keys in order, and those keys are your header row. Each object after that becomes one table row, with its values dropped into the matching columns. A Markdown pipe table needs three pieces: a header line, a separator line directly under it, and one line per data row. Every cell is wrapped in | characters.

Here is the concrete shape. Given two records, the header is built from the keys, the separator is a row of dashes, and each record is a pipe-separated line:

| key1 | key2 |
| ---- | ---- |
| val  | val  |

That | ---- | separator is not decoration. Markdown renderers require it to recognize the block as a table at all. Skip it and GitHub prints your pipes as literal text.

A worked example, JSON array to a finished table

Say you hit an endpoint and get back a small list of team members:

[
  { "name": "Ada", "role": "eng", "active": true },
  { "name": "Linus", "role": "maint", "active": true },
  { "name": "Grace", "role": "eng", "active": false }
]

Walk it through. The first object has three keys: name, role, active. Those become the headers. Each object then contributes one pipe-separated row of its values. The result:

| name  | role  | active |
| ----- | ----- | ------ |
| Ada   | eng   | true   |
| Linus | maint | true   |
| Grace | eng   | false  |

Paste that into a pull request description and the reviewer sees a grid instead of a code block they have to mentally parse. The whitespace padding inside cells is optional. Renderers ignore it, so |Ada|eng|true| renders identically. Padding just makes the raw Markdown readable in the diff, which matters when someone reviews the .md file itself.

If you want this without doing it by hand, the JSON to Markdown Table converter reads the array, builds the headers, and emits the finished table with one click. But the logic above is all it runs.

When objects have different shapes

Real data is rarely uniform. One record carries an extra field, another is missing one. The safe behavior is a key union: collect every key that appears across every object, in first-seen order, and use that full set as the columns. An object that lacks a key gets a blank cell in that position rather than dropping the column.

Consider this:

[
  { "id": 1, "name": "alpha", "team": "core" },
  { "id": 2, "name": "beta" }
]

The union of keys is id, name, team. The second record has no team, so its cell under that column is empty:

| id | name  | team |
| -- | ----- | ---- |
| 1  | alpha | core |
| 2  | beta  |      |

This is exactly what you want when documenting a half-finished schema: the column stays, the gap is visible, and you fill it in later. The alternative, dropping any key that is not present in all records, would silently lose team and quietly mislead anyone reading the table.

Pipe escaping and line breaks

This is where hand-conversion bites you and where a tool earns its place. A pipe character inside a value ends the Markdown cell early. Every column after it shifts left, and the table renders garbled. The fix is to escape any literal pipe to \| before writing the row.

A common offender is a shell command in your data:

cat file | grep error

Written raw into a cell, that single pipe splits one cell into two and breaks the row. Escaped, it becomes cat file \| grep error and renders as one intact cell. Backslashes get escaped too, so a Windows path like C:\Users\me survives instead of being read as escape sequences.

Line breaks inside a value need a decision. A Markdown table row is a single line, so an embedded newline would terminate the row early. Two reasonable answers exist: flatten the newline to a space, or replace it with a <br> tag so the cell shows two visual lines. Pick whichever fits the destination. GitHub honors <br> inside cells; some plainer renderers do not.

Alignment separators and nested fields

The separator row does double duty. Beyond marking the block as a table, it sets column alignment through where you place a colon: :--- for left, :---: for center, ---: for right. Most renderers, GitHub included, read that row and align the whole column to match. For a generated table, one alignment across all columns usually covers it. Center alignment reads well for short numeric columns; left is the sane default for text.

Nested fields are the last trap. A value that is itself an object or array cannot become several columns without a decision about how to flatten it. The clean default is to stringify it back to compact JSON and drop it in a single cell. A field like { "k": "v" } shows up as {"k":"v"} rather than the useless [object Object] you get from naive string coercion. The structure stays visible and copy-paste accurate. If you genuinely need addr.city and addr.zip as their own columns, flatten the JSON first, then convert. To eyeball or repair messy nested JSON before that step, run it through the JSON formatter to pretty-print and validate it.

What I reach for this on

I file a lot of issues that boil down to "here is the data, here is what is wrong with it." Early on I would paste the raw JSON array straight into the issue body, and every reviewer had to scroll a code block counting braces. The first time I converted a six-record array to a Markdown table before posting, the back-and-forth on that issue dropped from a dozen comments to two. The data was identical. The format did all the work. Now any time a payload is going in front of another human rather than a parser, it goes through a table first. The escaping is the part I will not do by hand anymore, because the one time a pipe in a command string broke the table silently, nobody noticed the shifted columns until a wrong value got copied out of the wrong cell.

The short version

Object keys become headers, the separator row defines alignment and makes Markdown recognize the table, and each object becomes one pipe-separated row. The two things to get right are escaping literal pipes so they do not break the row, and keeping the key union so mixed-shape records still line up. Get those two right and a JSON array drops cleanly into any README, issue, or changelog.


Made by Toolora · Updated 2026-06-13