JSON to HTML Table

Turn a JSON array into a styled HTML table. Nested keys flatten to dot notation, columns can be trimmed and reordered, then copy the markup or the table.

๐ŸŒ Espaรฑol

๐Ÿ”’ Private by design: everything runs locally in your browser and never uploaded to any server.

Paste, build, trim, and take the markup

  1. Paste a JSON array into the box at the top, or press Load .json file to open one from your device, or Load sample data for two records to experiment on.
  2. Press Build table. A line above the controls reports how many rows and columns were detected, and everything below it appears at once.
  3. Under Columns, untick anything that does not belong on the page and use the up and down arrows to order what is left.
  4. Choose one of the five entries under Styling preset and watch Preview and the HTML box redraw as you go.
  5. Finish with Copy HTML, Copy rendered table or Download .html. Start over empties the box and clears the table.

The data behind Load sample data is two rows about Ada Lovelace and Alan Turing, with name, role and born columns. It exists because reading the generated markup next to the data it came from explains this tool faster than any description does.

The column list is where most of the work happens

A raw API response almost never has the column set you want to publish. Internal IDs, timestamps and nested metadata arrive alongside the three fields anyone reading the page cares about, and trimming that list is the part of the job a straight conversion cannot do for you.

Every key seen anywhere in your data appears in the list, in the order it was first encountered while scanning the rows from the top, so a key that exists only on the last record sits at the far right until you move it. Rows missing a key are not a problem: they get an empty cell in that column rather than shifting every later column across, which is the failure mode of tables assembled by hand from inconsistent data.

Two behaviours are worth learning early. Unticking a column drops it out of the kept order and moves its entry below the ticked ones, where it loses its arrows; ticking it again puts it back at the end rather than where it was. And rebuilding resets the whole selection, so do the trimming once the final JSON is in the box.

Flattening borrowed wholesale from the CSV converter

Nested structure is the one thing a grid cannot represent, and this page does not invent its own answer. It imports the flattening and the column derivation from Convert JSON to CSV, so both tools treat identical input identically.

An object nested inside a record becomes dot-notation columns, to any depth, so {"user":{"city":"NYC"}} gives you a column headed user.city. An array value is not exploded into tags.0 and tags.1, because that would make the column count depend on the longest array in the data; it is written into a single cell as its JSON text instead. A null and a missing key both become an empty cell, since HTML has no null either.

One consequence catches people out. A top-level key literally named user.city produces exactly the same heading as a nested city inside a user object, and nothing in the finished table records which one it was.

What the striped preset writes into every row

There is no stylesheet, no class attribute and no <style> block in the output. Every preset that styles anything at all is a set of inline style attributes written onto the <table>, <th>, <td> and <tr> elements themselves, which is the only styling that survives being pasted into an email client, a CMS editor or a wiki.

Build the sample and switch to Plain (no styling) to see the bare structure the other four decorate:

<table>
  <thead>
    <tr><th>name</th><th>role</th><th>born</th></tr>
  </thead>
  <tbody>
    <tr><td>Ada Lovelace</td><td>Mathematician</td><td>1815</td></tr>
    <tr><td>Alan Turing</td><td>Computer scientist</td><td>1912</td></tr>
  </tbody>
</table>

On Striped rows, the default, the table opens with border-collapse:collapse;width:100%;font-family:sans-serif;font-size:14px;, each header cell carries a padded left-aligned style with a #eef1f5 background, each body cell gets padding:8px 12px;, and every second row counting from zero picks up background:#f7f9fb; on the <tr> itself, so the Ada row is untinted and the Turing row is tinted. Striping is unique to that preset: Bordered puts a line around every cell, Dark mode swaps in dark backgrounds with pale text, and Minimal drops the header fill in favour of a rule underneath it.

Values are escaped on the way in, so a cell holding <script>alert(1)</script> is written as text with its angle brackets turned into entities. That is what makes the live preview safe to render, since the preview is the generated markup inserted into this page.

Copy HTML, copy the rendered table, or download table.html

The three output buttons are genuinely different, and picking the wrong one is the most common way to end up pasting source code into a document.

