Excel, CSV & JSON Converter and Cleaner avatar

Excel, CSV & JSON Converter and Cleaner

Pricing

from $0.21 / 1,000 row processeds

Go to Apify Store
Excel, CSV & JSON Converter and Cleaner

Excel, CSV & JSON Converter and Cleaner

Convert Excel (xlsx, xls), CSV, TSV, JSON and JSON Lines files to JSON, CSV or XLSX and clean them: trim, drop empty rows and columns, snake_case headers, remove duplicates, detect numbers and dates. Every row lands in the dataset.

Pricing

from $0.21 / 1,000 row processeds

Rating

0.0

(0)

Developer

James Brown

James Brown

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

7 days ago

Last modified

Categories

Share

Convert Excel (xlsx, xls), CSV, TSV, JSON and JSON Lines files into clean rows, and get them back as JSON, JSON Lines, CSV or XLSX. Pick the sheets you want, then switch on the cleaning you need: trim whitespace, drop empty rows and columns, snake_case headers, remove duplicate rows (on all columns or just the ones you choose), and detect numbers, booleans and dates. Every cleaned row lands in the run's dataset, so Apify's built-in exports (JSON, CSV, Excel, XML, HTML) and API work out of the box, and a ready-made converted file plus a run summary are saved next to it.

Use it as an Excel to JSON converter, an Excel to CSV converter, a CSV to Excel converter, a CSV cleaner, or a spreadsheet deduplicator in a pipeline: give it a file URL or an uploaded file, get tidy data out. No Excel, no LibreOffice, no macros, and the file is never sent to a third party.

What it does

StepWhat happens
Read.xlsx, .xlsm, .xls, .csv, .tsv, .json, .jsonl from a public URL or from a key-value store upload. Format is detected from the file contents. Excel sheets by name or number, or all visible sheets
TrimLeading/trailing spaces, non-breaking spaces and zero-width characters removed; blank cells become null
Drop emptyRows where every cell is empty, and columns with no value in any data row
Normalize headers" Order ID ", orderId, Order-ID → order_id. Accents removed, blank headers → column_3, repeats → name_2
De-duplicateFirst occurrence kept. Whole row, or only the columns you name (e.g. email), optionally ignoring case. Works across all sheets and files of the run
Detect types42, 1,200.50, true, 2026-10-01 become a number, number, boolean and date. Leading zeros (007), digit strings over 15 digits (IDs, card numbers) and timezone-aware timestamps stay text
OutputOne dataset item per cleaned row, converted.json/.jsonl/.csv/.xlsx in the key-value store, and a SUMMARY record

Formula-injection guard. In the converted CSV and XLSX file, any text cell that starts with =, +, -, @, a tab or a carriage return gets a leading apostrophe, so Excel and Google Sheets show it as text instead of running it (=HYPERLINK(...), +cmd|...). Plain numbers such as -5 are left alone. The number of guarded cells is reported in the summary. The dataset and the JSON/JSONL files keep the original values, so see the limitation below before you open a dataset CSV export in a spreadsheet.

Input

