Markdown Table to JSON Converter
Turn a documented table into data a program can use. Each row becomes an object keyed by the header, with numbers as numbers and empty cells as null.
To convert a Markdown table to JSON, paste the table or the whole document above. Each row becomes one object keyed by the column headers, and each column is given a single type decided from all of its values, so no key is a number in one object and a string in the next. Empty cells become null. Choose pretty or minified, then copy or download.
Want to reshape it before you use it? Open the app
The document was the spec, now the code needs it
The pattern is always the same. A table in a design doc lists the retry limits per service, or the feature flags per plan, or the rate limits per tier. It was written for a person to read. Now something has to enforce it, and the code wants a lookup, not a paragraph.
The other frequent case is a language model. Ask one to compare six libraries and you get a Markdown table, which is the right format for reading and the wrong format for doing anything else with. Converting it costs a paste.
Both want the same output: an array of objects keyed by the header row, typed well enough to use without a cleaning pass.
Worked example
A table from a release doc, with an empty cell and a line break in it:
| service | version | released | replicas | notes |
|:--------|--------:|:--------:|---------:|:------|
| billing-api | 2.4.1 | 2026-01-08 | 6 | rollout paused<br>resumed 09 Jan |
| web-frontend | 5.0.0 | 2026-01-12 | 4 | canary 10% \| full 14 Jan |
| search-index | 1.19.3 | 2026-01-15 | 12 | |
And the JSON:
[
{
"service": "billing-api",
"version": "2.4.1",
"released": "2026-01-08",
"replicas": 6,
"notes": "rollout paused\nresumed 09 Jan"
},
{
"service": "web-frontend",
"version": "5.0.0",
"released": "2026-01-12",
"replicas": 4,
"notes": "canary 10% | full 14 Jan"
},
{
"service": "search-index",
"version": "1.19.3",
"released": "2026-01-15",
"replicas": 12,
"notes": null
}
]
replicas is the only numeric column, and it is numeric because every value in it is an exact integer. version stays a string, which matters: 5.0.0 is not a number at all, and 2.4 written as one would come back as 2.4 and then compare wrongly against 2.10. The empty notes cell is null, not "". The line break survives as \n and the escaped pipe as a plain pipe.
One type per column, decided from the whole column
A column is numeric only when every non-empty value in it round trips exactly through a number. That rule is conservative on purpose, and it is what prevents the most common failure in a generated JSON file: the same key being a number in one object and a string in the next, which breaks any schema check and most typed clients.
The visible consequence is that a version column, a semantic version, an ID with a leading zero and a price with a trailing zero all stay strings. If you want 2.0 as a number you can cast it downstream, and you will have made that choice deliberately rather than discovering it.
Booleans work the same way: a column of lowercase true and false becomes real booleans, and a column of Y and N stays text, because Y is not a boolean in JSON and pretending otherwise loses the distinction between Y, y and yes.
Empty cells, and why they are null
An empty Markdown cell becomes null, not an empty string. In almost every consumer those mean different things: null is "no value here" and an empty string is a value that happens to be zero characters long. Config loaders apply defaults for one and not the other.
Every object carries every key, including the null ones. A key that disappears when its value is empty makes the shape of the array change row by row, which is exactly what a typed consumer cannot deal with.
Nesting, and the honest answer about it
A Markdown table is flat, so the JSON is flat. A column called limits.retries becomes a key spelled limits.retries, not a limits object with a retries inside it.
That is a deliberate refusal rather than a missing feature. Building structure out of punctuation in a header is a guess, and it goes wrong for every column whose name legitimately contains a dot, which in practice is most of the ones produced by a previous flattening step. If you want nesting, the shape you want is specific to your consumer and a two-line map in your own code will get it right.
Frequently Asked Questions
Why is my version column a string?
Because 5.0.0 is not a number, and 2.4 written as one would come back as 2.4 and then sort wrongly against 2.10. Versions are identifiers, and keeping them as strings is what makes a comparison against them behave.
Do empty cells become null or an empty string?
Null. Those mean different things to almost every consumer, and config loaders apply a default for one and not the other. Every object still carries every key, so the shape of the array does not change row by row.
Can I get one object per line instead of an array?
Yes, the Structure switch emits JSON Lines. Use an array when a person or an API will read the result, and lines when something is going to stream it or bulk-load it.
Does a header like limits.retries create a nested object?
No. It becomes a key spelled limits.retries. Inferring structure from a dot in a header is a guess that breaks every column whose name genuinely contains one, which is common after an earlier flattening step.
What happens to a br inside a cell?
It becomes a real newline in the JSON string, escaped as \n. That is the only way a Markdown cell can hold a second line, so treating it as anything else would lose part of the value.
My document has several tables. Do they all end up in one array?
No. Each table is listed separately with the heading above it as its label, and you pick one. Merging tables with different columns into one array would produce objects with inconsistent keys, which is the thing this page is most careful to avoid.
Related
Turn the documented table into data
One object per row, one type per column, real nulls. Paste and copy.
Back to the converter