JSON Toolkit

JSONL in pratica: aprire, convertire e diagnosticare le righe difettose

In sintesi: Passare un intero file .jsonl a JSON.parse e ricevere un errore quasi mai significa che il file è rotto — significa che la strategia di lettura è sbagliata. Questa guida copre i modi standard di ispezionare e leggere JSONL riga per riga, i comandi per convertire da e verso array JSON, una tabella di sei tipi di righe difettose e il modo corretto di consumare NDJSON da risposte HTTP in streaming.

Intermedio~9 min di letturaPubblicato il 2026-11-06Aggiornato il 2026-11-06
Guida pratica alla lettura riga per riga di file JSONL e all'isolamento delle righe difettose

In sintesi: ogni riga di un file JSONL è un valore JSON valido e indipendente, ma il file nel suo complesso non è un unico documento JSON. Se passare l'intero file a JSON.parse o json.load fallisce, nove volte su dieci il file sta bene — è la strategia di lettura a essere sbagliata.

Un record per riga, niente virgole tra le righe, niente array esterno — JSONL rinuncia alla "struttura globale del documento" in cambio di scrittura per accodamento, lettura in streaming e isolamento dei guasti riga per riga. Tutti e tre i vantaggi si realizzano solo elaborando il file riga per riga, e tutti e tre diventano errori nel momento in cui si prova a fare il parse dell'intero file in una volta.

1. Tre nomi, un formato: JSONL, NDJSON, JSON Lines

I principianti spesso assumono che la differenza di nomi implichi una differenza di sintassi. Non è così:

Nome Contesto tipico Estensione / media type
JSON Lines Ingegneria dei dati, dataset ML (pandas, Spark, Hugging Face) .jsonl
NDJSON API HTTP in streaming, dati in tempo reale .ndjson, application/x-ndjson
JSONL Abbreviazione informale per l'uno o l'altro —

Qualsiasi strumento che legge uno legge anche l'altro. L'unico criterio di scelta del nome è la convenzione del tuo ecosistema a valle: scrivi .jsonl quando prepari dataset per Hugging Face, imposta application/x-ndjson quando esponi un'API in streaming. Un file JSONL portabile soddisfa tre regole: codifica UTF-8 (senza BOM), un valore JSON completo per riga, righe separate da \n. Un newline finale a fine file è fortemente raccomandato, e le righe vuote non sono record validi. I dettagli del protocollo e la motivazione del design sono trattati nella nostra panoramica di JSON Lines e NDJSON; questo articolo riguarda strettamente cosa fare una volta che il file è tra le mani.

2. Ispezione: prima head, poi grep, l'editor per ultimo

Fare doppio clic su un .jsonl da 500 MB per aprirlo in un editor è l'errore più comune: tre milioni di righe di testo più evidenziazione della sintassi più un buffer di undo congeleranno praticamente qualsiasi editor — e non è colpa del JSON. Fare il parse di un documento da 45 MB richiede circa 208 ms in Node 22; a bloccarsi è il rendering. I formati orientati alle righe esistono per gli strumenti a riga, perché i record coincidono con le righe fisiche:

# Prima misura la scala: numero di record e dimensione del file, senza leggere il contenuto
wc -l events.jsonl && du -h events.jsonl

# Guarda come è fatto un record (conferma campi e struttura)
head -n 1 events.jsonl | jq .

# Trova i record target direttamente: gli strumenti a riga funzionano — un privilegio del JSONL
grep '"level":"error"' events.jsonl | head -n 5
tail -n 100 events.jsonl | jq 'select(.latency_ms > 500)'

Nota l'ultimo comando: ogni riga in uscita da tail è ancora un valore JSON indipendente, quindi va direttamente in jq senza dover ricostruire un array. Questo approccio funziona identicamente su file di diversi gigabyte con overhead di memoria quasi nullo — è lo stesso principio di perché i file JSON grandi rallentano le pagine.

3. I tre modelli standard di lettura riga per riga

La lettura riga per riga deve gestire tre cose: saltare le righe vuote (specialmente il newline finale), segnalare gli errori con il numero di riga e non lasciare mai che una sola riga difettosa faccia fallire tutto il lavoro. I modelli standard delle tre lingue ruotano attorno a questi punti.

Python, solo libreria standard:

import json

with open("events.jsonl", encoding="utf-8") as f:
    for line_number, line in enumerate(f, start=1):
        if not line.strip():          # salta le righe vuote
            continue
        try:
            record = json.loads(line) # analizza ogni riga indipendentemente
        except json.JSONDecodeError as e:
            print(f"Riga {line_number} fallita: {e}")  # segnala, non interrompere
            continue                  # isola la riga difettosa
        handle(record)