Copy HTML puts the markup on the clipboard as plain text, which is what a template, a code editor or a CMS in source mode wants. Copy rendered table writes both an HTML and a plain-text flavour, so a word processor or a mail composer receives a real table with its styling already applied while a plain-text target still gets the source. If the browser refuses clipboard access, a message asks you to select the text and copy it by hand. Download .html saves the markup as table.html, the table element on its own with no doctype or <body> wrapper, so add one if you need a standalone page.

The HTML Table Generator is the neighbour to know about: a grid you type or paste into, importing these exact five preset definitions rather than defining its own, so a Bordered table looks identical whichever tool made it. It also handles merged cells, which the converter here has no way to express. For a README or a wiki that speaks Markdown instead, the Markdown Table Generator produces GFM pipe tables.

Odd inputs, and the two messages that stop it

The parser is deliberately lenient about what counts as a table. A single top-level object, not wrapped in an array, becomes a one-row table with its keys as columns, and an array of plain values such as ["x","y"] becomes a one-column table headed value.

Exactly two inputs are refused, each with a message above the controls. Text that will not parse reports that it does not look like valid JSON and suggests checking for a missing comma, bracket or quote. A genuinely empty array is rejected too, because zero rows give no column set to derive anything from. Either refusal also takes down whatever table was on screen, so a failed rebuild leaves you with the message and nothing else.

When the source will not parse and you cannot see why, the JSON Formatter is the fastest check and the JSON Editor lets you repair the document as a tree. Data that starts life in a spreadsheet can come through Convert Excel to JSON, which returns a plain array of objects for a single-sheet workbook and an object keyed by sheet name for a multi-sheet one unless you untick its include-every-sheet option. And if you want to read and sort the data rather than publish it, the CSV Viewer opens a .csv or .tsv file as a sortable, searchable grid.

See it in action

Screenshot of the JSON to HTML Table tool with a converter that turns a JSON array into an HTML table, started from pasted JSON, a loaded .json file or the built-in sample data
JSON to HTML Table mid-process: a converter that turns a JSON array into an HTML table, started from pasted JSON, a loaded .json file or the built-in sample data.
Diagram: where the work happens on a SysFenix page that has no file input at all: the tool arrives as ordinary JavaScript inside the page, works the answer out on your own device and renders it in place, so the upload, queue and server-side record a typical online tool needs never happen
Where the work happens on a SysFenix page that has no file input at all: the tool arrives as ordinary JavaScript inside the page, works the answer out on your own device and renders it in place, so the upload, queue and server-side record a typical online tool needs never happen.

Frequently asked questions

I edited the JSON in the box but the table did not change.

Nothing is recomputed from the text until you press Build table again. The parse happens on that click and only on that click, so an edit sits in the box until you rebuild. The controls below behave the other way round, since ticking a column, moving one with the arrows or switching the styling preset redraws the preview and the markup straight away.

Why did a column jump to the end of the list after I unticked and reticked it?

Because unticking removes it from the kept list and ticking it again appends it at the end rather than restoring its old position. That is also why an unticked column drops below the ticked ones, where it keeps its checkbox but loses its up and down arrows. Walk it back with the arrows, which swap it one place at a time and refuse to move past either end.

Can I keep my chosen columns when I build a second payload?

No. Every build starts the selection over from the full set of detected columns, in the order each key first appeared, so rebuilding throws away the trimming and reordering you did to the previous data. Get the final JSON into the box first and do the column work once, rather than tidying a draft and then pasting the real thing.

An array value shows up in the markup with strange quote entities.

An array is turned into its JSON text first and then escaped for HTML, so the double quotes inside it arrive as the entity &quot; in the source. It displays as ordinary quotes in the preview and anywhere you paste it. Arrays are deliberately never split into numbered columns, which keeps the column count independent of how long any one row's array happens to be.

A cell came out empty. Was the key missing, or was the value null?

You cannot tell from the table, and neither can the table. A key absent from that object and a key holding an explicit null both render as an empty cell, exactly as they do in the CSV converter this page borrows its flattening from. Where the difference matters downstream, write a visible sentinel such as the word none into the source JSON before you build.

Which styling preset is active before I touch the dropdown?

Striped rows, not Plain (no styling). The stripe lands on every second body row counting from zero, so the first row stays untinted and the second picks up a pale background, while the header row is styled separately from both. Plain (no styling) is the one preset that emits no style attributes anywhere, which is what you want when the destination already has its own table CSS.

Related tools