Skip to content
Back to Blog
By JSONConvert Team··6 min read

JSON to HTML Table: A Practical Guide

An API hands you a JSON array and you want to show it as a table on a page. No React, no data grid library, just rows and columns. The mechanical part is a loop, but real API data has missing keys, nested objects, and values that will break your markup if you paste them in raw. This guide covers how to turn JSON into an HTML table that survives messy input.

The Shape That Converts Cleanly

A table wants a flat array of objects, where each object is one row and each key is one column. This is the shape most list endpoints return:

That maps to a table with three columns and two rows. Keys become the header, values become the cells. Anything more complicated than this needs a decision before it becomes markup, which is what the rest of this guide is about.

If your data is a single object rather than an array, wrap it in an array first, or render it as a two-column key-value table instead. A bare object has no natural row structure.

Handling Missing and Extra Keys

Real rows are not uniform. One record has an email, the next does not. If you build your column list from the first row alone, you drop every key that only appears later:

Take the columns from the first row and you lose the email entirely. The fix is to collect the union of keys across every row, then fill missing cells with an empty value. That keeps the table rectangular even when the data is not.

ApproachWhat happens to email
Keys from first rowColumn never appears
Union of all keysColumn shows, empty where absent
Intersection of keysColumn dropped if any row lacks it
Union is almost always what you want. A blank cell is honest; a silently missing column hides data.

Nested Values Need a Decision

The moment a value is itself an object or an array, there is no obvious cell for it. [object Object] is what you get if you ignore the problem, and it is useless.

You have three reasonable options for tags and address. Join arrays into a comma-separated string (math, engine). Serialize objects back to compact JSON so the structure stays visible. Or flatten nested keys into dotted columns like address.city. Pick one and apply it consistently. Mixing them in the same table confuses whoever reads it.

For deeply nested API responses, do not force it into one table at all. Explore the structure first in the JSON viewer to see how deep it goes, then decide which slice actually belongs in a table.

Escaping Is Not Optional

This is the part people skip and regret. If a value contains <, >, or &, dropping it straight into HTML either breaks your layout or opens an injection hole. A name like <script> is not hypothetical when your data comes from user input.

Escape every value before it touches the markup:

Wrapping the value in String() first also handles numbers, booleans, and null without a separate check. Never build a table from untrusted JSON without this step.

Building It in JavaScript

Put the pieces together and the whole converter is short. Collect the union of keys, build the header, then map each row to cells, escaping as you go:

The ?? '' handles the missing-key case, turning an absent value into a blank cell instead of the string undefined. For nested values, run each cell through a helper that joins arrays and stringifies objects before it reaches escapeHtml.

Skip the Boilerplate for One-Off Tables

If you just need the table once, for a README, an email, or a quick admin page, writing the function is overkill. Paste your array into the JSON to HTML table converter and copy the markup out. Want the same data as a Markdown table for docs or a GitHub comment instead, the JSON to Markdown converter produces that from the same input.

Before you convert, it helps to clean the input. Malformed JSON produces a broken or empty table with no clear error, so run the array through the formatter first to confirm it parses and to see the key structure. A table is only as good as the data you feed it, and thirty seconds of checking the shape saves you from debugging markup that was never the problem.

Related Tools