TSV wins on simplicity and loses on escaping
A tab separated file splits fields on a single tab and rows on a single newline. There is no quoting layer, no doubled quote characters, no dialect argument about whether a comma inside an address needs wrapping. Every parser agrees on where the boundaries sit, which is exactly why TSV survives as the clipboard format between a browser and a spreadsheet.
The price is blunt. The IANA registration for text/tab-separated-values says fields do not contain tabs or newlines, full stop. There is no sanctioned way to store one. A JSON string is free to hold both, so a converter has to make a decision on your behalf, and the decision changes your data. That single fact drives most of the controls above.
[{ "id": 12, "note": "Refund\nagreed" }]id note
12 Refund\nagreedEscaping keeps the row count honest, which matters when a downstream script counts lines to count records. The reader sees the two characters backslash and n rather than a real break, so a note reads a little oddly in a cell. Replacing breaks with a space reads better for a person and loses the paragraph structure. Removing them silently glues words together. Leaving them alone produces a file that no TSV parser reads correctly, and the option exists only for the case where you are pasting into a tool you already know tolerates it.
How the four whitespace settings compare
| Setting | Row count stays right | Value survives a round trip | Reads well in a cell |
|---|---|---|---|
| Escape as \t and \n | Yes | Yes, if the reader unescapes | No |
| Replace with a space | Yes | No, breaks are gone for good | Yes |
| Remove them | Yes | No, and words run together | Mostly |
| Leave them alone | No | Not through any TSV parser | Broken |
Pick escaping when the file feeds another program. Pick the space swap when a person opens the sheet and reads the column. The Values cleaned tile counts how many values hit either rule, so a zero there means the setting made no difference to this payload at all.
Finding the table inside the response
Real API output rarely arrives as a bare array. It arrives wrapped in pagination, a status field, and a nested envelope. The old version of this page refused anything that was not an array at the top level. This one walks the object breadth first, takes the first array whose members are mostly objects, and tells you underneath the panes which path it used, for example 3 records read from data.items.
{"meta": { "page": 1 },"data": { "items": [ ... ] }}data.itemsRead that line before you trust the output. Breadth first means shallow arrays win, so a payload holding two candidate arrays at different depths gives you the shallower one. If it guesses the wrong branch, copy the branch you want into the left pane on its own. When no array of objects exists anywhere, the object itself becomes a single row with one column per key, which is the sensible reading of a config blob or a single record fetch.
Nested objects, arrays, and the columns they turn into
A table has no second dimension inside a cell, so nesting has to collapse somewhere. Flattening to dot keys is the default because it keeps every scalar addressable: price.amount and price.currency become two ordinary columns, and a filter or a pivot works on them the same way it works on anything else. Depth costs width. Three levels of nesting across a wide object produces a header row nobody wants to scroll.
Keeping nested objects as JSON text is the choice when the sheet is a staging area rather than the destination. One column holds {"amount":24.99,"currency":"GBP"}, the structure survives intact, and a later script parses it back. Dropping the column suits the case where you want a quick eyeball of the top level fields and the nested detail is noise.
Arrays get their own control because the right answer differs from objects. A tags list reads well joined with a pipe, since the point is to see membership at a glance. An array of line items reads terribly that way and wants one column per index, giving items[0].sku, items[1].sku, and so on. Index columns match the longest array in the file, so one order with forty lines widens the sheet for every other row.
Ragged records and where the header comes from
JSON arrays are not obliged to be uniform. One record carries a supplier field, the next does not, a third adds weight_kg. The header is built as the union of every key found across every record, so no field is dropped for being rare, and any record missing a key gets whatever your Missing values setting says.
The Ragged rows tile counts records that were short of at least one column. A high number against a small column count usually means the payload mixes two record types that were never meant to share a table, and splitting the JSON first gives a cleaner result than filling gaps. A high number against a wide column count is normal for optional fields.
Column order defaults to the order keys first appear, which keeps id and name near the left where the source put them. Alphabetical order exists for diffing: two exports of the same endpoint taken a week apart line up column for column only when both are sorted.
The three missing value markers
- Empty cell writes nothing between the tabs. A spreadsheet shows a blank, which is what most people want when they are reading rather than loading.
- The text null writes the four letters. Useful when a genuine empty string and an absent field have to stay distinguishable, and harmless as long as no real value in the column is the word null.
- \N for database loads is the marker MySQL
LOAD DATA INFILEand PostgresCOPYread as SQL NULL by default. Feeding those loaders an empty field instead puts an empty string in a text column and throws an error on a numeric one, so this setting saves a round of debugging when the file goes straight into a table.
What a spreadsheet does to your identifiers
This is where clean TSV still ends in wrong data. Excel, Sheets, and LibreOffice all guess a type per cell on open, and the guesses run against exactly the values that matter most: codes, phone numbers, part numbers, barcodes.
| Your value | What lands in the cell | Why |
|---|---|---|
07030 | 7030 | Read as a number, and numbers have no leading zero |
9780306406157001 | 9780306406157000 | Excel keeps 15 digits of precision and zeroes the tail |
3-4 | 04-Mar | Read as a date in the current locale |
MAR1 | 01-Mar | Month abbreviation plus a day number |
1E5 | 100000 | Read as scientific notation |
=SUM(A1:A9) | A live formula | A leading equals sign starts a formula, not text |
The panel under the converter scans the finished TSV for these patterns and names the column, an example value, and how many rows are affected. Nothing about the file is wrong. The rewrite happens on import, so the fix belongs at import time.
Two ways to open the file without the damage
In Excel, use Data, From Text/CSV rather than a double click, then set the affected columns to Text in the preview before loading. If you are pasting from the clipboard instead, format the destination columns as Text first, then use Paste Special, Text. In Google Sheets, use File, Import and untick Convert text to numbers, dates, and formulas. Both routes take ten seconds and beat repairing a column of postcodes afterwards.
The leading equals sign is a security problem, not only a formatting one. A value such as =cmd|'/c calc'!A0 arriving from a user field runs as a formula the moment somebody opens the export and clicks through the warning. The same applies to values starting with a plus, a minus, or an at sign. Neutralise formulas is on by default and prefixes those values with a single quote, which spreadsheets treat as a text marker and do not display in the cell. Turn it off when the file goes to a script rather than a person, because the quote is then a real character your parser has to strip.
Encoding, line endings, and the file extension
Output is UTF-8. Excel on Windows still assumes the local ANSI code page when it opens a plain text file by double click, so accented names and non Latin scripts arrive as mojibake. Tick BOM on download and the saved file starts with a byte order mark that tells Excel it is reading UTF-8. Leave it off for anything else, since a stray BOM shows up as three odd bytes at the front of the first header name and breaks a column match in scripts and database loaders.
Line endings follow the same split. LF suits Unix pipelines and git. CRLF suits Windows tooling and older Excel import paths. Neither affects the data, and a mismatch shows up as a trailing carriage return glued to the last column of every row.
One more detail costs people time: Windows maps the .tsv extension to nothing by default, while .txt opens the Excel import wizard. If you want the wizard, rename the download. If you want a double click to open in Excel directly, .xls is a lie the program tolerates and other tools do not, so it is worth avoiding.
Where this page stops
One table per conversion. If your payload holds orders and customers as two sibling arrays, run them separately. Nothing here builds a relational split or writes multiple sheets.
No type detection, and none is planned. Every value is written as its JSON form, so a number stays a number and a date stays the string it already was. Deciding that 2026-03-04 is a date and reformatting it is the kind of guess that quietly corrupts a column, and it belongs to the tool that imports the file.
Everything runs in this browser tab, so a payload with customer data in it never leaves your machine and nothing survives a refresh. The practical ceiling is a few megabytes: past that the editors get sluggish long before the conversion itself struggles, and a scripted export beats a browser for a hundred thousand rows.
Pages for the neighbouring jobs
Commas rather than tabs is JSON to CSV, which brings a quoting layer and the dialect choices that come with it. For a real workbook instead of a text file, use JSON to Excel. Coming back the other way, TSV to JSON rebuilds records from a pasted table, and CSV to TSV handles the swap between the two flat formats. When the JSON refuses to parse before you get this far, the JSON fixer repairs trailing commas and unquoted keys, and the JSON viewer is the faster way to find which branch holds the array you actually want.
