How to convert json data to a markdown comparison table
- Step 1Shape entities as a JSON array of objects — One entity per object, attributes as keys:
[{ "plan": "Pro", "price": 12, "seats": 5, "api": true }, ...]. Each object is a row; the keys become the column headers in first-seen order. - Step 2Decide orientation before converting — This tool puts entities as rows. If you want attributes as rows and entities as columns (feature-down-the-side layout), run the array through json-transposer first, then bring the transposed JSON here.
- Step 3Open the tool and drop the file — Drop a
.json,.ndjson,.jsonl, or.txtfile, or an.xlsx/.xlssheet (first sheet read as rows → JSON objects). Parsing is local. - Step 4Pick alignment — Left (
:---) for feature text, Center (:---:) for ✓/✗ support columns, Right (---:) for prices and benchmark numbers. Alignment applies to all columns at once. - Step 5Click Convert to Markdown — The tool writes a header row, an alignment row, and one body row per entity. A single object yields a one-row table.
- Step 6Copy or download into your page — Use Copy or Download MD and paste under a heading you write. The preview truncates very long output at 5,000 characters on screen, but the full table is copied/downloaded.
Orientation: what the tool does vs. what you may want
The single most common surprise with comparison tables. The tool is entity-per-row, keys-as-columns — with no flip toggle.
| Desired layout | Does this tool do it? | How |
|---|---|---|
| Entities as rows, attributes as columns | Yes (default and only mode) | Feed [{plan, price, ...}, ...] directly |
| Attributes as rows, entities as columns | Not directly | Transpose with json-transposer first, then convert |
| ✓ / ✗ support cells | Yes | Put "✓"/"✗" (or true/false) as the value; choose Center alignment |
How comparison values render
Cell rendering rules straight from the conversion logic.
| Value | Cell output | Comparison example |
|---|---|---|
| Number | Verbatim, no rounding | 12345.67 → 12345.67 |
| Boolean | true / false | api: true → true (or map to ✓ in your data) |
| Array (e.g. per-run results) | JSON.stringify inline | [101, 99, 100] → [101,99,100] |
| Missing attribute | Empty cell | A plan without trial_days → blank |
Tier limits
Real configured numbers. A Pro tool; free is size-gated.
| Limit | Free | Pro |
|---|---|---|
| File size | 2 MB | 100 MB |
| Files per run | 1 | 10 (bulk) |
| Runs in | Browser | Browser |
Cookbook
Real comparison datasets turned into Markdown tables, with the exact output emitted.
Pricing tiers → comparison table
ExampleEach plan is a row, each attribute a column. Booleans show as true/false — map them to ✓/✗ in your data if you prefer symbols.
Input (plans.json):
[
{ "plan": "Free", "price": 0, "seats": 1, "api": false },
{ "plan": "Pro", "price": 12, "seats": 5, "api": true }
]
Output (alignment: left):
| plan | price | seats | api |
| :--- | :--- | :--- | :--- |
| Free | 0 | 1 | false |
| Pro | 12 | 5 | true |Flip to feature-down-the-side via transpose
ExampleFor the classic layout with features on the left and products across the top, transpose first. The transposed JSON then has features as objects, which this tool renders as rows — i.e. feature per row, product per column.
Original (this tool → products as rows):
[{ "product": "A", "speed": 100, "ram": 8 },
{ "product": "B", "speed": 120, "ram": 16 }]
After json-transposer (features as rows):
[{ "feature": "speed", "A": 100, "B": 120 },
{ "feature": "ram", "A": 8, "B": 16 }]
→
| feature | A | B |
| :--- | :--- | :--- |
| speed | 100 | 120 |
| ram | 8 | 16 |Benchmark results with numbers right-aligned
ExampleRight alignment lines up the numeric columns so faster/slower is easy to scan. Numbers render exactly as stored — no rounding.
Input:
[
{ "engine": "v8", "ops": 1250000, "ms": 0.8 },
{ "engine": "spidermonkey", "ops": 1180000, "ms": 0.85 }
]
Output (alignment: right):
| engine | ops | ms |
| ---: | ---: | ---: |
| v8 | 1250000 | 0.8 |
| spidermonkey | 1180000 | 0.85 |Per-run results inlined as JSON
ExampleIf a benchmark object carries an array of per-run samples, the whole array is stringified into one cell. Keep raw runs in a separate appendix and put the summary stat in the table for readability.
Input:
[{ "engine": "v8", "runs": [101, 99, 100], "mean": 100 }]
Output:
| engine | runs | mean |
| :--- | :--- | :--- |
| v8 | [101,99,100] | 100 |Plan with an extra attribute gets its own column
ExampleA trial-only plan adds trial_days. The column appears for all rows, blank where the attribute is absent — the union-of-keys behavior.
Input:
[
{ "plan": "Trial", "price": 0, "trial_days": 14 },
{ "plan": "Pro", "price": 12 }
]
Output:
| plan | price | trial_days |
| :--- | :--- | :--- |
| Trial | 0 | 14 |
| Pro | 12 | |Errors and edge cases
Real errors and silent failures sourced from each platform's own documentation. Match the wording to the row, fix what the row says to fix.
Wanting entities as columns
Needs transposeThere is no orientation toggle. The tool always renders entities as rows and keys as columns. For features-down-the-side, run the array through json-transposer first so attributes become the row objects, then convert.
Top-level JSON is an object of entities
One-row tableA shape like { "Free": {...}, "Pro": {...} } (plan names as keys) becomes one row with Free/Pro columns holding stringified objects. Convert it to an array of {plan, ...} objects first — or transpose.
Inconsistent attribute keys across entities
Sparse columnsIf products use different key names for the same attribute (ram vs memory), they become separate columns with half-blank rows. Normalize keys with json-key-renamer before converting so the comparison aligns.
Nested spec objects
Inlined JSONA nested spec like { "cpu": { "cores": 8, "ghz": 3.2 } } puts the whole cpu object, stringified, into one cell. Flatten with json-flattener to get cpu.cores and cpu.ghz as separate comparable columns.
A spec value contains a pipe
PreservedA value like "1|2|4 cores" is backslash-escaped inside the cell so the row keeps its column count. It renders with the pipes visible. Automatic, no toggle.
Invalid JSON
Parse errorJSON.parse rejects trailing commas, comments, and single quotes. Repair with json-format-fixer or locate the issue with json-validator, then convert.
Empty array
Header-onlyNo entities means no keys and no columns. Provide at least one object so the headers can be detected.
Mixed scalar and array values in one column
Mixed cellsIf one product's price is a number and another's is an array of regional prices, the cells render differently (a number vs a stringified array). Normalize the value shape per column before converting for a clean comparison.
File over 2 MB on free
Blocked on freeFree caps input at 2 MB. A large benchmark dataset with many per-run arrays can exceed it. Summarize runs (keep means, drop raw arrays) or upgrade to Pro (100 MB).
Frequently asked questions
Can I put features down the side and products across the top?
Not directly — this tool always renders entities as rows and keys as columns. For the features-down-the-side layout, transpose the array first with json-transposer so attributes become the row objects, then convert the result here.
Is there an orientation toggle?
No. The only formatting control is column alignment. Orientation is fixed (one object per row); change it by transposing your data before conversion.
How do I get ✓ / ✗ support cells?
Put the symbol you want as the value in your JSON ("✓" / "✗"), or store booleans and accept true/false cells. The tool renders values verbatim; choose Center alignment for tidy support columns.
How are columns determined?
As the union of all keys across the entity objects, in first-seen order. An attribute present on only some entities still gets a column, blank where absent.
Are numbers rounded?
No. Numeric values render exactly as stored — 12345.67 stays 12345.67, 1250000 stays 1250000. Round in your data first if you want fewer decimals.
What happens to a nested spec object?
It's JSON-stringified into one cell. To compare nested fields individually, flatten the JSON first with json-flattener so each nested field becomes its own column.
Why are there two columns for the same attribute?
Entities used different key names (ram vs memory). Each distinct key is its own column. Normalize keys with json-key-renamer before converting.
Can I align price/benchmark columns to the right?
Yes, but it's all-or-nothing — Right alignment applies to every column. For a table that's mostly numeric, Right works well; for mixed text-and-number tables, Left is usually the better whole-table choice.
What inputs are accepted?
.json, .ndjson, .jsonl, .txt, and .xlsx/.xls. A spreadsheet's first sheet becomes a JSON array of objects before the table is built — convenient if your comparison lives in a sheet.
Is competitive-analysis data uploaded?
No. Conversion is fully client-side — pricing, specs, and benchmark data never leave your browser. Only an anonymous run count is logged if you're signed in.
How large can the dataset be?
2 MB on free, 100 MB on Pro. Benchmark datasets with many raw per-run arrays are the most likely to approach the free cap.
Can I generate the comparison table in a build step?
Yes — it's a Pro tool with API access. Run it via the local runner in your docs or marketing build to regenerate the comparison table from source JSON. Conversion runs locally, so the data stays on your machine.
Privacy first
Conversion runs locally in your browser. No file is uploaded — only metadata counters are saved for signed-in dashboard stats.