HTML Table to JSON: How to Preserve Rows, Columns and Structure
Symptom: you pasted a <table> and want JSON
You copied an HTML table from a web page, a backend export, or an email, and want JSON for programmatic use. Doing "one object per row" breaks on multi-row headers and merged cells. Here's the correct approach.
Basic idea: headers as keys, rows as objects
For a clean table (first row header, each following row a record), the logic is simple:
| Name | Age |
| Tom | 28 |
| Lily | 31 |
→
[
{ "Name": "Tom", "Age": "28" },
{ "Name": "Lily", "Age": "31" }
]
Note: table cells are strings by default; numbers and dates need later type conversion.
Common pitfalls
1. Headers spanning columns (colspan) / rows (rowspan)
A merged cell means "this cell belongs to multiple logical positions". Naive "take by column index" misaligns. Handle by:
- Expand
colspan: copy the content into each column it spans; - Expand
rowspan: fill the content down into each row it spans; - Then read the expanded full matrix.
2. Multi-level headers / nested thead
Some tables have two header rows; decide which becomes the key, or concatenate both as level1.level2.
3. Tags inside cells
If <td> wraps <a>, <span>, do you want the text or an attribute (like href)? Decide upfront.
4. Whitespace and invisible characters
Cells copied from web pages often carry , zero-width spaces, or leading/trailing blanks. Normalize before converting: trim() everything and map \u00A0 to a plain space — otherwise every later diff, dedup, or join fails on things that "look identical but don't match".
How to convert with a tool
Open the HTML Table to JSON tool on ToolVault:
- Paste the whole
<table>...</table>(or a fragment containing a table); - Optionally toggle “first row as header keys” and “empty cells as null”;
- The tool parses the DOM, expands merged cells, and builds an object array;
- Preview to confirm alignment, then copy; for docs, pair with HTML to Markdown.
The structure traps: rowspan, colspan, and thead
HTML tables carry layout semantics that flatten badly. A colspan="3" header means three JSON keys share one visual label — a naive extractor produces three identical column names or one key swallowing three columns' data. rowspan is worse: the first data row carries the category that visually applies to the next four rows, but the DOM puts it in one cell — extraction must forward-fill the spanned value down the rows. And thead/tbody boundaries decide what becomes headers versus rows. When a converted table "mostly looks right but some rows are shifted", the culprit is almost always a span. Inspect with querySelectorAll('td[rowspan]') first.
Scraping workflows: from URL to clean JSON
Copy-paste from a browser works for one-off tables, but the repeatable pipeline is: fetch the page (server-side to avoid CORS), select the table (document.querySelector('#main table') in a DOM parser), and normalize before converting — strip (it becomes U+00A0 in JSON and silently breaks equality checks), collapse thousand separators in numeric columns, and decide on empty cells (null versus empty string, consistently). If the source table paginates, append a page parameter loop and merge on a stable key column. The HTML table → JSON converter handles the DOM-to-structure step; the normalization rules are yours to define and are where all the real-world dirt lives.
Nested tables: convert or reject?
A table inside a cell cannot become a JSON scalar — the honest options are: stringify the inner table's HTML, convert it to a nested array and accept the schema complexity, or reject with a clear error. Legacy enterprise reports love nested tables for layout; if you control neither the source nor the schema, stringify-and-flag is the least surprising choice, with the raw HTML preserved for later re-extraction.
Headers off, duplicate labels, and empty cells
When first row as header is off, columns become positional keys (col1, col2, …). That is better than inventing names, but rename before shipping a public API. Duplicate header labels after colspan expansion collide — qualify them (Region / North, Region / South) or the later object keys overwrite earlier ones in most JS serializers. Empty cells: decide once between "" and null (the tool can map empties to null) and keep the choice identical across CSV and Markdown siblings so joins do not disagree on "missing."
After conversion, spot-check with the JSON formatter: look for shifted rows (rowspan), identical keys (colspan headers), and U+00A0 spaces that survived a bad paste.
FAQ
Are merged cells auto-expanded?
A good tool does. The key is flattening rowspan/colspan into a complete matrix before mapping keys, or later values shift.
Output numbers are all strings — does it matter?
Fine for display/storage; if they feed calculation, Number() them in code, and clean thousand-separators / currency symbols first.
Can a header-less table be converted?
Yes — see the tool section: positional keys (column1, column2...) are generated when no header row exists.
Large table output rows don't line up — why?
First check whether a rowspan shifted rows apart; then check whether the <tbody> mixes in grouping rows (<tr class="group">) — many admin exports interleave subtotal rows between data rows, which the parser must filter by class or column count.
Yes — the tool usually auto-numbers keys as col1, col2…; you can rename later.
Paste hygiene before you convert
Copy from a spreadsheet UI often brings Excel’s non-breaking spaces and styled empty cells that look blank but become "" keys. Prefer “Copy as HTML” only when you need structure; otherwise export CSV and use the CSV/JSON converter. For true <table> HTML, paste into the HTML table to JSON tool, enable header-as-keys when the first row is labels, and immediately open the result in the JSON formatter to catch shifted columns from rowspan.
Triage notes
The four pitfalls are ordered with intent: merged cells come first because "index by column" inevitably misaligns against colspan/rowspan, and the fix is to expand the table into a full matrix first — when a large table's rows don't line up, checking rowspan is likewise step one. The multi-header entry asks you to settle long-versus-wide up front, because the two JSON shapes serve entirely different purposes — a business decision, not a parsing one. The invisible-character advice is concrete: leave \u00A0 unnormalized and every later diff, dedupe, or join "looks identical yet won't match"; the duplicate-header collision likewise notes that most JS serializers let the later write overwrite the earlier.
Provided by ToolVault. Related tools: HTML Table to JSON, HTML to Markdown, JSON Formatter. Visit the home page for more developer tools.
Related Tools
Related Articles
Online CSV to SQL Tool: Import Tabular Data into Databases
CSV to INSERT with empty cells → NULL, MySQL backticks or PostgreSQL quotes, BOM stripping, Markdown pipe escaping, and when to switch to COPY/LOAD instead.
Excel to JSON: Dates Become Numbers / Chinese Garbled — Fix
When converting Excel to JSON, dates turn into numbers like 44927, Chinese is garbled, empty cells vanish? Explains Excel serial dates and encoding pitfalls, with our Excel to JSON tool.
The Complete Guide to Table Format Conversion: Excel, CSV, JSON, Markdown, and HTML
Convert between Excel, CSV, JSON, Markdown, and HTML tables — delimiters, headers, empty cells, merged-cell boundaries, and matching browser tools.