Data Formats2026-09-29

JSONL to JSON: Convert Lines into an Array Safely

Turn newline-delimited JSON into a JSON array with Python or Node.js, reject malformed lines, and avoid common blank-line and memory mistakes.

jsonljsonpythonjavascriptdata-formats

Convert JSONL to a JSON array

JSONL (JSON Lines) contains one complete JSON value per line. To make a conventional JSON document from independent records, parse each line and put the values inside an array. Do not just add [ and ] around the file: array elements need commas, while JSONL lines do not.

Input, events.jsonl:

{"event":"login","userId":1}
{"event":"logout","userId":1}

Output, events.json:

[
  {"event": "login", "userId": 1},
  {"event": "logout", "userId": 1}
]

The JSON Lines format uses UTF-8 and requires every line to contain a valid JSON value. An empty line is not a record. Individual values need not be objects; use schema checks if your application requires objects with particular fields. See JSON vs JSONL when deciding which format fits a workflow.

Python: a small file

import json
from pathlib import Path

items = []
for number, line in enumerate(Path("events.jsonl").read_text(encoding="utf-8").splitlines(), 1):
    if not line.strip():
        raise ValueError(f"Blank JSONL line {number}")
    try:
        items.append(json.loads(line))
    except json.JSONDecodeError as error:
        raise ValueError(f"Invalid JSONL line {number}: {error}") from error

Path("events.json").write_text(
    json.dumps(items, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
)

This loads the input and result into memory. It is convenient for a small file; it is a poor fit for gigabytes of records.

Node.js: process lines as a stream

import { createReadStream, createWriteStream } from "node:fs";
import { createInterface } from "node:readline";

const lines = createInterface({
  input: createReadStream("events.jsonl", { encoding: "utf8" }),
  crlfDelay: Infinity,
});
const output = createWriteStream("events.json", { encoding: "utf8" });
output.write("[\n");
let first = true;
let lineNumber = 0;
for await (const line of lines) {
  lineNumber++;
  if (!line.trim()) throw new Error(`Blank JSONL line ${lineNumber}`);
  let value;
  try {
    value = JSON.parse(line);
  } catch (error) {
    throw new Error(`Invalid JSONL line ${lineNumber}: ${error.message}`);
  }
  output.write(`${first ? "" : ",\n"}${JSON.stringify(value)}`);
  first = false;
}
output.end("\n]\n");

This avoids holding the whole array in memory, although it writes a partial output file if a later line is invalid. For an import pipeline, write to a temporary file and rename it only after successful completion; handle stream errors and backpressure in production code.

Validate before using the result

For a modest output, paste the completed JSON array into the JSON Validator, then use the JSON Formatter to inspect it. Those tools expect one JSON value and are not whole-file JSONL converters. For large exports, validate with your application's streaming parser instead of pasting sensitive or very large data into a browser tab.

If you get a parse error, look for a blank line, a trailing comma on a JSONL record, or a literal line break inside a quoted string. The JSON specification, RFC 8259, defines the syntax that each JSONL line must obey.