JSON Lines to Parquet Converter
The format that streams into the format that stores. One typed column per field, compressed properly, and a file your query engine can read one column at a time.
To convert JSON Lines to Parquet, drop your .jsonl or .ndjson file above. Each line becomes a row, nested objects flatten into dotted columns, each column is typed once from every line, and DuckDB running in your browser writes the Parquet file. Choose Snappy, Zstd or no compression, then download.
Want to filter events before writing? Open the app
The natural next step for an NDJSON export
NDJSON is a good transport format and a poor archive. Every line repeats every key name, every value is text on the wire, and answering a question about one field means parsing all of them on every line.
Parquet is what the same data looks like once it has stopped moving: each column stored together, compressed with knowledge of its own type, and readable independently. A query touching two fields out of twenty reads two columns instead of the whole file.
The compression difference is usually striking on event data, because event data is repetitive. A status field with six distinct values across a million rows compresses to almost nothing in a columnar layout, and to a million copies of the same string in NDJSON.
Types read from every line, not the first one
This is the difference that matters most for an NDJSON source. A schema inferred from the first few hundred lines is how a load breaks two hours in: a field that is null for the first ten thousand records and a string thereafter, or an integer that acquires a decimal point somewhere in the middle of the file.
Every line is read before any type is decided. A column becomes an integer type only if every value in it is a whole number, a floating type only if every value is numeric, a boolean only if every value is a real boolean. Anything mixed becomes VARCHAR, and no value is changed.
The types are then declared to DuckDB explicitly rather than left to be sniffed again, so the file's schema is exactly the one the preview showed you.
What the schema ends up as
- BIGINT for whole-number columns, which keeps identifiers and counts exact.
- DOUBLE for numeric columns with fractions.
- BOOLEAN for columns of real JSON booleans.
- DATE for columns of exact ISO dates, which is what lets a date filter push down instead of scanning.
- VARCHAR for everything else, including timestamps with an offset, which stay as written because the timezone question has no safe default.
Flattening, and the promise about row counts
Nested objects become dotted columns. Parquet can express nested types and this writes flat ones, because flat columns are what query engines optimise for and what every BI tool can see without configuration.
Nesting past four levels and arrays of objects stay as JSON text in a VARCHAR column. Most engines, DuckDB included, can extract from JSON text in a column when you need to go further, so nothing is lost and the shape stays predictable.
The row count is always exactly the line count. A converter that spread nested arrays into rows would multiply your event data and every aggregate computed afterwards would be wrong, so that is not something this offers even as an option.
Columns from the union of every line
Event streams change shape over time. A field added in March is missing from every line written before it, and a converter that infers its columns from the first record silently drops it from the entire file.
The columns here are the union of every key across every line, in first-seen order. A field that appears on one line in a million still gets a column, with nulls elsewhere, which is what lets you query for exactly those records afterwards.
This is also why the type inference reads the whole file rather than stopping early. The two decisions are the same decision: knowing what the columns are and knowing what is in them both require looking at all of it.
Running it, and the size question
DuckDB compiled to WebAssembly runs inside the page. The first conversion downloads it, a few megabytes, and caches it for the session.
Nothing is uploaded, which for an event export usually means user identifiers, IP addresses or request payloads that you would rather not post to somebody's endpoint in exchange for a file conversion.
The ceiling is your tab's memory. A hundred megabyte NDJSON file is comfortable; for something in the gigabytes the full editor streams, and at that scale running the conversion in your pipeline rather than a browser is usually the right call anyway.
Frequently Asked Questions
Is the schema inferred from a sample?
No, from every line. That is the point. A schema taken from the first few hundred lines is how an NDJSON load breaks two hours in, when a field that was null early turns out to be a string later.
Are nested fields written as nested Parquet types?
No, they flatten into dotted columns. Flat columns are what query engines optimise for and what BI tools can see without configuration. Nesting past four levels and arrays stay as JSON text in a VARCHAR column, which DuckDB and most engines can still query.
Will the Parquet have the same number of rows as my file has lines?
Exactly. Spreading nested arrays into extra rows would multiply your event data and invalidate every aggregate computed afterwards, so it is not offered even as an option.
Why is my timestamp column VARCHAR rather than TIMESTAMP?
Because a timestamp carrying an offset has a timezone question that no converter can answer for you. It comes through exactly as written, and casting it in your query is one function call with the semantics you chose.
Snappy or Zstd?
Snappy unless you have a reason. It is read by every Parquet implementation and decompresses fast. Zstd is meaningfully smaller and every current reader handles it, which suits cold storage for a large event export.
Is the export uploaded anywhere?
No. The file is parsed and written by DuckDB compiled to WebAssembly inside your browser tab. For event data holding user identifiers or request payloads, that is usually the deciding factor.
Archive the export properly
Typed from every line, columnar, compressed, and it never leaves your machine.
Back to the converter