13 · Output JSON
Uno script non sa leggere la prosa. Se chiedi a Claude “quanti TODO ci sono?” e lui risponde “ne ho trovati dodici, di cui tre importanti”, per un programma quella frase è un muro. Questo modulo insegna a far parlare Claude in dati: risposte JSON con una forma precisa, che uno script legge senza interpretare. È il pezzo che trasforma gli esperimenti del modulo 12 in automazioni affidabili.
Se parti da zerojq: l'attrezzo del modulo (e come installarlo)
jq è lo strumento da terminale per estrarre dati da un JSON: gli passi un
“percorso” come .result o .utenti[0].nome e lui tira fuori quel pezzo — le pinzette per una
scatola disordinata. Si installa con brew install jq su macOS, winget install jqlang.jq su
Windows, sudo apt install jq su Linux. I tre flag che vedrai ovunque nel modulo: -r = output
“grezzo”, senza le virgolette attorno alle stringhe; -e = “fallisci se il risultato è
nullo/falso”, utile negli script che devono fermarsi; // dentro un’espressione = “oppure”:
.nome // "sconosciuto" significa “il nome, o «sconosciuto» se manca”.
Obiettivi
Sezione intitolata “Obiettivi”- Scegliere il formato di output giusto per quello che devi fare.
- Leggere lo schema di una risposta
jsone sapere quali campi esistono davvero. - Forzare un output strutturato con
--json-schema, invece di parsare della prosa. - Consumare
stream-jsonper avere token in tempo reale e seguire i subagent. - Usare gli eventi di sistema per far fallire una CI quando un plugin o un server MCP non si carica.
Concetti chiave
Sezione intitolata “Concetti chiave”Tre formati, tre usi diversi.
| Formato | Cosa produce | Quando |
|---|---|---|
text (default) |
Testo semplice | Uso a mano, pipe verso altri comandi Unix |
json |
Un singolo oggetto JSON con risultato, ID di sessione, costi e metadati | Script che devono ramificare o tracciare la spesa |
stream-json |
Un oggetto JSON per riga, man mano che le cose accadono (il formato si chiama JSONL o NDJSON) | Seguire il lavoro in tempo reale, monitoraggio |
Parsare la prosa è un antipattern. Chiedere a un modello di rispondere “solo con l’elenco” e poi
estrarre con una regex funziona finché non smette di funzionare. Per un output che uno script deve
consumare, esiste --json-schema: la risposta viene validata contro lo schema e arriva nel campo
structured_output già strutturata.
Il costo è nella risposta. Con --output-format json, la risposta include total_cost_usd e una
ripartizione per modello: uno script può tracciare la spesa per invocazione senza aprire la dashboard
di utilizzo sul sito di Anthropic. Sono stime calcolate localmente e possono differire dai
conteggi ufficiali del tuo piano.
Il formato json
Sezione intitolata “Il formato json”claude -p "Summarize this project" --output-format jsonUn esempio reale, prodotto da claude --version 2.1.233, ridotto ai campi principali:
{ "type": "result", "subtype": "success", "is_error": false, "result": "ok", "session_id": "62a5cb30-f042-4152-8909-f2ad6571c0af", "num_turns": 1, "stop_reason": "end_turn", "terminal_reason": "completed", "total_cost_usd": 0.108213, "duration_ms": 2426, "duration_api_ms": 3067, "permission_denials": [], "api_error_status": null, "usage": { "input_tokens": 2, "output_tokens": 4, "cache_read_input_tokens": 16016, "cache_creation_input_tokens": 9952 }}Quella sopra è una risposta vera (accorciata). Ora i campi, quelli che ti serviranno davvero:
| Campo | Tipo | Cosa contiene |
|---|---|---|
result |
string | Il testo della risposta. È il campo che estrai il 90% delle volte |
structured_output |
object | Presente solo con --json-schema: l’oggetto validato |
is_error |
boolean | Se il run è andato in errore |
subtype |
string | "success" o il tipo di fallimento |
session_id |
string | ID della sessione, per riprenderla con --resume |
num_turns |
integer | Quanti turni agentici sono serviti |
stop_reason |
string | Perché si è fermato, es. "end_turn" |
terminal_reason |
string | Es. "completed" |
total_cost_usd |
number | Costo stimato dell’invocazione |
usage |
object | Token di input, output, cache read e cache creation |
modelUsage |
object | Ripartizione per modello, con costo e finestra di contesto |
permission_denials |
array | Le azioni negate dai permessi durante il run |
duration_ms / duration_api_ms |
integer | Durata totale e durata delle chiamate API |
api_error_status |
integer or null | Stato HTTP in caso di errore API |
uuid |
string | Identificatore dell’evento |
Estrarre con jq
Sezione intitolata “Estrarre con jq”# Il testo della rispostaclaude -p "Summarize this project" --output-format json | jq -r '.result'
# L'ID di sessione, per riprenderla doposid=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
# Il costoclaude -p "..." --output-format json | jq '.total_cost_usd'
# Fallire se il run è andato in erroreclaude -p "..." --output-format json | jq -e '.is_error == false' > /dev/nullOutput validato con --json-schema
Sezione intitolata “Output validato con --json-schema”È la funzione più utile del modulo. Uno schema JSON è la descrizione
della forma che un JSON deve avere: quali campi esistono, di che tipo è ciascuno, quali sono
obbligatori. Il modulo prestampato, insomma: se manca un campo obbligatorio, il modulo non è valido.
Passandolo a Claude con --json-schema, la risposta deve rispettare quella forma — e il tuo
script può leggerla alla cieca, senza controllare se stavolta il modello ha scritto le cose in modo
diverso:
claude -p "Extract the main function names from auth.py" \ --output-format json \ --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'L’oggetto validato finisce in structured_output; il campo result contiene lo stesso JSON come
stringa:
{ "result": "{\"functions\":[\"alpha\",\"beta\"]}", "structured_output": { "functions": ["alpha", "beta"] }, "subtype": "success", "is_error": false}Quindi:
claude -p "Extract function names from auth.py" \ --output-format json \ --json-schema '{...}' \ | jq '.structured_output'Il formato stream-json
Sezione intitolata “Il formato stream-json”Un oggetto JSON per riga, man mano che le cose accadono. Richiede --verbose; per i token man mano che
vengono generati serve anche --include-partial-messages:
claude -p "Explain recursion" --output-format stream-json --verbose --include-partial-messagesI tipi di evento
Sezione intitolata “I tipi di evento”Verificati su un run reale:
type |
subtype |
Quando |
|---|---|---|
system |
init |
Primo evento: metadati di sessione |
system |
api_retry |
Prima di ritentare una richiesta API fallita |
system |
plugin_install |
Installazione di plugin da marketplace, con CLAUDE_CODE_SYNC_PLUGIN_INSTALL |
assistant |
— | Un messaggio dell’assistente |
user |
— | Un messaggio utente, o un risultato di tool |
stream_event |
— | Delta di streaming, con --include-partial-messages |
rate_limit_event |
— | Informazioni sui rate limit |
result |
success |
Ultimo evento: risposta finale, costo, metadati |
Streaming del testo
Sezione intitolata “Streaming del testo”claude -p "Write a poem" --output-format stream-json --verbose --include-partial-messages | \ jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'-r produce stringhe grezze senza virgolette, -j unisce senza a-capo: i token scorrono in continuo.
Seguire i subagent
Sezione intitolata “Seguire i subagent”I messaggi dei subagent compaiono nello stream come assistant e user il cui campo
parent_tool_use_id è l’ID della chiamata a tool che li ha generati. I messaggi della conversazione
principale hanno null in quel campo.
Di default vengono emessi solo i blocchi tool_use e tool_result dei subagent. Con
--forward-subagent-text — o la variabile CLAUDE_CODE_FORWARD_SUBAGENT_TEXT — vengono emessi anche
testo e thinking, così puoi ricostruire la trascrizione di ciascuno. Con l’opzione attiva, i messaggi
arrivano da ogni livello di annidamento: un subagent che ne genera un altro produce messaggi il cui
parent_tool_use_id è l’ID della chiamata che li ha creati, quindi puoi ricostruire l’albero completo
seguendo gli ID.
L’evento system/init
Sezione intitolata “L’evento system/init”È il primo evento e contiene i metadati di sessione. I campi presenti in un run reale:
type, subtype, cwd, session_id, tools, mcp_servers, model, permissionMode,slash_commands, terminal_slash_commands, apiKeySource, claude_code_version,output_style, agentsPiù, quando applicabile:
| Campo | Cosa contiene |
|---|---|
plugins |
Plugin caricati correttamente, con name e path |
plugin_errors |
Errori di caricamento, con plugin, type, message. La chiave è assente se non ci sono errori |
mcp_server_errors |
Voci di --mcp-config scartate dalla validazione, con name, type, message. Assente se non ci sono errori |
capabilities |
Array di stringhe con i comportamenti di protocollo implementati. Usalo per il feature detection — “c’è la funzione che mi serve?” chiesto direttamente, invece di dedurlo dal numero di versione (che può mentire) |
L’evento system/api_retry
Sezione intitolata “L’evento system/api_retry”| Campo | Tipo | Cosa contiene |
|---|---|---|
attempt |
integer | Numero del tentativo, da 1 |
max_retries |
integer | Retry totali permessi |
retry_delay_ms |
integer | Millisecondi al prossimo tentativo |
error_status |
integer or null | Stato HTTP, o null per errori di connessione |
error |
string | Categoria: authentication_failed, oauth_org_not_allowed, billing_error, rate_limit, overloaded, invalid_request, model_not_found, server_error, max_output_tokens, unknown |
Esempio guidato
Sezione intitolata “Esempio guidato”-
Vedi la differenza tra i tre formati. Stesso prompt, tre volte:
Terminal window claude -p "list three benefits of code review, one per line"Terminal window claude -p "list three benefits of code review, one per line" --output-format json | jq -r '.result'Terminal window claude -p "list three benefits of code review, one per line" \--output-format stream-json --verbose | jq -r '.type'L’ultimo ti mostra la sequenza degli eventi.
-
Estrai il costo di un’invocazione.
Terminal window claude -p "explain what this project does" --output-format json \| jq '{costo: .total_cost_usd, turni: .num_turns, secondi: (.duration_ms/1000)}' -
Ottieni dati strutturati invece di prosa. Il salto di qualità:
Terminal window claude -p "List every TODO comment in src/ with its file and line" \--allowedTools "Read,Grep,Glob" \--output-format json \--json-schema '{"type": "object","properties": {"todos": {"type": "array","items": {"type": "object","properties": {"file": { "type": "string" },"riga": { "type": "integer" },"testo": { "type": "string" }},"required": ["file", "riga", "testo"]}}},"required": ["todos"]}' \| jq -r '.structured_output.todos[] | "\(.file):\(.riga) \(.testo)"'Output atteso, nella forma:
src/api/users.js:42 handle paginationsrc/lib/cache.js:17 invalidate on write -
Streaming del testo in tempo reale.
Terminal window claude -p "Write a short poem about debugging" \--output-format stream-json --verbose --include-partial-messages \| jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text' -
Segui cosa fa Claude, tool per tool. Utile per capire dove si è impantanato:
Terminal window claude -p "Find and fix the failing test" \--allowedTools "Read,Edit,Bash(npm test *)" \--output-format stream-json --verbose \| jq -r 'select(.type == "assistant")| .message.content[]?| select(.type == "tool_use")| "→ \(.name) \(.input | tostring | .[0:80])"' -
Distingui i subagent dalla conversazione principale.
Terminal window claude -p "Use a subagent to map the error handling in src/" \--output-format stream-json --verbose --forward-subagent-text \| jq -r 'select(.type=="assistant") | if .parent_tool_use_id == null then "[main]" else "[sub]" end' -
Un gate di CI completo. Mette insieme validazione degli eventi di sistema e output strutturato:
ci-gate.sh #!/usr/bin/env bashset -euo pipefailOUT=$(git diff origin/main...HEAD | claude -p \"Review this diff. Report only defects affecting correctness or security." \--output-format json \--json-schema '{"type":"object","properties":{"bloccanti":{"type":"array","items":{"type":"object","properties":{"file":{"type":"string"},"problema":{"type":"string"},"scenario":{"type":"string"}},"required":["file","problema","scenario"]}}},"required":["bloccanti"]}' \--max-turns 10)# 1. Il run è riuscito?echo "$OUT" | jq -e '.is_error == false' >/dev/null || { echo "run fallito" >&2; exit 2; }# 2. Ci sono stati permessi negati che hanno azzoppato la review?NEGATI=$(echo "$OUT" | jq '.permission_denials | length')[ "$NEGATI" -gt 0 ] && echo "attenzione: $NEGATI azioni negate dai permessi" >&2# 3. Il verdettoN=$(echo "$OUT" | jq '.structured_output.bloccanti | length')echo "$OUT" | jq -r '.structured_output.bloccanti[] | "✘ \(.file): \(.problema)\n → \(.scenario)"'echo "costo: $(echo "$OUT" | jq -r '.total_cost_usd') USD"[ "$N" -eq 0 ] && exit 0 || exit 1Il controllo su
permission_denialsè quello che quasi nessuno fa: se Claude non è riuscito a leggere metà dei file, il “nessun problema trovato” non vuol dire niente.
Esercizio
Sezione intitolata “Esercizio”Costruisci un report di progetto in JSON, consumabile da un altro programma.
- Fai produrre a Claude un inventario strutturato del progetto: linguaggi, entry point, comandi, rischi.
- Lo schema deve essere abbastanza stretto da rendere l’output usabile senza post-processing.
- Aggiungi al report i metadati del run: costo, durata, modello.
- Salva tutto in un unico file JSON valido.
Soluzione
#!/usr/bin/env bashset -euo pipefail
SCHEMA='{ "type": "object", "properties": { "linguaggi": { "type": "array", "items": { "type": "object", "properties": { "nome": { "type": "string" }, "ruolo": { "type": "string", "enum": ["primario", "secondario", "tooling"] } }, "required": ["nome", "ruolo"] } }, "entrypoint": { "type": "array", "items": { "type": "object", "properties": { "file": { "type": "string" }, "descrizione":{ "type": "string" } }, "required": ["file", "descrizione"] } }, "comandi": { "type": "object", "properties": { "install": { "type": "string" }, "dev": { "type": "string" }, "test": { "type": "string" }, "build": { "type": "string" } }, "required": ["install", "test"] }, "rischi": { "type": "array", "items": { "type": "object", "properties": { "descrizione": { "type": "string" }, "prova": { "type": "string" }, "gravita": { "type": "string", "enum": ["alta", "media", "bassa"] } }, "required": ["descrizione", "prova", "gravita"] } } }, "required": ["linguaggi", "entrypoint", "comandi", "rischi"]}'
# Lancia Claude con lo schema definito sopra. Il prompt, in italiano:# "fai l'inventario del progetto; per i comandi riporta quelli ESATTI dei# file di configurazione, non quelli che ti aspetteresti; per i rischi,# solo cose puntabili col dito — 'prova' deve citare un percorso".RUN=$(claude -p "Produce an inventory of this project. For comandi, report the exact commands from the project config files, not what you would expect. For rischi, report only things you can point at concretely; prova must cite a file path." \ --allowedTools "Read,Grep,Glob" \ --output-format json \ --json-schema "$SCHEMA" \ --max-turns 15)
# Unisce inventario e metadati in un solo documento. La sintassi jq# '{chiave: espressione, ...}' COSTRUISCE un nuovo oggetto: qui uno con# due sezioni — l'inventario di Claude e i dati sul run stesso.echo "$RUN" | jq '{ inventario: .structured_output, run: { modello: (.modelUsage | keys | .[0]), # il nome del modello usato costo_usd: .total_cost_usd, durata_ms: .duration_ms, turni: .num_turns, negati: (.permission_denials | length) # quante azioni sono state negate }}' > inventario.json
# ultima verifica: "jq -e ." fallisce se il file non e JSON valido,# e "&&" stampa la conferma SOLO se la verifica passajq -e . inventario.json > /dev/null && echo "✔ inventario.json scritto e valido"Le scelte che rendono lo schema utile:
| Scelta | Effetto |
|---|---|
enum su ruolo e gravita |
Elimina la variabilità: non otterrai “abbastanza importante” al posto di media |
prova obbligatoria nei rischi |
Il modello deve citare un file: taglia le osservazioni generiche |
comandi come oggetto con chiavi fisse |
Uno script può leggere .comandi.test senza cercare in un array |
install e test obbligatori, dev e build no |
Non tutti i progetti li hanno: renderli obbligatori costringerebbe a inventarli |
| L’istruzione “the exact commands from the project config files” | Lo schema garantisce la forma, non la veridicità: quella la chiedi nel prompt |
L’ultimo punto è il più importante e il più frainteso: --json-schema garantisce che l’output sia
strutturato, non che sia corretto. La verifica resta compito tuo — nel gate del
modulo 12 era il test che passa o fallisce; qui è il campo prova che puoi
controllare a campione.
Errori comuni e troubleshooting
Sezione intitolata “Errori comuni e troubleshooting”| Sintomo | Causa | Soluzione |
|---|---|---|
stream-json non produce nulla di utile |
Manca --verbose |
È obbligatorio con stream-json |
| Non vedi i token man mano che arrivano | Manca --include-partial-messages |
Aggiungilo per ricevere gli eventi stream_event |
Error: --json-schema is not a valid JSON Schema |
Schema malformato | La diagnostica del validatore segue il messaggio |
Uno schema con "format": "email" non valida le email |
format è trattato come annotazione |
Valida a valle, o usa pattern |
structured_output è assente |
Non hai passato --json-schema |
Senza schema, c’è solo result come testo |
result contiene JSON come stringa |
È il comportamento previsto con --json-schema |
L’oggetto parsato sta in structured_output |
| La CI passa ma i risultati sono strani | Un server MCP o un plugin non si è caricato: il run esce pulito comunque | Controlla plugin_errors e mcp_server_errors in system/init |
| La review “non trova nulla” ma non ha letto niente | Permessi negati durante il run | Controlla permission_denials nella risposta |
| La fine di una risposta lunga viene tagliata | Consumatore lento | Claude Code attende lo svuotamento, fino a 30 s: leggi lo stream più rapidamente |
| Non distingui i messaggi dei subagent | Serve il campo parent_tool_use_id |
null = conversazione principale; con --forward-subagent-text arrivano anche testo e thinking |
| I costi non tornano con la fattura | Sono stime lato client | Per i valori reali usa la dashboard di utilizzo |
Cheat-sheet
Sezione intitolata “Cheat-sheet”I tre formati
claude -p "..." # textclaude -p "..." --output-format json # oggetto singoloclaude -p "..." --output-format stream-json --verbose # un oggetto per rigaI campi che userai
| Campo | |
|---|---|
.result |
Il testo della risposta |
.structured_output |
L’oggetto validato, con --json-schema |
.session_id |
Per riprendere con --resume |
.is_error |
Per far fallire uno script |
.total_cost_usd |
Costo stimato |
.permission_denials |
Azioni negate durante il run |
.num_turns |
Turni agentici usati |
Ricette jq
# Testo della risposta| jq -r '.result'
# Output strutturato| jq '.structured_output'
# Fallisce se il run è andato in errore| jq -e '.is_error == false' > /dev/null
# Cattura l'ID di sessionesid=$(... | jq -r '.session_id')
# Streaming del testo| jq -rj 'select(.type=="stream_event" and .event.delta.type?=="text_delta") | .event.delta.text'
# Solo le chiamate a tool| jq -r 'select(.type=="assistant") | .message.content[]? | select(.type=="tool_use") | .name'
# Gate: nessun errore di plugin o MCP| jq -e 'select(.type=="system" and .subtype=="init") | (.plugin_errors // []) + (.mcp_server_errors // []) | length == 0'Eventi di stream-json
system/init → primo evento, metadati di sessionesystem/api_retry → retry di una chiamata APIsystem/plugin_install → installazione pluginassistant, user → messaggi (parent_tool_use_id = null → main)stream_event → delta di testo (serve --include-partial-messages)rate_limit_event → informazioni sui rate limitresult/success → ultimo evento, risposta finale e costiPagine ufficiali consultate, verificate il 16 agosto 2026:
- Run Claude Code programmatically — formati di output,
--json-schema, eventi di stream,system/init,api_retry - CLI reference —
--output-format,--json-schema,--include-partial-messages,--forward-subagent-text - Agent SDK — structured outputs — output strutturati nell’SDK
- Agent SDK — streaming output — streaming programmatico
- Cost tracking — natura delle stime di costo
- JSON Schema — lo standard degli schemi
- jq — il parser usato negli esempi
Gli schemi di risposta mostrati in questa pagina sono stati verificati eseguendo claude --version 2.1.233
con --output-format json e --output-format stream-json il 16 agosto 2026.
Corso indipendente, non affiliato ad Anthropic o Google. Claude e Claude Code sono marchi di Anthropic; Antigravity e Gemini sono marchi di Google.