JSONL to CSV Converter
Drop a .jsonl or .ndjson file and get a CSV back. Lines with different keys line up under one set of columns, nested objects flatten with dots, and the records stay on your machine.
Want to filter rows or pick columns first? Open the app
Where these files come from
JSONL used to be a log format and not much else. Machine learning tooling adopted it wholesale, and now most people who land on a page like this are holding one of five things:
- Model evaluation output. One line per test case, with the prompt, the response, a score and some metadata. You want it in a sheet to sort by score and read the bottom twenty by hand.
- Fine-tuning and training sets. Every major provider takes training data as one example per line. Reviewing a thousand examples for duplicates or leakage is spreadsheet work, not text editor work.
- Conversation and assistant exports. Chat history, tool call traces, agent runs. Columns turn a wall of transcripts into something you can filter by date or by user.
- Warehouse and streaming payloads. BigQuery loads newline-delimited JSON, Kafka topics get dumped in it, and Kinesis and Pub/Sub archives arrive the same way. A CSV is what the analyst asking about it can actually open.
- Structured application logs. One JSON object per event is the house style for most log libraries. Pulling an hour of events into columns beats writing another one-off jq incantation.
None of them is uniform for long. Fields appear halfway through a run, optional metadata sits on some records only, and a nested object holds half the information.
Worked example: four lines from an eval run
Four records. Each has a nested meta object, and only the third carries a note field.
{"id":"run_01","model":"gpt-4o-mini","score":0.91,"meta":{"split":"eval","seed":7}}
{"id":"run_02","model":"gpt-4o-mini","score":0.88,"meta":{"split":"eval","seed":8}}
{"id":"run_03","model":"claude-3-haiku","score":0.94,"meta":{"split":"holdout","seed":7},"note":"retry"}
{"id":"run_04","model":"claude-3-haiku","score":0.9,"meta":{"split":"holdout","seed":9}}
Paste those into the box above and the CSV is:
id,model,score,meta.split,meta.seed,note
run_01,gpt-4o-mini,0.91,eval,7,
run_02,gpt-4o-mini,0.88,eval,8,
run_03,claude-3-haiku,0.94,holdout,7,retry
run_04,claude-3-haiku,0.9,holdout,9,
Six columns from four keys, because meta was an object and became meta.split and meta.seed rather than a cell full of braces. The note column exists because line three mentioned it, and it sits at the right hand end because that is where the parser met it. Three rows have a trailing comma with nothing after it, which is a genuinely empty cell in that column.
Above the result the widget prints a note saying it read the input as JSON Lines, one JSON value per line. Worth a glance: if that note is absent, the file parsed as a single JSON document instead, which usually means a wrapping array you were not expecting.
JSONL and JSON are not interchangeable
Both are made of JSON, which is why the two get confused. The difference is the packaging. A .json file is one value: open a bracket at the top, close it at the bottom, commas between the records inside. A .jsonl file is a stack of complete values, one per line, with no bracket and no commas holding them together.
That packaging decides what you can do with the file. A JSON array has to be parsed in full before the first record is available, so a 4 GB export needs 4 GB of memory and a streaming parser to avoid it. JSONL is readable a line at a time, appendable by opening the file and writing one more line, splittable across workers by counting newlines, and greppable with tools that know nothing about JSON. It also survives a crash: an interrupted write costs you the last line, not the closing bracket that makes the entire file invalid.
That is why training data, log pipelines and warehouse loaders standardised on it, and why an array is still better for a config file or an API response. This page takes either: the engine tries the whole document as one JSON value first, then falls back to reading it line by line, so a mislabelled file converts anyway.
Ragged keys become one set of columns
Real JSONL is rarely uniform, and it does not have to be, since nothing enforces a shared schema between two lines. The columns are therefore the union of every key in the file, ordered by when each was first met, and any line that is missing one gets an empty cell.
- The first line sets the left hand columns. A field that appears for the first time on line 900 becomes the right-most column, with 899 blanks above it.
- Key order inside a line does not matter. If one line writes score before model and the next writes them the other way round, both land in the same two columns.
- A JSON null and a key that was never there both produce an empty cell. The CSV cannot tell you which it was, so keep the source file if the difference carries meaning.
- Nested objects flatten with dots for up to four parts, so
a.b.c.dis a column but a fifth level is written into that cell as JSON text, with a note counting how often it happened. - Arrays stay in one cell as JSON text, always. Nothing is exploded into extra rows without you asking, and if an array held objects the result says how many fields that applied to.
- Lines that are bare values rather than objects still work. A file of plain numbers or strings converts to a single column called value.
Things that catch people out
- One bad line stops everything. A truncated final line from a killed job, or a stray log message in the middle of a dump, fails the conversion with the parser's message rather than being skipped. Dropping records out of a dataset silently is the worse behaviour, so it is not offered.
- Blank lines and CRLF are fine. Empty lines anywhere are ignored, Windows line endings are handled, and a trailing newline at the end of the file changes nothing.
- Newlines inside a value are quoted, not broken. A completion field holding three paragraphs stays one CSV field, wrapped in quotes with the line breaks intact. Commas and embedded quotes get the same treatment, so long text columns from model output survive.
- A dotted key in the source is ambiguous. If a line already has a literal key spelled meta.split alongside a nested meta object, both write into a column of that name and one overwrites the other. Rare, but it is the one collision this scheme cannot see coming.
- Excel still reformats CSV on open. The converter writes 007 as the characters 007, then Excel decides it is 7 when you double click the file. Import through Data, then From Text, and mark that column as text.
- 100 MB is the ceiling on this page. No row cap and no metering under it. Above it, the widget offers the full editor, which reads the file in chunks rather than holding it all at once.
Frequently Asked Questions
What is JSONL, and how is it different from JSON?
JSONL is one complete JSON value per line, with no wrapping array and no commas between records. A JSON file is a single value, so a reader has to parse all of it before the first record is usable. JSONL can be read, appended to, split and streamed a line at a time, which is why exports, logs and training sets use it. Both are valid JSON syntax; the difference is the packaging around the records.
Is NDJSON the same thing?
Yes, in practice. NDJSON, newline-delimited JSON and JSON Lines all describe one JSON value per line, and the two spellings exist mainly because two specs were written separately. This page takes .jsonl, .ndjson, .json and .txt, and it reads the content rather than trusting the extension.
What happens when lines have different keys?
Every key in the file gets a column. The order is first seen wins, so the first line sets the layout and a key that shows up only later is appended on the right. Lines that never mention a key get an empty cell there, and JSON null lands as an empty cell too, so the two are indistinguishable in the output.
How are nested objects and arrays handled?
A nested object becomes dotted columns, so meta with split inside it gives meta.split. That continues for up to four dot-separated parts, and a branch deeper than that is written into one cell as JSON text with a note in the result. Arrays are always kept as JSON text in a single cell; no row is invented behind your back, and if the array held objects the note says so.
One line in my file is malformed. Will it be skipped?
No, and that is on purpose. A single unparseable line fails the whole conversion with the parser's own message, because quietly dropping records from a dataset is worse than refusing it. Truncated last lines from an interrupted write are the usual culprit. Blank lines, Windows line endings and a trailing newline are all fine.
Is anything uploaded, and how large a file can it take?
Nothing is uploaded. The file is read and converted by JavaScript in your tab, which is the part that matters when the records are chat transcripts or customer data. The limit here is 100 MB with no row cap and no daily quota; a larger file is handed to the full editor, which streams it instead.
Related
Convert your JSONL to CSV
Drop the .jsonl or .ndjson file, check the columns in the preview, then copy the CSV or download it. No sign-up and no row cap.
Back to the converter