JSONL Examples
Real-world use cases, complete sample files, and code samples showing when and why to use JSONL
JSONL vs JSON
Every sample on this page assumes you already know what a .jsonl file looks like. If you do not, the five-minute quick start covers the basics first.
Traditional JSON
[
{
"id": 1,
"name": "Alice",
"email": "[email protected]"
},
{
"id": 2,
"name": "Bob",
"email": "[email protected]"
}
]Must load entire file
Cannot append easily
Memory intensive for large data
JSONL Format
{"id": 1, "name": "Alice", "email": "[email protected]"}
{"id": 2, "name": "Bob", "email": "[email protected]"}Stream line-by-line
Append-friendly
Memory efficient
When to Use JSONL
Each card below shows the record shape you would actually find in production, not a two-field toy. Real records carry timestamps, identifiers, and nested detail, and that is exactly what makes the one-object-per-line rule worth having.
Machine Learning and AI Training Data
Widely used for passing training data to ML models (for example OpenAI, Google Vertex AI, and most open-source fine-tuning stacks). Each line is one complete training example, so a dataset of millions of conversations streams through a tokenizer without ever being fully resident in memory. Modern chat fine-tuning uses a messages array rather than a flat prompt and response pair. The JSONL for machine learning guide covers dataset splits, token budgets, and validation.
{"messages":[{"role":"system","content":"You are a terse support agent."},{"role":"user","content":"How do I rotate my API key?"},{"role":"assistant","content":"Open Settings, Keys, then Rotate. The old key stops working in 24 hours."}]}
{"messages":[{"role":"system","content":"You are a terse support agent."},{"role":"user","content":"Do you support SSO?"},{"role":"assistant","content":"Yes, SAML 2.0 and OIDC on the Business plan and above."}]}Streaming Data
Process records as they arrive instead of waiting for a complete document. A consumer can act on line one while line ten million is still being written, which is impossible with a single JSON array because the closing bracket never arrives until the producer is finished. See JSONL for data streaming for backpressure and checkpointing patterns.
{"offset":184920,"topic":"orders","key":"ord_8841","ts":"2026-07-14T09:12:04.118Z","payload":{"status":"paid","total_cents":4599}}
{"offset":184921,"topic":"orders","key":"ord_8842","ts":"2026-07-14T09:12:04.503Z","payload":{"status":"pending","total_cents":12750}}Application Logging
The natural format for structured logs. Each entry is one JSON object and new entries are appended to the end of the file, so nothing has to be parsed and rewritten. Real log lines carry an ISO timestamp, a level, the emitting service, and a trace identifier that lets you stitch one request back together across services. More detail in JSONL for log processing.
{"ts":"2026-07-14T09:12:04.118Z","level":"info","service":"checkout-api","trace_id":"4f9c1a2b7d3e5081","msg":"request completed","method":"POST","path":"/v1/orders","status":201,"duration_ms":84}
{"ts":"2026-07-14T09:12:07.902Z","level":"error","service":"checkout-api","trace_id":"9b21ef44c7a0d316","msg":"payment gateway timeout","attempt":3,"duration_ms":30021}Big Data Pipelines
A default interchange format for ingesting, exporting, and processing data in systems like Apache Spark, Hadoop, BigQuery, and Snowflake. Because every line is independent, a large file can be split on newline boundaries and handed to as many workers as you have, with no coordination between them.
{"user_id":10482,"account_status":"active","plan":"business","seats":24,"mrr_cents":119900,"signup_date":"2026-02-03","last_login":"2026-07-13T22:41:09Z"}
{"user_id":10483,"account_status":"churned","plan":"starter","seats":1,"mrr_cents":0,"signup_date":"2026-01-19","last_login":"2026-05-28T11:07:44Z"}Analytics Events
One event per line, appended as it happens. A production analytics record almost always carries the event name, the user and session it belongs to, a timestamp, and a nested properties object whose keys vary by event type. The nesting is why JSONL beats CSV here: you cannot express a variable property bag in a fixed column list. See JSONL for analytics pipelines.
{"event":"signup_completed","user_id":"u_7741","session_id":"s_a91c33","ts":"2026-07-14T15:03:11Z","properties":{"plan":"starter","referrer":"newsletter","country":"US"}}
{"event":"purchase","user_id":"u_7690","session_id":"s_b04f18","ts":"2026-07-14T15:04:52Z","properties":{"order_id":"ord_8841","total_cents":4599,"currency":"USD","items":3}}Streaming APIs and Bulk Exports
APIs that return a large or open-ended number of results can stream them as JSONL, letting the client process each record as it arrives rather than buffering the whole response. The same shape is used for bulk export endpoints, where the server writes a file the client downloads later. The JSONL for API development guide covers the endpoint patterns, content types, and bulk operations that go with this.
{"product_id":"sku_1001","name":"Widget A","price_cents":9999,"currency":"USD","stock":45,"categories":["hardware","featured"],"updated_at":"2026-07-12T08:30:00Z"}
{"product_id":"sku_1002","name":"Widget B","price_cents":14999,"currency":"USD","stock":12,"categories":["hardware"],"updated_at":"2026-07-13T16:45:21Z"}Realistic Sample Files
Four complete files you can copy wholesale into a scratch directory and run the code samples against. Each one is internally consistent, uses field names you would really see, and is small enough to read end to end. Save each block with a .jsonl extension and a trailing newline on the last record. If you want to confirm one parses cleanly before you use it, paste it into the JSONL validator.
app.jsonl - Application Log
Seven lines from a checkout service over about ninety seconds. Two requests share a trace_id because one retried, and the last line is the recovery. This is the file the jq and DuckDB samples further down are written against.
{"ts":"2026-07-14T09:12:04.118Z","level":"info","service":"checkout-api","trace_id":"4f9c1a2b7d3e5081","msg":"request completed","method":"POST","path":"/v1/orders","status":201,"duration_ms":84}
{"ts":"2026-07-14T09:12:31.007Z","level":"warn","service":"checkout-api","trace_id":"9b21ef44c7a0d316","msg":"payment gateway slow","attempt":1,"duration_ms":4820}
{"ts":"2026-07-14T09:12:44.556Z","level":"info","service":"inventory","trace_id":"2c88a501f6b4e973","msg":"stock reserved","sku":"sku_1002","qty":2}
{"ts":"2026-07-14T09:13:01.284Z","level":"error","service":"checkout-api","trace_id":"9b21ef44c7a0d316","msg":"payment gateway timeout","attempt":3,"duration_ms":30021}
{"ts":"2026-07-14T09:13:02.010Z","level":"error","service":"notifications","trace_id":"9b21ef44c7a0d316","msg":"receipt email not sent","reason":"upstream_failure"}
{"ts":"2026-07-14T09:13:19.663Z","level":"info","service":"checkout-api","trace_id":"9b21ef44c7a0d316","msg":"request completed","method":"POST","path":"/v1/orders","status":201,"duration_ms":268}
{"ts":"2026-07-14T09:13:40.902Z","level":"debug","service":"inventory","trace_id":"2c88a501f6b4e973","msg":"cache warmed","entries":1840}train.jsonl - Chat Fine-Tuning Data
Five examples in the chat messages shape that current fine-tuning APIs expect. The system message is identical on every line on purpose: it defines the persona the model is being tuned toward, and varying it record by record is one of the most common ways a fine-tune goes sideways.
{"messages":[{"role":"system","content":"You are a terse support agent for a billing product."},{"role":"user","content":"How do I rotate my API key?"},{"role":"assistant","content":"Open Settings, then Keys, then Rotate. The old key keeps working for 24 hours."}]}
{"messages":[{"role":"system","content":"You are a terse support agent for a billing product."},{"role":"user","content":"Do you support SSO?"},{"role":"assistant","content":"Yes. SAML 2.0 and OIDC, on the Business plan and above."}]}
{"messages":[{"role":"system","content":"You are a terse support agent for a billing product."},{"role":"user","content":"My invoice shows the wrong tax rate."},{"role":"assistant","content":"Tax is set by the billing address on file. Update it under Settings, Billing, and reissue the invoice."}]}
{"messages":[{"role":"system","content":"You are a terse support agent for a billing product."},{"role":"user","content":"Can I export every charge from last quarter?"},{"role":"assistant","content":"Yes. Reports, Charges, pick the date range, then Export. You get a JSONL file by email."}]}
{"messages":[{"role":"system","content":"You are a terse support agent for a billing product."},{"role":"user","content":"What happens if a card is declined?"},{"role":"assistant","content":"We retry on days 1, 3, and 7. After the third failure the subscription moves to past_due."}]}events.jsonl - Analytics Event Stream
Six events across two sessions on a single afternoon. Note that properties holds a different set of keys for each event type. That variability is normal and is the reason this data lives in JSONL instead of a CSV with a fixed header.
{"event":"page_view","user_id":"u_7741","session_id":"s_a91c33","ts":"2026-07-14T15:01:47Z","properties":{"path":"/pricing","referrer":"newsletter","country":"US"}}
{"event":"signup_started","user_id":"u_7741","session_id":"s_a91c33","ts":"2026-07-14T15:02:29Z","properties":{"plan":"starter","method":"email"}}
{"event":"signup_completed","user_id":"u_7741","session_id":"s_a91c33","ts":"2026-07-14T15:03:11Z","properties":{"plan":"starter","method":"email","country":"US"}}
{"event":"page_view","user_id":"u_7690","session_id":"s_b04f18","ts":"2026-07-14T15:04:02Z","properties":{"path":"/cart","referrer":"direct","country":"CA"}}
{"event":"purchase","user_id":"u_7690","session_id":"s_b04f18","ts":"2026-07-14T15:04:52Z","properties":{"order_id":"ord_8841","total_cents":4599,"currency":"USD","items":3}}
{"event":"purchase","user_id":"u_7741","session_id":"s_a91c33","ts":"2026-07-14T15:19:38Z","properties":{"order_id":"ord_8842","total_cents":12750,"currency":"USD","items":1}}products.jsonl - API Bulk Export
Six product records as a bulk export endpoint would emit them: uniform keys, a nested dimensions object, an array of categories, and an explicit null where a value is genuinely absent rather than unknown.
{"product_id":"sku_1001","name":"Widget A","price_cents":9999,"currency":"USD","stock":45,"categories":["hardware","featured"],"dimensions":{"w_mm":80,"h_mm":40,"d_mm":22},"discontinued_at":null,"updated_at":"2026-07-12T08:30:00Z"}
{"product_id":"sku_1002","name":"Widget B","price_cents":14999,"currency":"USD","stock":12,"categories":["hardware"],"dimensions":{"w_mm":120,"h_mm":60,"d_mm":30},"discontinued_at":null,"updated_at":"2026-07-13T16:45:21Z"}
{"product_id":"sku_1003","name":"Cable Kit","price_cents":2499,"currency":"USD","stock":310,"categories":["accessories"],"dimensions":{"w_mm":150,"h_mm":90,"d_mm":15},"discontinued_at":null,"updated_at":"2026-07-09T11:02:55Z"}
{"product_id":"sku_1004","name":"Widget A Mk1","price_cents":7999,"currency":"USD","stock":0,"categories":["hardware","clearance"],"dimensions":{"w_mm":80,"h_mm":40,"d_mm":22},"discontinued_at":"2026-04-30","updated_at":"2026-05-01T00:00:00Z"}
{"product_id":"sku_1005","name":"Mounting Plate","price_cents":1899,"currency":"USD","stock":128,"categories":["accessories","featured"],"dimensions":{"w_mm":200,"h_mm":200,"d_mm":4},"discontinued_at":null,"updated_at":"2026-07-14T07:18:40Z"}
{"product_id":"sku_1006","name":"Widget C","price_cents":24999,"currency":"USD","stock":6,"categories":["hardware"],"dimensions":{"w_mm":140,"h_mm":75,"d_mm":38},"discontinued_at":null,"updated_at":"2026-07-14T07:19:02Z"}Common Record Shapes
JSONL constrains the file, not the record. Any value that is legal inside a JSON object is legal inside a JSONL line, as long as the whole thing fits on one physical line. These are the four shapes that come up constantly, and the questions each one raises. The format definition spells out the underlying rules.
Nested Objects
Nesting is fine and extremely common. The only rule that matters is that the nested structure must be serialized without literal newlines, so no pretty-printing. Anything indented across several lines is a JSON document, not a JSONL record.
{"order_id":"ord_8841","customer":{"id":"u_7690","email":"[email protected]","address":{"city":"Toronto","country":"CA"}},"total_cents":4599}Arrays Inside a Record
A record can contain arrays of scalars or arrays of objects. What it must not be is a bare array at the top level of every line, which people reach for when converting from a JSON array and end up with a file that no downstream tool can map to columns.
{"order_id":"ord_8841","tags":["gift","express"],"items":[{"sku":"sku_1001","qty":2},{"sku":"sku_1003","qty":1}]}Mixed Record Types with a Discriminator
Nothing requires every line in a file to have the same shape. When a single stream carries several kinds of record, give each one a discriminator field, conventionally called type or event, as the first key. Consumers switch on it and skip what they do not handle. Without a discriminator, readers are forced to guess by probing for keys, which breaks silently the moment a new record type appears.
{"type":"order_created","order_id":"ord_8841","total_cents":4599,"ts":"2026-07-14T15:04:52Z"}
{"type":"payment_failed","order_id":"ord_8841","reason":"card_declined","attempt":1,"ts":"2026-07-14T15:05:03Z"}
{"type":"order_shipped","order_id":"ord_8841","carrier":"UPS","tracking":"1Z999AA10123456784","ts":"2026-07-15T11:22:10Z"}Null Versus a Missing Key
These are not the same thing, and the difference bites in production. An explicit null asserts that the field exists and has no value. An absent key asserts nothing at all. Pick one convention per dataset and hold to it, because loaders behave differently: a schema inferred from the first few records may never learn about a key that only appears later. Decide up front, document it, and keep it stable. The common mistakes page covers what goes wrong when a producer switches conventions mid-file.
{"user_id":"u_7741","nickname":null,"verified_at":"2026-07-14T15:03:11Z"}
{"user_id":"u_7690","verified_at":null}Code Examples
Every sample below runs against the files in the previous section. For the full per-language treatment, including error handling and compression, see the JSONL in every language reference.
Python - Streaming Read
Use this when the file is larger than memory, or when you do not know how large it will get. Iterating the file object yields one line at a time, so peak memory tracks the longest single record rather than the file size. Skipping blank lines and catching decode errors per line means one corrupt record cannot take down the whole run.
import json
with open('events.jsonl', 'r', encoding='utf-8') as f:
for line_no, line in enumerate(f, start=1):
line = line.strip()
if not line:
continue
try:
record = json.loads(line)
except json.JSONDecodeError as exc:
print(f'skipping line {line_no}: {exc}')
continue
print(record['event'], record['user_id'])Python - Writing JSONL
Use this whenever you are producing a file for someone else to stream. Two details matter more than they look: ensure_ascii=False keeps real UTF-8 in the output instead of escape sequences, and writing the newline after every record, including the last, means a downstream appender does not silently glue two records together. Open with mode 'a' instead of 'w' to append to an existing log.
import json
records = [
{'event': 'signup_completed', 'user_id': 'u_7741', 'properties': {'plan': 'starter'}},
{'event': 'purchase', 'user_id': 'u_7690', 'properties': {'total_cents': 4599}},
]
with open('output.jsonl', 'w', encoding='utf-8') as f:
for record in records:
f.write(json.dumps(record, ensure_ascii=False) + '\n')
# Appending a single record to an existing file
with open('output.jsonl', 'a', encoding='utf-8') as f:
f.write(json.dumps({'event': 'logout', 'user_id': 'u_7741'}) + '\n')Python - pandas
Use this for exploratory analysis when the file fits comfortably in memory. The lines=True argument is what tells pandas the file is JSONL rather than a single JSON array, and it is the single most commonly missed argument in the whole API. For files that do not fit, chunksize turns the same call into an iterator of DataFrames.
import pandas as pd
df = pd.read_json('events.jsonl', lines=True)
print(df.head())
print(df.groupby('event').size())
# Nested objects arrive as dicts - flatten them into columns
props = pd.json_normalize(df['properties'])
# Write a DataFrame back out as JSONL
df.to_json('clean.jsonl', orient='records', lines=True)
# Files larger than memory: process a chunk at a time
with pd.read_json('events.jsonl', lines=True, chunksize=50000) as reader:
for chunk in reader:
print(len(chunk))JavaScript - Streaming Read with readline
Use this in Node for any file you would not want to hold in a string. The readline module handles the chunk boundaries for you, which is the part people get wrong when they roll their own splitter and a record happens to straddle two reads. Set crlfDelay: Infinity so files written on Windows with CRLF endings parse correctly, and remember that for await has to live inside an async function.
const fs = require('fs');
const readline = require('readline');
// Read JSONL file with streams
async function main() {
const rl = readline.createInterface({
input: fs.createReadStream('events.jsonl'),
crlfDelay: Infinity
});
for await (const line of rl) {
if (!line.trim()) continue;
const record = JSON.parse(line);
console.log(record.event, record.user_id);
}
}
main();JavaScript - Writing JSONL
Use a write stream rather than building one giant string, so memory stays flat as the output grows. When write returns false the internal buffer is full, and a producer that ignores that signal on a very large export will balloon memory. Waiting for the drain event is the fix.
const fs = require('fs');
const records = [
{ event: 'signup_completed', user_id: 'u_7741', properties: { plan: 'starter' } },
{ event: 'purchase', user_id: 'u_7690', properties: { total_cents: 4599 } }
];
async function write() {
const out = fs.createWriteStream('output.jsonl', { encoding: 'utf8' });
for (const record of records) {
const ok = out.write(JSON.stringify(record) + '\n');
if (!ok) {
await new Promise(resolve => out.once('drain', resolve));
}
}
out.end();
}
write();Go - bufio.Scanner and the 64KB Limit
Use this for high-throughput readers. There is one real footgun: bufio.Scanner refuses any token longer than 64KB by default and stops with bufio.ErrTooLong. Because Scan simply returns false, a naive loop looks like it reached the end of the file and exits successfully, having silently dropped every remaining record. JSONL lines carrying a nested payload or an embedded document cross 64KB routinely. Always call Buffer to raise the ceiling, and always check scanner.Err() after the loop.
package main
import (
"bufio"
"encoding/json"
"fmt"
"os"
)
func main() {
f, err := os.Open("events.jsonl")
if err != nil {
panic(err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
// Default max token is 64KB. Raise it or long records are dropped.
buf := make([]byte, 0, 1024*1024)
scanner.Buffer(buf, 16*1024*1024)
for scanner.Scan() {
line := scanner.Bytes()
if len(line) == 0 {
continue
}
var record map[string]interface{}
if err := json.Unmarshal(line, &record); err != nil {
fmt.Println("bad line:", err)
continue
}
fmt.Println(record["event"], record["user_id"])
}
// Never skip this - it is how you learn the loop ended early.
if err := scanner.Err(); err != nil {
panic(err)
}
}jq - Filter, Project, Count, Convert
Use jq when you want an answer now and do not want to write a script. It reads a stream of JSON values, which is exactly what a JSONL file is, so no special flag is needed to get started. The one flag worth memorizing is -c, for compact output: without it jq pretty-prints, and your JSONL file comes back out as something that is no longer JSONL.
# Filter: keep only error records
jq -c 'select(.level == "error")' app.jsonl
# Project: pull a few fields into a smaller record
jq -c '{ts, service, msg}' app.jsonl
# Rename and reach into a nested field
jq -c '{user: .user_id, plan: .properties.plan}' events.jsonl
# Count records, then count by group
wc -l < app.jsonl
jq -s 'group_by(.service) | map({service: .[0].service, n: length})' app.jsonl
# Convert to CSV, header row first
jq -r '["ts","service","level","msg"], [.ts, .service, .level, .msg] | @csv' app.jsonl
# Go the other way: split a JSON array into JSONL
jq -c '.[]' array.json > data.jsonlDuckDB - Query JSONL with SQL, No Import
This is the one to reach for first on any file too big to eyeball and too awkward for jq. DuckDB reads a JSONL file directly as a table: no schema definition, no load step, no database to stand up. It infers column types from the data, understands nested fields with dot notation, expands globs across a directory, and decompresses gzip on the fly. On a multi-gigabyte log file it will finish an aggregate in seconds while a hand-written script is still parsing.
-- Look at the file. That is the entire setup.
SELECT * FROM read_ndjson('events.jsonl') LIMIT 10;
-- Aggregate straight off disk
SELECT event, count(*) AS n
FROM read_ndjson('events.jsonl')
GROUP BY event
ORDER BY n DESC;
-- Nested fields with dot notation
SELECT properties.plan AS plan, count(*) AS signups
FROM read_ndjson('events.jsonl')
WHERE event = 'signup_completed'
GROUP BY plan;
-- A whole directory of daily files, gzip included
SELECT service, count(*) AS errors
FROM read_ndjson('logs/2026-07-*.jsonl.gz')
WHERE level = 'error'
GROUP BY service;
-- Ragged records: union the keys instead of failing, and skip bad lines
SELECT * FROM read_ndjson('mixed.jsonl', union_by_name = true, ignore_errors = true);
-- Write the result back out as JSONL
COPY (SELECT * FROM read_ndjson('events.jsonl') WHERE event = 'purchase')
TO 'purchases.jsonl' (FORMAT JSON);More options for working with JSONL files at this scale are collected on the JSONL tools page, and the performance guide compares parsing throughput across approaches.
Best Practices
Do
- Use UTF-8 encoding, with no byte order mark - see the format definition
- Write exactly one valid JSON object per line, per the specification
- Use \n (LF) line endings, and read with CRLF tolerance - the error dictionary lists the symptoms when you do not
- Stream large files instead of loading them, as the best practices guide explains
- Compress with gzip for storage and transfer - most readers decompress transparently
- Keep a stable key convention across a dataset, including null versus missing keys
Don't
- Don't pretty-print records across multiple lines - the most common JSONL mistake by a distance
- Don't use trailing commas or comments - neither is legal JSON, and the error dictionary decodes the parser message
- Don't wrap the file in array brackets - that makes it JSON, not JSONL, as the definition makes clear
- Don't load an entire file when you only need to scan it once
- Don't let one bad line kill the run - handle parse errors per record and log them, as covered under troubleshooting