Node con il modulo readline integrato, memoria costante a prescindere dalla dimensione del file:

import fs from "node:fs";
import readline from "node:readline";

const input = fs.createReadStream("events.jsonl", "utf8");
const lines = readline.createInterface({ input, crlfDelay: Infinity });
let lineNumber = 0;
for await (const line of lines) {
  lineNumber += 1;
  if (!line.trim()) continue;
  try {
    handle(JSON.parse(line));
  } catch (e) {
    console.error(`Riga ${lineNumber}: ${e.message}`);
  }
}

In Go 1.27 encoding/json/v2 diventa lo standard stabile, e il suo decodificatore in stream valore per valore si adatta perfettamente al JSONL; usa gojq se devi incorporare espressioni jq. Sulle librerie, non avere fretta di installare pacchetti: jsonlines in Python riporta il numero e il testo grezzo di ogni riga che fallisce, e orjsonl legge e scrive direttamente file compressi gzip/bzip2/zstd; il pacchetto Node ndjson non pubblica rilasci dal 2020 — il readline integrato basta.

4. Convertire da e verso array JSON

La direzione conta; invertila e ogni comando sarà sbagliato. Ricorda una regola: array → JSONL è una map (ogni record compresso su una riga), JSONL → array è una collect (righe raccolte in una lista).

# Array JSON → JSONL: -c comprime ogni elemento su una riga
jq -c '.[]' records.json > records.jsonl

# JSONL → array JSON: -s raccoglie tutto l'input in un unico array
jq -s '.' events.jsonl > records.json

# Filtra JSONL riga per riga: non serve -s, jq processa l'input come stream
jq -c 'select(.active == true)' events.jsonl > active.jsonl

I one-liner Python sono equivalenti: array → JSONL scrive json.dumps(r, ensure_ascii=False) + "\n" per record; al contrario si raccolgono i json.loads riga per riga in una lista. Ogni direzione ha una trappola. All'andata (array → JSONL), ogni newline reale dentro un record deve essere escapato da json.dumps — altrimenti un record si strappa su più righe. Al ritorno (JSONL → array), non concatenare le righe in un'unica stringa gigante per analizzarla — con milioni di righe è una copia integrale non necessaria in memoria. Le aggregazioni (somme, raggruppamenti, medie) richiedono -s, che carica tutto; su file di diversi GB passa ad awk o a un accumulatore in streaming in Python.

5. Sei tipi di righe difettose e come diagnosticarle

Quasi tutti gli errori JSONL vivono a livello di riga. Validare riga per riga riportando i numeri di riga è l'unica diagnosi affidabile. Ordinate per frequenza:

Sintomo Causa radice Correzione
Extra data a fine riga Virgole aggiunte tra le righe Eliminale; ogni riga deve bastare a sé
Un record diviso su più righe Newline reale non escapato dentro il record (formattato a mano) Ricomprimi con json.dumps / jq -c
Prima riga fallisce con Unexpected token ï Il file ha un BOM Risalva in UTF-8 senza BOM
Solo l'ultima riga dà errore ma sembra corretta Newline finale mancante o spazi indesiderati Aggiungi \n; salta le righe vuote per convenzione
Errore di newline dentro una stringa Newline letterale dentro una stringa, non \n Correggi il generatore; non concatenare mai stringhe JSON a mano
Interi grandi cambiano valore Parse via double (troncamento a 2^53) Usa un parser che preserva la precisione, vedi precisione bigint

La routine diagnostica è sempre di tre passi: head -n 1 per confermare codifica e struttura della prima riga; eseguire uno script riga per riga che riporta il numero di ogni riga difettosa invece di fermarsi alla prima; poi estrarre le righe difettose una a una — una riga è un record completo, ispezionarla in isolamento non costa nulla. Per gli import in batch la politica raccomandata è "quarantena, non aborto": scrivi le righe difettose con i loro numeri in errors.jsonl, continua a caricare quelle buone, poi decidi il reimport in base alla proporzione di righe difettose.

6. JSONL su HTTP: consumo riga per riga e backpressure

Lo schema tipico di NDJSON su HTTP è un server che scrive di continuo e un client che consuma di continuo. Sul client, non chiamare response.json() — bufferizza l'intero corpo prima del parse, vanificando il senso dello streaming. Leggi lo stream del corpo direttamente, dividi su \n e riporta qualsiasi frammento finale incompleto all'iterazione successiva:

const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = "";
for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += value;
  const lines = buffer.split("\n");
  buffer = lines.pop();               // l'ultimo pezzo può essere una riga incompleta
  for (const line of lines) {
    if (line.trim()) handle(JSON.parse(line));
  }
}

Sul server due cose sono obbligatorie: prima, quando res.write() restituisce false, attendi l'evento drain (backpressure), altrimenti la memoria cresce senza limiti ogni volta che il consumatore è più lento del produttore; seconda, disabilita il middleware gzip sulle rotte in streaming — gzip deve bufferizzare l'intera risposta prima di comprimere qualcosa, il che neutralizza completamente lo streaming NDJSON. Entrambi i comportamenti sono confermati da test locali e da più fonti indipendenti in inglese, non sono supposizioni.

7. Quando JSONL è la scelta sbagliata

Anche lo spazio negativo della scelta del formato conta. Tre scenari dovrebbero restare con l'array JSON:

Requisito Perché JSONL mal si adatta Usa invece
Validare la struttura dell'intero documento Nessuna struttura globale; la deriva dei campi passa inosservata Array JSON + Schema
Accesso casuale al record N Lunghezze di riga variabili; nessuna ricerca binaria SQLite / Parquet
Annidamento profondo, riferimenti tra record Ogni riga è isolata; i riferimenti non hanno dove stare Documento JSON standard

Regola empirica in una frase: se i record arrivano nel tempo o vengono consumati riga per riga (log, eventi, export, dataset di addestramento), usa JSONL; se un piccolo volume viene prodotto una volta, consumato una volta e richiede la semantica del documento intero, usa un array JSON. Le varianti derivate (JSON Text Sequences delimitate da RS) sono un altro argomento — vedi l'articolo su RFC 7464.

FAQ

C'è una differenza reale tra JSONL e NDJSON?

In termini ingegneristici no. Entrambi significano "un valore JSON completo per riga, nessun wrapper esterno, niente virgole tra le righe", e qualsiasi strumento che ne supporta uno supporta anche l'altro. La differenza è di denominazione e abitudini dell'ecosistema: .jsonl domina l'ingegneria dei dati e i dataset ML; .ndjson e application/x-ndjson dominano le API HTTP in streaming. Quando ti integri con un sistema esterno, segui l'estensione e il media type che la sua documentazione specifica.

Posso leggere un file .jsonl direttamente con JSON.parse?

Non il file intero. JSON.parse accetta esattamente un testo JSON, mentre JSONL è una serie di valori indipendenti disposti riga per riga, quindi il parse dell'intero file fallisce sempre con "Unexpected token" o "Extra data". Dividi in righe e analizza ogni riga non vuota singolarmente, riportando il numero di riga in caso di fallimento.

Se una riga di un file JSONL è corrotta, l'intero file è compromesso?

No — l'isolamento dei guasti riga per riga è il cuore del design del formato. Una riga difettosa danneggia solo sé stessa: saltala, annota il suo numero e continua la lettura. Per gli import in batch, metti in quarantena le righe difettose in un file separato, carica i record corretti normalmente, poi decidi correzione e reimport in base alla proporzione di righe difettose.

Come converto JSONL in Excel o CSV?

Prima trasformalo in array o estrai i campi riga per riga, poi esegui una conversione tabellare: comprimi con jq -c '.[]' data.jsonl e passa il risultato a un convertitore, o invia ogni riga tramite json.loads a pandas. Attenzione: i campi annidati appiattiti producono nomi di colonne con punti, i campi array possono essere serializzati solo come testo dentro la cella, e le colonne di ID lunghi andrebbero dichiarate come stringhe in anticipo per evitare perdite di precisione.

Riferimenti

Strumenti correlati

Articoli correlati

JSON Lines e NDJSON: dati in streaming

Un valore JSON per riga ti regala l'appendibilità, lo streaming e il danno isolato a una sola riga; il costo è l'assenza di struttura complessiva, quindi la validazione deve avvenire riga per riga.

Intermedio~10 min di letturaAggiornato il 2026-10-30

Parsing, rendering e streaming di JSON di grandi dimensioni

Il parsing spesso non è il collo di bottiglia; lo sono il rendering e le copie. Ottimizza lungo i quattro strati "parse → traverse → render → transport" così il tuo sforzo non va sprecato.

Intermedio~13 min di letturaAggiornato il 2026-10-31