JSON to HTML Converter

Paste an API response, pick the layout you want, then take the markup. An array of objects becomes a table with a header row. Everything else becomes a nested list or a definition list, whichever reads better for the shape you have.

Layout

JSON in

Ready

HTML out

Waiting for input

Three layouts, one object

The layout buttons above change the elements the converter reaches for, nothing else. Same input, same escaping, different container. Here is a two key object under each one.

{"code": "NW-4", "onTimeRate": 0.91}

Table

A key column and a value column, with <th scope="row"> on the key so screen readers announce it as the row heading.

<table class="jx-table jx-pairs"><tbody><tr><th scope="row">code</th><td>NW-4</td></tr></tbody></table>

Nested list

Objects become <ul>, arrays become <ol> so the index stays visible. The lightest option on a phone.

<ul class="jx-list"><li><span class="jx-key">code</span> NW-4</li></ul>

Definition list

A <dt> per key with its <dd> underneath. The right pick when the output is documentation rather than data.

<dl class="jx-dl"><dt>code</dt><dd>NW-4</dd></dl>

One rule sits above the layout choice. An array whose items are all objects becomes a real <table> with a <thead> in table mode, because a row set is the one JSON shape a table was built for. Arrays of numbers or strings never become tables. They become a list, since a single column table is a list wearing borders.

A table needs rows that agree

Header columns come from the union of every key in the array, in first seen order. Load the sample and look at the carriers table. Two carriers have five keys. Baltic Rail carries a sixth key, note, so the header grows to six columns and the other two rows get an empty cell marked class="jx-missing".

Empty cells are honest output, not a bug, though they are worth reading as a signal. A response where half the rows are missing half the columns is usually two record types sharing one array. Splitting the array upstream produces two clean tables instead of one ragged one. When the shape is genuinely optional data, style .jx-missing with a dash or a muted background so a reader tells a blank apart from a zero.

Key order comes from the JSON itself. Objects keep insertion order, so the column order matches the order your API wrote the fields. Sorting the keys before pasting is the way to force a different column order.

Escaping happens before anything reaches the page

JSON strings hold whatever the source system stored, and that includes markup. A product name of <script>alert(1)</script> converts to escaped text, never to a live tag. Five characters are replaced every time a key or a value is written out: &, <, >, ", and '. Keys go through the same path, since a hostile key name is as dangerous as a hostile value.

Two string shapes get promoted to links. A value matching https:// or http:// end to end becomes an anchor, and a plain address becomes a mailto: link. The match has to cover the whole string. A sentence with a URL inside it stays text, which keeps the converter from guessing at prose. The Rendered tab writes the output into a sandboxed iframe with scripting switched off, so previewing untrusted data stays contained.

Escaping protects the page you paste into. It says nothing about whether the data belongs there. Salaries, tokens and internal IDs are still readable to whoever opens the HTML.

Class names, styles and the wrapper

The class prefix field renames every generated hook at once. Set it to report and you get report-table, report-list, report-key. Clearing the field drops the prefix entirely and leaves bare names like table and key, which suits a page where those classes already exist in your stylesheet.

Include style block adds a small <style> element above the markup with borders, header shading and colored numbers. It is a legible default, not a design. Turn it off once the fragment lives inside a site with its own CSS, otherwise the block competes with your own rules on every paste.

Full HTML page wraps the result in a doctype, a head with the charset and viewport meta tags, and a <main> element. Use it when the download is meant to open on its own. Leave it off when the output is going into a Blade template, a React component or a CMS field.

Where this stops short

  • Nothing is paginated. A 10,000 row array produces a 10,000 row table, which is a slow paint in the Rendered tab and a slower one in a browser you ship it to.
  • Deep nesting in table mode puts tables inside table cells. Past three levels this gets hard to read on a narrow screen. Nested list mode handles depth much better.
  • Columns are neither sorted nor filtered nor reordered. The output is static markup with no JavaScript attached.
  • Arrays of arrays become nested ordered lists rather than a grid, because JSON gives no header row to build one from.
  • Dates, currency and units stay exactly as written. 0.91 renders as 0.91, not as 91 percent.
  • Styling is class based, so the output is not ready for HTML email. Mail clients need inline styles, which means running the fragment through an inliner first.
  • Malformed input is repaired before parsing, so trailing commas and single quotes get through. A repair changes what you meant sometimes, so check the output against the source when the input was broken.

The same conversion in your own code

Pasting works for a one off. A recurring report belongs in the codebase. In PHP, a row set is a foreach over the union of keys.

$rows = json_decode($payload, true, 512, JSON_THROW_ON_ERROR);$cols = array_keys(array_merge(...array_map('array_keys', $rows)));echo '<table><thead><tr>';foreach ($cols as $col) {echo '<th scope="col">' . htmlspecialchars($col, ENT_QUOTES) . '</th>';} echo '</tr></thead><tbody>';foreach ($rows as $row) {echo '<tr>';foreach ($cols as $col) {echo '<td>' . htmlspecialchars((string) ($row[$col] ?? ''), ENT_QUOTES) . '</td>';} echo '</tr>';} echo '</tbody></table>';

In a Blade view the escaping is already handled, so the loop shrinks to the structure.

<table><thead><tr>@foreach($cols as $col)<th scope="col">{{ $col }}</th>@endforeach</tr></thead><tbody>@foreach($rows as $row)<tr>@foreach($cols as $col)<td>{{ $row[$col] ?? '' }}</td>@endforeach</tr>@endforeach </tbody></table>

The rule worth carrying over from this page is the escape call on every key and every value, including the ones you believe are safe. Data that looks clean in staging arrives with an ampersand in it eventually.

Questions about converting JSON to HTML

Does my JSON get sent to a server?

No. Parsing and markup generation run in this page as JavaScript, so the data never leaves the browser. Closing the tab is all the cleanup there is.

Why did my array become a list instead of a table?

Table mode builds a real table only when every item in the array is an object. An array of strings or numbers has no columns to name, so it renders as a list. Wrapping each value in an object with a key of your choice turns it into a one column table.

Some table cells came out empty. What happened?

The header row is the union of keys across all items, so a key present on one record and missing on another leaves an empty cell on the records without it. Those cells carry a jx-missing class, ready to style with a dash or a muted background.

Is the output safe to drop into a live page?

Every key and value is HTML escaped, so markup inside a string renders as visible text rather than executing. Two exceptions are deliberate: a value that is entirely a URL becomes an anchor, and an address becomes a mailto link. Review those if the source data is untrusted.

How do I fit the markup to my existing stylesheet?

Set the class prefix to the namespace your CSS already uses, or clear the field to get unprefixed names. Then switch off the style block so the fragment carries no styles of its own and inherits yours.

What is the largest file this handles?

A few megabytes of JSON converts fine. Past that the Rendered tab slows down before the markup generation does, since laying out tens of thousands of table rows is the expensive part. For anything larger, run the conversion server side and paginate the result.

Will it fix broken JSON?

Common damage gets repaired before parsing: trailing commas, single quoted strings, unquoted keys. The repair is a best guess, so compare the output against your source when the input was malformed rather than trusting it blindly.