Rendere compatibile il proprio codice PHP con lo standard JSON5

Rendere compatibile il proprio codice PHP con lo standard JSON5

In una subentranza su un progetto legacy PHP mi sono trovato davanti a un applicativo in cui qualcuno, anni prima, aveva sostituito tutte le chiamate a json_decode() con json5_decode(), seguendo alla lettera un consiglio diffuso dell'epoca: installa la libreria, fai un search-and-replace globale, e il tuo codice "parla JSON5". Il risultato era che anche gli endpoint API che ricevevano payload da sistemi esterni passavano per il parser JSON5, più permissivo e più lento del parser nativo, senza alcun motivo, perché quei payload erano JSON standard generato da macchine. Era una decisione presa una volta e mai più rivista, e introduceva una superficie di rischio e un costo di performance dove non serviva nulla.

Quell'esperienza è il motivo per cui vale la pena chiarire, una volta per tutte, cosa è JSON5, a cosa serve davvero, e soprattutto dove non va usato. Perché JSON5 è uno strumento utile, ma è uno strumento specifico, e il consiglio "rimpiazzalo dappertutto" è esattamente il modo sbagliato di adottarlo.

TL;DR

  • A cosa serve JSON5: ai file di configurazione scritti a mano (permette commenti, trailing comma, virgolette singole), NON all'interscambio dati e alle API.
  • Errore comune: sostituire ovunque json_decode con json5_decode. Sulle API è più lento e inutile: lì resta JSON puro.
  • In PHP: composer require colinodell/json5, poi json5_decode() (prova prima il parser nativo, poi ricade su JSON5: nessun costo extra se l'input è JSON).
  • Direzione: si legge JSON5 (config), si scrive JSON standard.
  • Input non fidato: meglio il JSON nativo con JSON_THROW_ON_ERROR o json_validate() (PHP 8.3), più rigido.

Cos'è JSON5 e quale problema risolve

JSON5 è un'estensione di JSON pensata per un obiettivo preciso: rendere il formato più facile da scrivere e mantenere a mano per un essere umano. Lo dice la stessa specifica ufficiale di JSON5, che lo definisce un'estensione che prende in prestito alcune comodità sintattiche da ECMAScript 5.1. Le differenze rispetto a JSON sono tutte orientate alla scrittura manuale: puoi inserire commenti, di riga e di blocco, cosa che JSON puro non permette e che è una delle sue mancanze più sentite in un file di configurazione; puoi lasciare una virgola finale (trailing comma) dopo l'ultimo elemento di un array o di un oggetto, senza che il parser si lamenti, il che elimina un'intera classe di errori di battitura; puoi usare le virgolette singole per le stringhe; puoi lasciare le chiavi degli oggetti non quotate se sono identificatori validi; e puoi scrivere i numeri in forme aggiuntive come l'esadecimale, il segno più esplicito, o valori speciali come Infinity e NaN.

{
  // configurazione del servizio di notifica
  driver: 'smtp',
  host: 'mail.example.com',
  port: 587,

  /* i timeout sono in secondi.
     valori piu' alti per le reti lente. */
  timeout: 30,

  retry: {
    attempts: 3,
    backoff: 1.5,   // moltiplicatore esponenziale
  },

  // la virgola finale qui sotto non e' un errore: JSON5 la accetta
  recipients: [
    '[email protected]',
    '[email protected]',
  ],
}

Guardando questo esempio è evidente a cosa serve JSON5: è un formato per i file di configurazione che una persona apre, legge e modifica. I commenti spiegano le scelte, i trailing comma rendono i diff più puliti quando aggiungi una riga, e la sintassi più rilassata riduce gli errori. Tutto questo non ha alcun valore quando i dati sono generati e consumati da macchine, ed è qui che nasce l'equivoco da cui sono partito.

Dove JSON5 NON va usato: l'interscambio dati e le API

Il punto che il consiglio "sostituisci json_decode ovunque" sbaglia è fondamentale: JSON5 non è un JSON migliore per ogni scopo, è un JSON più comodo da scrivere a mano, e queste due cose sono diverse. Per l'interscambio di dati tra sistemi, per i payload delle API REST, per la serializzazione di risposte verso un client, lo standard è e resta JSON puro, per ragioni precise. JSON è uno standard formale, rigoroso, supportato nativamente da ogni linguaggio e da ogni runtime senza dipendenze; la sua rigidità, l'assenza di commenti e di trailing comma, è una caratteristica voluta, non un limite, perché un formato di interscambio deve essere prevedibile e privo di ambiguità. Aggiungere la tolleranza di JSON5 a un canale dato-a-dato significa accettare input più permissivi senza alcun beneficio, perché dall'altra parte c'è una macchina che non ha bisogno di scrivere commenti.

C'è poi una ragione di performance concreta. Il parser JSON nativo di PHP, json_decode(), è implementato in C ed è molto veloce; un parser JSON5 in PHP, per quanto ben fatto, deve gestire una grammatica più ampia ed è inevitabilmente più lento. Far passare per il parser JSON5 dei payload che sono JSON standard, come accadeva nel progetto che ho ereditato, significa pagare un costo su ogni richiesta senza ottenere nulla. La regola pratica che applico è netta: JSON5 per i file di configurazione che gli esseri umani scrivono, JSON puro per tutto ciò che viaggia tra sistemi. Sono due usi separati che non vanno mescolati con un search-and-replace.

Se ti riconosci nella situazione di una codebase ereditata dove scelte come questa sono state prese una volta e mai più riviste, è il tipo di debito tecnico silenzioso su cui intervengo regolarmente: nel mio profilo professionale trovi l'esperienza concreta sulla modernizzazione di applicativi PHP, dove spesso il valore maggiore sta nel rimuovere complessità inutile più che nell'aggiungerne.

Come si integra JSON5 in PHP, fatto bene

Detto dove usarlo, vediamo come. In PHP non esiste un parser JSON5 nativo, quindi serve una libreria, e quella di riferimento è colinodell/json5, un parser JSON5 compatibile UTF-8 installabile via Composer. Si aggiunge al progetto con un comando:

composer require colinodell/json5

La libreria espone json5_decode(), una funzione drop-in che accetta gli stessi identici parametri di json_decode() nativo. Il dettaglio che la rende ben progettata, ed è anche la risposta al problema di performance, è la sua strategia interna: prova prima a interpretare l'input con il parser JSON nativo, velocissimo, e solo se quello fallisce ricade sul parser JSON5. Questo significa che un input che è già JSON valido viene gestito alla massima velocità, e il costo aggiuntivo di JSON5 si paga solo quando serve davvero, cioè quando l'input contiene effettivamente le estensioni JSON5.

declare(strict_types=1);

// Lettura di un file di configurazione scritto a mano, con commenti e trailing comma
$raw = file_get_contents(__DIR__ . '/config/notifiche.json5');
$config = json5_decode($raw, true); // secondo parametro true: array associativo, come json_decode

Una nota importante sull'asimmetria fra lettura e scrittura, perché è l'altra metà di un equivoco diffuso, dato che in giro si parla anche di un json5_encode(). La direzione che ha senso è la lettura: parti da un file JSON5 scritto da una persona e lo trasformi in strutture dati PHP. La direzione opposta, serializzare strutture PHP in JSON5, ha molto meno senso, perché l'output generato da una macchina non ha bisogno di commenti né di trailing comma, e per produrre output si usa il json_encode() nativo che genera JSON standard, leggibile da chiunque. In pratica: leggi configurazioni in JSON5, scrivi dati in JSON. Non esiste un caso d'uso comune in cui un programma debba generare JSON5 invece di JSON.

Sicurezza: il parser permissivo e l'input non fidato

C'è una dimensione che spesso si trascura e che oggi è la prima cosa che valuto: cosa succede se l'input JSON5 non è il tuo. Un file di configurazione che scrivi tu, nel tuo repository, è input fidato, e lì JSON5 è perfettamente sicuro. Ma se per qualche ragione passi a json5_decode() dei dati che provengono dall'esterno, da un utente o da un sistema terzo, allora valgono le stesse cautele di qualsiasi parser, con un'attenzione in più dovuta alla grammatica più ampia. Vanno imposti limiti di profondità per evitare strutture annidate maliziose pensate a esaurire memoria e stack, va validato lo schema dei dati ottenuti prima di usarli, e soprattutto va riconsiderato se JSON5 sia la scelta giusta: per input non fidato proveniente da macchine, il JSON nativo con JSON_THROW_ON_ERROR è più rigido e quindi più difensivo.

Vale la pena ricordare, a questo proposito, che PHP ha aggiunto nella versione 8.3 la funzione nativa json_validate(), che verifica se una stringa è JSON valido senza costruirne la rappresentazione in memoria, utile proprio per validare in modo economico input potenzialmente grandi o non fidati prima di decodificarli. È pensata per il JSON standard, non per JSON5, ed è un altro segnale del fatto che i due formati vivono in contesti diversi: il JSON rigoroso e validabile per i dati che arrivano da fuori, il JSON5 comodo per la configurazione che scrivi tu.

Come si adotta JSON5 in un progetto esistente, senza fare danni

Visto che il problema di partenza era proprio un'adozione fatta male, vale la pena descrivere quella fatta bene, perché è l'opposto del search-and-replace globale. Il primo passo non è tecnico ma di inventario: si distinguono nel progetto i file e i flussi che sono configurazione scritta a mano da quelli che sono dati che viaggiano fra sistemi. I primi sono i candidati a JSON5; i secondi non si toccano e restano su json_decode() nativo. Questa separazione è la decisione che conta, e va presa guardando ogni punto in cui il codice legge JSON, non applicando una sostituzione cieca a tutti.

Fatto l'inventario, si introduce un punto unico di caricamento della configurazione, un piccolo componente che legge il file JSON5, lo decodifica e, soprattutto, valida che la struttura ottenuta sia quella attesa prima di consegnarla al resto dell'applicazione. Questo è il tassello che manca quasi sempre nelle adozioni improvvisate: leggere un file di configurazione senza validarlo significa che un errore di battitura in una chiave o un valore del tipo sbagliato si propaga in profondità e si manifesta come un errore oscuro lontano dalla causa. Validare al momento del caricamento trasforma quell'errore in un messaggio chiaro al primo avvio.

declare(strict_types=1);

final class ConfigLoader
{
    public static function load(string $path): array
    {
        $config = json5_decode(file_get_contents($path), true);

        // validazione minima: i campi obbligatori e i loro tipi
        foreach (['driver', 'host', 'port'] as $required) {
            if (!array_key_exists($required, $config)) {
                throw new \RuntimeException("Config: manca la chiave obbligatoria '$required' in $path");
            }
        }

        return $config;
    }
}

C'è infine una considerazione di team che è facile sottovalutare: introdurre un formato significa introdurre una convenzione che tutti devono conoscere. Se in un progetto i file di configurazione sono in JSON5 ma metà del team non sa che i commenti e i trailing comma sono ammessi, qualcuno passerà tempo a "correggere" file che non sono rotti, o a chiedersi perché un commento non genera un errore. Adottare JSON5 va quindi accompagnato da una riga di documentazione nel progetto che dice "i file .json5 sono configurazione e accettano commenti e trailing comma; i dati e le API restano JSON standard". È una frase, ma è quella che evita la confusione e mantiene netta la separazione fra i due usi che è il cuore di tutto questo discorso.

JSON5 nel panorama dei formati di configurazione del 2026

Per chiudere il cerchio, conviene collocare JSON5 fra le alternative, perché non è l'unica risposta al problema "voglio file di configurazione comodi da scrivere a mano". Se l'unica cosa che ti serve sono i commenti, esiste anche JSONC (JSON with Comments), una variante più minimale usata per esempio dagli strumenti dell'ecosistema dei tool di sviluppo. Se vuoi una sintassi ancora più leggera per l'occhio umano, YAML e TOML sono molto diffusi per la configurazione, ciascuno con i suoi compromessi: YAML è espressivo ma ha insidie note legate all'indentazione e alla conversione automatica dei tipi, TOML è più rigido e prevedibile. Nell'ecosistema Symfony c'è NEON, e in quasi ogni progetto PHP convivono i file .env per i segreti e i valori d'ambiente, che seguono un loro formato.

La scelta fra questi non è una questione di moda, ma di chi scrive e legge il file e con quali esigenze. Per una configurazione strutturata, annidata, scritta da sviluppatori che apprezzano i commenti e la familiarità con la sintassi JSON, JSON5 è una scelta sensata e a basso attrito, proprio perché chi conosce JSON lo legge senza imparare nulla di nuovo. Per i segreti e i valori d'ambiente, i file .env restano lo standard, ed è un tema che ha implicazioni di sicurezza importanti che approfondisco nell'articolo sulla configurazione di applicazioni Symfony e Laravel tra .env, secret e sicurezza. E quando si tratta di portare una vecchia applicazione PHP verso pratiche moderne, la scelta dei formati di configurazione è uno dei tanti tasselli di un percorso più ampio, che descrivo nella guida alla migrazione da PHP 5.6 a PHP 8.

Rendere il proprio codice PHP compatibile con JSON5, nel 2026, non significa sostituire ovunque le funzioni JSON come suggeriva la ricetta del 2018, ma capire che JSON5 è uno strumento per un compito ben definito: i file di configurazione scritti e mantenuti da esseri umani. Per quel compito si integra in modo pulito con una libreria drop-in che non penalizza le performance grazie al fallback sul parser nativo, e aggiunge commenti e trailing comma proprio dove servono. Per tutto il resto, l'interscambio dati e le API, JSON puro resta lo standard, ed è giusto così. Se hai una codebase dove scelte come il search-and-replace globale di anni fa hanno lasciato complessità o costi che nessuno ha più rivisto, contattami per un confronto diretto: spesso la modernizzazione più utile non è adottare l'ultima novità ovunque, ma rimettere ogni strumento al posto giusto.

Ultima modifica: