JSON Toolkit

FAQ su interrogazione e validazione: JSONPath e Schema

In sintesi: Se le query di percorso devono usare JSONPath o Pointer, come scegliere tra Patch e Merge Patch, cosa può bloccare Schema e come scegliere una versione draft — tutto spiegato in un colpo.

Intermedio~12 min di letturaPubblicato il 2026-10-30Aggiornato il 2026-10-30
Copertina della FAQ su interrogazione e validazione

In sintesi: usa JSONPath per trovare nodi per condizione, JSON Pointer o JSON Patch per indirizzare e modificare posizioni esatte, e Merge Patch per unire configurazioni; fissa i nuovi Schema su 2020-12 e attiva allErrors così ogni errore torna in un unico passaggio.

L'interrogazione è "trovalo"; la validazione è "conferma che sia corretto". Ognuna ha il proprio standard, quindi non mescolarle.

1. Query di percorso

Come scelgo tra JSONPath e JSON Pointer?

Dipende da se stai "trovando per condizione" o "recuperando per posizione".

Dimensione JSONPath JSON Pointer
Standard RFC 9535 (2024) RFC 6901 (2013)
Espressione $.users[?(@.age > 18)].id /users/0/id
Wildcard e ricorsione Supporta *, .. Non supportati
Filtro condizionale Supportato Non supportato
Risultato Un insieme di nodi (possibilmente diversi) Una singola posizione
Espressività Forte, ma con un pesante retaggio di differenze tra implementazioni Debole, ma pienamente deterministica
Uso tipico Estrazione da log, debug, esplorazione dei dati JSON Patch, indirizzamento di configurazione, percorsi di errore

La regola di scelta è semplice:

  • Devi "ottenere tutti i nodi che corrispondono a una condizione" → JSONPath;
  • Devi "fissare una posizione esatta e aggiungerla, rimuoverla o modificarla" → JSON Pointer;
  • Usali insieme: trova il nodo con JSONPath e descrivi la sua posizione con Pointer.

Non usare JSONPath per l'indirizzamento in un contratto API. JSONPath restituisce un insieme, e i tre rami per risultati vuoti, a un elemento e a più elementi rendono il protocollo complicato; Pointer significa "o punta a una posizione esistente o non esiste", il che si adatta meglio a un contratto.

Qual è la differenza tra $.. e $.*?

$.. è la discesa ricorsiva e percorre ogni livello; $.* restituisce solo i figli diretti del livello corrente.

{
  "a": { "id": 1, "child": { "id": 2 } },
  "b": { "id": 3 }
}
  • $.* → i due oggetti {id:1, child:{...}} e {id:3};
  • $..id → tre valori: 1, 2, 3;
  • $.*.id → 1 e 3, escludendo il 2 annidato.

La discesa ricorsiva è comoda ma costosa: $.. percorre l'intero albero, che su un documento grande è O(n), e raccoglie facilmente campi profondi che non intendevi prendere. Se funziona un livello concreto, evita ...

I filtri possono confrontare stringhe? Possono corrispondere con regex?

Il confronto tra stringhe funziona; la regex dipende dall'implementazione.

Sintassi standard (RFC 9535):

$.items[?(@.status == "active")]
$.items[?(@.price > 10 && @.stock > 0)]
$.items[?(@.name == $.target)]      // compare with another node

Il confronto tra stringhe usa virgolette doppie (le virgolette singole sono ammesse anche in RFC 9535, ma alcune implementazioni più vecchie accettano solo le doppie). Il confronto numerico segue la semantica numerica; mescolare una stringa con un numero, come "10" > 9, è comportamento indefinito nello standard, quindi non farci affidamento.

Per le regex, RFC 9535 definisce le funzioni match() e search(), ma il loro supporto varia notevolmente tra le implementazioni. L'approccio sicuro: lascia che JSONPath faccia il filtro grossolano, e fai la corrispondenza regex nel codice dopo aver recuperato i risultati.

Devo usare JSON Patch o Merge Patch?

Entrambi descrivono "come cambiare qualcosa", ma la loro espressività e semantica sono completamente diverse.

Dimensione JSON Patch (RFC 6902) Merge Patch (RFC 7386)
Forma Un array di operazioni [{"op":"add",...}] Un oggetto parziale
Operazioni supportate add / remove / replace / move / copy / test Solo merge (eliminazione tramite null)
Può indirizzare elementi di array Sì, tramite indice No, l'intero array viene sostituito
Atomicità test consente il locking ottimistico Nessuna
Dimensione del documento Grande ma preciso Piccola ma limitata nell'espressività
Adatto a Modifiche granulari, aggiornamenti parziali di API Unione di configurazioni, invio parziale di form

Criteri di decisione:

  • Per cambiare un campo del terzo elemento di un array → JSON Patch;
  • Per unire diverse voci di configurazione nella configurazione esistente → Merge Patch;
  • Per ottenere il controllo di concorrenza "fallisci se il valore originale non è X" → test di JSON Patch.

Nota la trappola nella semantica di null di Merge Patch: null significa "elimina questa chiave", non "imposta il valore a null". Impostare un valore a null è impossibile con Merge Patch.

2. Validazione con Schema

Quali problemi risolve JSON Schema?

Trasforma "come dovrebbero apparire i dati" da un accordo verbale in un contratto verificabile dalla macchina. Gli errori che può davvero bloccare:

  • Campi obbligatori mancanti (required);
  • Tipo errato, per esempio una stringa dove ci si aspetta un numero (type);
  • Valori fuori intervallo (minimum / maximum / minLength);
  • Valori fuori dall'insieme consentito (enum);
  • Tipi di elementi dell'array incoerenti o lunghezza dell'array errata (items / minItems);
  • Campi mancanti in strutture annidate (validazione ricorsiva tramite properties);
  • Campi in eccesso (additionalProperties: false).

Ciò che non può risolvere: regole di business ("l'ora di fine deve essere successiva all'ora di inizio"), condizioni tra campi (alcune possono essere espresse con if/then, ma la leggibilità peggiora), unicità nel database e controlli di permessi.

Quindi Schema è il guardiano della "forma dei dati", non un sostituto della logica di business.

Devo usare draft-07 o 2020-12?

Usa 2020-12 per i nuovi progetti; per i sistemi esistenti, controlla cosa supporta il validatore.

Versione Stato Modifiche chiave
draft-04 Superata Molte librerie più vecchie la usano ancora come default
draft-07 Standard de facto Supporto più ampio, ecosistema più ricco
2019-09 Transitoria Ha introdotto $defs, unevaluatedProperties
2020-12 Attualmente raccomandata items diviso in prefixItems + items, supporta $dynamicRef

Due trappole di migrazione:

  1. La sintassi delle tuple è cambiata: in draft-07 un items con valore array esprimeva una tupla; 2020-12 l'ha spostata in prefixItems;
  2. $ref e parole chiave sorelle: in draft-07 le parole chiave sorelle vengono ignorate quando $ref è presente; 2020-12 consente loro di coesistere.

Se il validatore supporta solo draft-07, scrivere comunque sintassi 2020-12 viene ignorato silenziosamente — e il caso più pericoloso è "la validazione passa" quando in realtà non è stato validato nulla.

Come genero i tipi TypeScript da uno Schema?

Due percorsi: derivare i tipi dallo Schema, o generare lo Schema dai tipi.

Da Schema a tipi (il backend definisce il contratto, il frontend lo consuma):

import type { FromSchema } from 'json-schema-to-ts';

const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'string' },
    age: { type: 'integer' },
  },
  required: ['id'],
  additionalProperties: false,
} as const;

type User = FromSchema<typeof userSchema>;
// { id: string; age?: number }

La chiave è as const: senza di essa, type: 'string' viene inferito come string invece che come il letterale 'string', e l'inferenza si rompe.

Da tipi a Schema (il frontend ha già i tipi e necessita di validazione a runtime):

import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

const userSchema = z.object({ id: z.string(), age: z.number().int().optional() });
const jsonSchema = zodToJsonSchema(userSchema);

Il vantaggio di questo percorso è che "una definizione produce sia i tipi sia la validazione a runtime"; il costo è introdurre un DSL, e lo Schema generato può portare dettagli implementativi (per esempio una struttura $ref poco attraente).

Come localizzo il campo specifico quando la validazione fallisce?

Guarda l'instancePath (2020-12) o il dataPath (draft-07) restituito dal validatore; è un JSON Pointer alla posizione dell'errore.

[
  {
    "instancePath": "/items/2/price",
    "schemaPath": "#/properties/items/items/properties/price/type",
    "keyword": "type",
    "message": "must be number"
  }
]

instancePath: "/items/2/price" ti dice direttamente: il campo price del terzo elemento dell'array items ha il tipo sbagliato.

Due suggerimenti pratici:

  1. Restituisci tutti gli errori in una volta invece di lanciare sul primo — il frontend che sistema tutto in un colpo è più veloce dei tentativi andata e ritorno;
  2. Trasforma il Pointer in linguaggio semplice prima di mostrarlo agli utenti finali; /items/2/price è utile agli sviluppatori e inutile agli utenti.

allErrors: true è l'interruttore nella maggior parte dei validatori (Ajv richiede di attivarlo esplicitamente); il default è spesso la modalità fail-fast.

additionalProperties: false dovrebbe essere attivato?

Attivalo per i contratti interni; sii prudente con le API pubbliche.

Una volta attivato, qualsiasi campo non dichiarato sotto properties fa fallire la validazione. Il vantaggio è intercettare errori di battitura (userNmae) e passaggi inattesi; lo svantaggio è che l'aggiunta di un campo a monte rompe i consumatori a valle, distruggendo la retrocompatibilità.

Compromesso:

Scenario Raccomandazione
Tra servizi interni Attivalo. Le modifiche ai campi passano attraverso la gestione delle versioni
API pubbliche esterne Disattivalo. Consenti all'upstream di aggiungere campi
Consumo di dati di terze parti Disattivalo. Non controlli l'altra parte
Validazione della configurazione Attivalo. Un errore di battitura nella configurazione deve fallire subito

Come valido che gli elementi di un array siano unici?

Due semantiche diverse — non confonderle:

{ "uniqueItems": true }

Questo deduplica l'intero elemento, richiedendo che l'array non contenga due elementi profondamente uguali.

{
  "type": "array",
  "items": { "type": "object", "properties": { "id": { "type": "string" } } },
  "uniqueItems": true
}

Lo snippet sopra garantisce solo che gli oggetti nel loro insieme non siano duplicati; {"id":"a","name":"x"} e {"id":"a","name":"y"} non contano come duplicati.

Per deduplicare in base a un campo specifico, Schema non può esprimerlo; devi validare nel livello di business:

const ids = items.map((item) => item.id);
if (new Set(ids).size !== ids.length) {
  throw new Error('id 重复');
}

format valida davvero email e date?

Non necessariamente. Nella specifica, format è un'"annotazione" per impostazione predefinita e non un'"asserzione", quindi un validatore può raccoglierlo senza validarlo.

  • Ajv richiede di installare e configurare il pacchetto separato ajv-formats, altrimenti format: "email" è di fatto inerte;
  • Alcuni validatori trattano format come un'asserzione attivata per impostazione predefinita, quindi il comportamento è incoerente;
  • Anche quando è attivata, la regex di email è molto permissiva — a@b passa.

Conclusione: non trattare format come validazione forte. Fai una validazione rigorosa di email, URL e date nel codice di business; mantenere format nello Schema come documentazione vale la pena, ma non aspettarti che fermi dati sporchi.

3. Riferimento rapido

Domanda Conclusione
Trovare nodi per condizione JSONPath
Indirizzamento preciso, applicare patch JSON Pointer
Cambiare elementi di un array JSON Patch; Merge Patch non può farlo
Unire configurazioni Merge Patch
Versione di Schema per nuovi progetti 2020-12, attenzione a prefixItems
Generare tipi TS da uno Schema json-schema-to-ts + as const
Localizzare gli errori di validazione Leggi instancePath, attiva allErrors
Deduplicare per campo Schema non può; usa un Set nel livello di business
format: email La maggior parte dei validatori non lo controlla per impostazione predefinita, quindi non farci affidamento