CSV to JSONL Converter
Turn a spreadsheet into one JSON object per line, ready for a fine-tune upload or a warehouse load. Leading zeros stay text, blanks become null, and the rows never leave your browser.
Need to rename or drop columns before the conversion? Open the app
The spreadsheet is where training data actually lives
Almost every dataset I have watched being assembled started in a sheet, because that is the only place a group of people can edit rows together. The .jsonl file is the last step before the upload, not the working format:
- Fine-tuning sets built by hand. Two columns for the input and the ideal answer, a third for who wrote it, a fourth for whether it has been reviewed. Filter to the approved rows, export, convert, upload.
- Eval suites. A question per row with the expected answer and a difficulty tag. The harness reads it one line at a time so a crash halfway still leaves usable results.
- Labelling exports. Annotation tools hand back a CSV. The trainer wants lines.
- Warehouse loads. BigQuery's newline-delimited JSON, Snowflake stages, an OpenSearch bulk feed. All of them take lines and none of them takes a CSV.
- Replaying records through a pipeline. A log processor that reads JSON events can be fed a converted spreadsheet without writing a bespoke reader for it.
The failure mode is always the same, and it is not the brackets. It is a column of identifiers that arrives at the other end as numbers.
Worked example: four rows and a ZIP column
Support answers being prepared as training rows, with the store's ZIP kept alongside for filtering:
prompt,completion,store_zip,rating
What are your hours?,We open at 9am and close at 6pm.,02215,5
Do you deliver?,"Yes, within 10 miles.",94110,4
Where is my order?,Give me the order number and I will check.,60614,5
Can I return this?,Within 30 days with a receipt.,07039,3
The controls start on JSON Lines, so the result is four lines:
{"prompt":"What are your hours?","completion":"We open at 9am and close at 6pm.","store_zip":"02215","rating":5}
{"prompt":"Do you deliver?","completion":"Yes, within 10 miles.","store_zip":"94110","rating":4}
{"prompt":"Where is my order?","completion":"Give me the order number and I will check.","store_zip":"60614","rating":5}
{"prompt":"Can I return this?","completion":"Within 30 days with a receipt.","store_zip":"07039","rating":3}
Two things to notice. The comma inside "Yes, within 10 miles." stayed inside its value, because the quoting in the CSV was read properly rather than split on. And every ZIP is quoted, including 94110 and 60614, which would each survive as numbers on their own. That is deliberate, and it is the next section.
support.csv downloads as support.jsonl, and the preview under the widget shows the rows as a table so you can check the columns landed where you expected before copying anything.
The column decides, not the cell
CSV has no types. Something has to guess, and where a converter guesses cell by cell you end up with a column holding 9.99 as a number and "12.50" as a string, which no loader and no schema inference can work with. So the whole column is read first and one decision is applied to all of it.
- Numeric needs unanimity. A column is numeric only when every non-empty value in it prints back as the identical text.
02215would print as2215, so it fails, and with it the wholestore_zipcolumn. Delete that one row and the same column comes out as bare numbers. - Booleans work the same way. All values lowercase true or false gives you real booleans. One
Yesin the column and every value stays a string, which is better than half a survey column being converted. - Blank means null. Every time, on every line, and every line still carries every column. Records keep one shape even where values are missing.
- Long values stay text. Anything over 16 characters, and any digit run long enough to lose precision, is left as a string. A 17-digit order number arrives intact instead of rounded.
- Dates are strings.
2026-03-14is not turned into a timestamp. There is no way to know which timezone you meant, and guessing shifts your data by hours. - Values that look numeric but are not.
+7,2.40and1e3all fail the round trip and stay text, taking their column with them.
The practical version: if a column holds identifiers rather than quantities, make sure one value in it cannot be read as a number. A leading zero anywhere does that for free.
Before you upload it as a fine-tune file
This part gets glossed over on most converter pages, so here it is plainly. You will get correct JSONL. Whether the objects inside it are the ones your provider asked for is a separate question:
- Chat formats need structure a sheet cannot hold. A conversational training file wants a
messagesarray of role and content objects on each line. A flat CSV has no way to express that, so a prompt and completion export converts to flat lines that still need one reshaping pass. Ten lines of Python over the .jsonl, or start from JSON. - Flat instruction formats usually go straight in. Where the schema is a handful of top-level string fields, the output of this page is the file.
- Extra columns ride along. A reviewer name or a rating becomes another key on every line. Most validators ignore unknown fields, some reject them. Drop those columns before converting if you are not sure.
- Column names become key names exactly. A header with a space or an accent in it becomes a key with a space or an accent in it. Rename headers in the sheet first if the schema is strict.
- Duplicate headers get suffixed. Two columns both called
idarrive asidandid_2, with a note saying so, rather than one silently overwriting the other. Blank header cells becomecolumn_3and so on. - Dotted headers do not nest. A column called
user.nameis one key spelled that way. Every row becomes one flat object, by design.
Controls, limits and the small print
- Structure starts on JSON Lines. That is the only thing separating this page from the CSV to JSON one, which starts on an array. Switch it and the download becomes .json instead of .jsonl.
- Formatting only affects the array. Pretty gives two-space indentation to a JSON array. JSON Lines is always one compact object per line, because indenting a record would break the format.
- Delimiters are sniffed. Comma, semicolon, tab and pipe, read from the text itself. A semicolon export from a European Excel produces byte for byte the same lines as the comma version.
- Excel's invisible marker is stripped. Files saved from Excel often begin with a byte order mark. Left in, it becomes part of the first key name and produces a bug that survives three rounds of debugging because the key looks right on screen.
- Ragged rows are padded, not rejected. Short rows are widened to match the longest, and the widget says how many it padded.
- Nothing is uploaded. The conversion happens in your tab, which is what matters when the rows are customer messages. Up to 100 MB, no row cap, no daily quota.
Frequently Asked Questions
Will this produce a file I can upload for fine-tuning?
It produces valid JSONL with one flat object per line, which is the container every fine-tuning uploader wants. Whether the object inside satisfies your provider depends on their schema. A chat-format file needs a messages array holding role and content objects, and no flat spreadsheet contains that structure, so a prompt and completion CSV converts to lines that still need reshaping into messages. Instruction-style formats with plain top-level fields usually go straight in.
Why is my whole ZIP column quoted when only one value has a leading zero?
Because the type is decided per column, not per cell. A column becomes numeric only when every non-empty value in it survives the round trip back to identical text, and 02215 would print as 2215. One value like that keeps the entire column as strings, which is what you want: a column where some ZIPs are quoted and others are bare numbers is a column no loader can rely on.
What is the difference between JSONL and a JSON array here?
The container, not the records. JSONL is one compact object per line with no brackets around the file and no commas between records, so a reader can take a line at a time and a writer can append. A JSON array is one document that has to be parsed whole. This page starts on JSON Lines and downloads .jsonl; switch Structure to JSON array and you get the array and a .json file instead.
How do empty cells come out?
As JSON null, on every line, including lines where the rest of the row is fine. Not an empty string and not zero. Every line carries every column, so records stay the same shape even where a value is missing, which matters to loaders that infer a schema from the first few lines.
Can I get nested objects out of dotted column names?
No. A column headed user.name becomes a key spelled user.name, dot and all. Each row becomes exactly one flat object. If you need real nesting, convert here and reshape afterwards with a short script, or start from JSON that already has the structure.
Does it handle semicolon or tab separated files?
Yes, without being told. Comma, semicolon, tab and pipe are all sniffed from the text, which covers the semicolon files European Excel installs write by default. It also means cells copied straight out of a spreadsheet, which land on the clipboard as tab separated text, work in the paste box without saving a file first.
Related
Convert your CSV to JSONL
Drop the file or paste the rows, check the preview table, then copy the lines or download the .jsonl. No sign-up, no upload, no row cap.
Back to the converter