FieldDescription
fileUrlsPublic URLs of the files (up to 50 files, 100 MB each, 300 MB in total)
kvStoreKeys, kvStoreNameKeys of files you uploaded to a key-value store (the run's default store, or the named store you give)
inputFormatauto (default), xlsx, xls, csv, tsv, json, jsonl
sheets, allSheetsExcel: sheet names or 1-based numbers; or all visible sheets. Default: the first visible sheet
outputFormatjson (default), jsonl, csv, xlsx: the converted file in the key-value store
hasHeaderFirst non-empty row holds the column names (default on)
trimWhitespace, dropEmptyRowsOn by default
dropEmptyColumnsOff by default (every input column is kept); switch on to remove columns with no value in any data row. The no-input sample turns it on
normalizeHeaderssnake_case headers (default off)
dedupeRows, dedupeColumns, dedupeIgnoreCaseRemove duplicates. Naming dedupeColumns turns de-duplication on
detectTypes, dateFormat, yesNoAsBooleanNumber/boolean/date detection. dateFormat: iso (default), dmy (UK) or mdy (US) for 03/04/2026. yesNoAsBoolean is off by default because NO can be a country code
addSourceColumnsAdd sourceFile, sheet, sourceRow to every row
maxRowsStop after this many output rows (0 = the hard cap of 1,000,000)
jsonRecordsPath, flattenNestedJSON: dotted path to the array (e.g. data.items), and flatten {"a": {"b": 1}} into a column a.b
csvDelimiter, csvAddBomCSV input delimiter (auto reads `, ; tab

Run it with no input and it converts a small built-in messy CSV with the cleaning options switched on, so you can see the result in a few seconds (about $0.0062 including the start fee).

Example input

{
"fileUrls": ["https://example.com/exports/orders.xlsx"],
"sheets": ["Orders"],
"outputFormat": "csv",
"normalizeHeaders": true,
"dedupeColumns": ["order_id"],
"detectTypes": true
}

Example output (dataset items)

Source rows " Order ID ", "Total (GBP)", "Paid" with padded values, a repeated order and an empty row become:

[
{ "order_id": 1001, "customer_name": "Jane Doe", "order_date": "2026-10-01", "total_gbp": 1200.5, "paid": true, "notes": "ok" },
{ "order_id": 1002, "customer_name": "Sam Lee", "order_date": "2026-10-02", "total_gbp": 89, "paid": false, "notes": null }
]

Summary record (SUMMARY in the key-value store)

{
"rowsIn": 6, "rowsOut": 4, "rowsCharged": 4,
"emptyRowsDropped": 1, "duplicatesRemoved": 1,
"columns": ["order_id", "customer_name", "order_date", "total_gbp", "paid", "notes"],
"columnCount": 6, "emptyColumnsDropped": ["fax"],
"dedupeColumnsNotFound": [], "outputFormat": "json",
"formulaGuardedCells": 0, "truncatedCells": 0,
"stoppedAtRowLimit": false, "stoppedAtChargeLimit": false,
"convertedFile": "converted.json",
"tables": [{ "file": "orders.xlsx", "sheet": "Orders", "rowsIn": 6, "rowsOut": 4, "emptyRowsDropped": 1, "duplicatesRemoved": 1 }]
}

The same numbers appear in the run's status message. rowsIn counts data rows read (not the header row).

Pricing (pay per event)

EventPriceWhen it is charged
Actor start$0.005Once per run
Row processed$0.0003Per cleaned row written to the dataset and the converted file
Rows outApproximate cost
1,000$0.305
10,000$3.005
100,000$30.005
1,000,000 (the per-run cap)$300.005
  • Free: duplicate rows, dropped empty rows and the header row. Only rows that end up in your result are charged.
  • Cap: at most 1,000,000 output rows per run ($300.005 at most). Use maxRows, or Apify's "maximum cost per run", to set a lower ceiling; the run then stops cleanly, the converted file holds the rows delivered, and the summary says so.
  • A run that fails on a bad input (unreadable file, missing sheet, robots.txt refusal) fails before any row is pushed, so it costs only the start fee.
  • Maximum run memory is capped at 1,024 MB so the start fee is never multiplied.

Performance

Measured locally (Windows dev machine, Python 3.11, local storage, no pricing configured; not measured on the Apify platform): 100,000 rows × 8 columns, CSV → CSV in about 6 seconds, CSV → JSON in about 6 seconds, XLSX → XLSX in about 42 seconds, peak memory below 60 MB. Rows are streamed from disk and spooled to disk, so memory stays flat; the duplicate index keeps a 16-byte fingerprint per unique row.

What is stored

  • Dataset (default): one item per cleaned row. The keys are your file's column names.
  • Key-value store (default): converted.<format> (the converted file), SUMMARY (the record above) and PROGRESS (counters used to survive restarts; row numbers only, never cell contents).
  • Not stored: the files you give it are copied to the run's temporary disk, read, and deleted when the run ends. Nothing is kept outside your run, and nothing is sent to a third party. Cell contents are never written to the run log, and error messages contain neither cell contents nor full URLs (a URL's query string may hold a token; sourceFile shows the file name only).
  • Retention follows your Apify plan's storage rules. You control the data and can delete it.

Restarts and migrations

If the run is restarted or migrated, it re-reads the input, rebuilds the duplicate index and the converted file, and skips rows that were already delivered, so nothing is pushed or charged twice. Rows that were in flight at the moment of the interruption are pushed again without a charge and carry recoveredAfterRestart: true (in rare cases such a row appears twice in the dataset; the converted file never has repeats). If the source file changes between a restart and the original run, row numbers may shift.

Fetching files from URLs

When you give fileUrls, the actor identifies itself with a User-Agent, reads the host's robots.txt (RFC 9309) and refuses if the URL is disallowed, or if robots.txt cannot be fetched or answers 5xx/429. Every redirect hop is checked the same way, URLs that resolve to private or internal addresses are refused, and requests are spaced about one second apart. This cannot be switched off. Pages that need a login cannot be fetched; upload the file to a key-value store instead. Credentials in URLs (user:pass@host) are not sent: the actor never makes authenticated requests, so use a URL that needs no login.

Limitations

  • Formulas are not recalculated. Excel files are read with the values Excel last saved. A workbook written by a tool that stores formulas without cached values (some exporters) gives empty cells for those formulas. Open and re-save it in Excel first.
  • Merged cells: only the top-left cell holds the value; the rest are empty. Hidden sheets are skipped by allSheets (select them by name if needed). No .xlsb, .ods or password-protected files. Charts, images and macros are ignored.
  • Dates: Excel date cells come out as ISO text (2026-01-05, 2026-01-05T14:30:00); time-only cells as 14:30:00. The converted XLSX file stores them as real dates. Text dates are detected only with detectTypes; ambiguous 03/04/2026 needs dateFormat. Timezone-aware timestamps stay text.
  • Type detection is deliberately conservative: no currency or percent symbols (£5, 12%), no leading +, no values with leading zeros. 1.50 becomes 1.5. It works cell by cell, so a mixed column can contain both numbers and text.
  • The dataset is not formula-guarded. Apify's own CSV/Excel export of the dataset contains the original text. If a cell may start with =, +, - or @ and you will open the result in a spreadsheet, use the converted CSV/XLSX file from the key-value store (or outputFormat: csv/xlsx).
  • Duplicates compare exact values (after trimming and, if on, type detection) across the whole run: Ann and ann differ unless you set dedupeIgnoreCase; 1 and 1.0 are equal once numbers are detected. A dedupeColumns entry that exists in none of your files stops the run; one missing from only some files is listed in dedupeColumnsNotFound.
  • Limits: 50 files, 100 MB each (300 MB in total), a .xlsx that expands past 600 MB is refused (.xlsx is streamed, so memory stays flat), a .xls over 25 MB or with more than 3,000,000 cells (rows × columns) in a sheet is refused because the old format has to be loaded into memory (convert it to .xlsx or CSV first; the run fails before any row is pushed, so only the start fee is charged), JSON files up to 40 MB (use JSON Lines for larger ones), 1,000 columns, 1,000,000 output rows per run, a converted file over 200 MB is not stored (the dataset still has every row). XLSX cells are limited to 32,767 characters; longer text is cut and counted in truncatedCells.
  • Encodings: UTF-8 (with or without BOM) and UTF-16 are recognised; anything else is read as Windows-1252. Delimiters are detected for comma, semicolon, tab and pipe.
  • JSON: objects missing a key get null; nested objects/arrays stay as they are in the dataset and JSON output and become JSON text in CSV/XLSX (or flatten objects with flattenNested). A top-level object with several lists needs jsonRecordsPath.
  • A problem in an input file (unreadable, missing sheet, unknown de-duplication column) fails the run before anything is pushed. A file that breaks halfway through reading fails the run; rows already pushed stay in the dataset.

Licences and compliance

  • Libraries: openpyxl (MIT) reads and writes .xlsx, xlrd (BSD-3-Clause) reads .xls, defusedxml (PSF-2.0) hardens XML parsing, httpx (BSD-3-Clause), the Apify SDK (Apache-2.0). No GPL/AGPL code.
  • Personal data in your files stays in your run. You are responsible for having a lawful basis (for example under UK GDPR) for the data you process.

FAQ

How do I convert Excel to JSON? Put the .xlsx or .xls URL in fileUrls (or upload it to a key-value store and give its key), pick a sheet, leave outputFormat on json. Download the dataset as JSON or converted.json from the key-value store.

How do I convert CSV to Excel? Give the CSV URL and set outputFormat to xlsx. The file converted.xlsx appears in the key-value store with real numbers and dates when detectTypes is on.

Can it merge several files or sheets? Yes: give several URLs or keys, or turn on allSheets, and add addSourceColumns to see where each row came from. Columns are matched by name; a row simply has null for columns its file does not have.

Does it remove duplicate rows from a spreadsheet? Yes, dedupeRows for exact duplicate rows, or dedupeColumns (for example ["email"]) to keep the first row per key. Duplicates are not charged.

Is my file kept? No. It is read on the run's temporary disk and deleted when the run ends. Only the results you asked for are stored in your run's dataset and key-value store.