Vai al contenuto
Toova
Tutti gli strumenti

7 Trucchi JSON Che Ti Faranno Risparmiare Ore

Toova

Ogni sviluppatore JavaScript usa JSON.parse e JSON.stringify decine di volte alla settimana. Ma la maggior parte si ferma alle basi — analizzare le risposte API e serializzare gli oggetti in stringhe. L'API ha molto di più da offrire, e alcune delle funzionalità meno usate risolvono problemi che altrimenti richiederebbero librerie di terze parti o ore di debugging.

Questa guida copre sette trucchi che vanno oltre le impostazioni predefinite. Ognuno è immediatamente applicabile a codebase reali. Nessuna astrazione, nessun esempio inventato — questi sono pattern che si incontrano nei sistemi in produzione.

Puoi verificare ed esplorare tutti gli esempi di codice di questo articolo usando il JSON Formatter di Toova, che valida e formatta il JSON interamente nel tuo browser.

1. Clone Profondo con Round-Trip JSON (e i Suoi Limiti)

Il trucco più antico del manuale JavaScript: usa JSON.parse(JSON.stringify(obj)) per creare un clone profondo di un oggetto semplice.

const original = { a: 1, b: { c: 2 } };

// Approccio superficiale — gli oggetti annidati sono ancora condivisi
const shallowCopy = { ...original }; // b punta ancora allo stesso oggetto

// Clone profondo JSON — crea una copia completamente indipendente
const deepClone = JSON.parse(JSON.stringify(original));

deepClone.b.c = 99;
console.log(original.b.c); // 2 — originale intatto

Funziona perché serializzare in stringa e poi rianalizzare crea un albero di oggetti completamente nuovo senza riferimenti condivisi. È veloce, non richiede dipendenze, ed è disponibile da ES5.

L'alternativa moderna per l'uso in memoria è structuredClone():

// Alternativa moderna: structuredClone() — gestisce più tipi
// Supportato in Node.js 17+ e tutti i browser evergreen
const clone = structuredClone(original);

Conosci i limiti dell'approccio JSON prima di fare affidamento su di esso:

// JSON.parse/stringify NON gestisce:
const broken = {
  date: new Date(),      // diventa una stringa — perde il prototipo Date
  fn: () => 'hello',    // eliminato silenziosamente
  undef: undefined,     // eliminato silenziosamente
  inf: Infinity,        // diventa null
  map: new Map(),       // diventa {}
  cycle: null,          // i riferimenti circolari generano errore
};

Se il tuo oggetto contiene solo dati semplici — stringhe, numeri, booleani, array e oggetti semplici annidati — il round-trip JSON è sicuro e veloce. Per tutto il resto, preferisci structuredClone() o una libreria dedicata.

2. Replacer Personalizzato per Filtraggio e Mascheratura

La maggior parte degli sviluppatori sa che JSON.stringify accetta un secondo argomento, ma lo usa raramente. Quel secondo argomento è il replacer: un array di chiavi da includere, oppure una funzione che controlla esattamente come ogni valore viene serializzato.

Replacer come array — whitelist di chiavi specifiche:

const user = {
  id: 'u_001',
  name: 'Alice',
  password: 'hunter2',        // non deve apparire nei log
  creditCard: '4111111111111111', // non deve apparire nei log
  role: 'admin',
};

// Replacer come array: includi solo queste chiavi
JSON.stringify(user, ['id', 'name', 'role']);
// '{"id":"u_001","name":"Alice","role":"admin"}'

Replacer come funzione — trasforma o oscura i valori:

// Replacer come funzione: controllo completo su chiave/valore
const masked = JSON.stringify(user, (key, value) => {
  if (key === 'password' || key === 'creditCard') return '[REDACTED]';
  if (key === '' ) return value; // oggetto radice — restituisci sempre
  return value;
});
// '{"id":"u_001","name":"Alice","password":"[REDACTED]","creditCard":"[REDACTED]","role":"admin"}'

Una versione più sofisticata maschera i valori in base alla loro forma piuttosto che al nome della chiave:

// Replacer per mascheratura basata sul tipo
const sanitize = (key, value) => {
  if (typeof value === 'string' && value.match(/^4[0-9]{15}$/)) {
    return '****-****-****-' + value.slice(-4);
  }
  return value;
};

Questa tecnica è indispensabile per il middleware di logging: vuoi log strutturati con il contesto completo dell'oggetto, ma certi campi non devono mai raggiungere un aggregatore di log. Il replacer ti permette di gestire questo al confine della serializzazione invece di disperdere la logica di oscuramento in tutto il codebase.

3. Ordinare le Chiavi in Modo Deterministico

L'ordine delle chiavi degli oggetti in JavaScript è l'ordine di inserimento (per le chiavi stringa). Due oggetti con le stesse chiavi ma creati in ordine diverso producono stringhe JSON diverse, il che rompe i controlli di uguaglianza naïve, le chiavi di cache e gli hash di contenuto.

function sortedStringify(obj) {
  return JSON.stringify(obj, Object.keys(obj).sort());
}

const a = { z: 1, a: 2, m: 3 };
const b = { a: 2, m: 3, z: 1 };

sortedStringify(a) === sortedStringify(b); // true — ordine delle chiavi normalizzato

Per gli oggetti profondamente annidati, applica l'ordinamento in modo ricorsivo:

// Ordinamento ricorsivo delle chiavi per oggetti annidati
function sortKeys(value) {
  if (Array.isArray(value)) return value.map(sortKeys);
  if (value !== null && typeof value === 'object') {
    return Object.fromEntries(
      Object.keys(value).sort().map((k) => [k, sortKeys(value[k])])
    );
  }
  return value;
}

const sorted = JSON.stringify(sortKeys(deepNested));

Il JSON ordinato è essenziale quando:

  • Generi chiavi di cache dai body delle richieste
  • Calcoli checksum o firme su payload JSON
  • Confronti risposte API nei test indipendentemente dall'ordine dei campi
  • Memorizzi oggetti di configurazione dove l'ordine non dovrebbe influire sull'uguaglianza

Dopo l'ordinamento e la formattazione, usa lo strumento Text Diff per confrontare due stringhe JSON normalizzate e vedere esattamente quali valori sono cambiati tra le versioni.

4. Gestire BigInt Senza Perdere Precisione

Il tipo Number di JavaScript può rappresentare in modo sicuro interi fino a 253 − 1. Per ID generati da sistemi distribuiti, importi finanziari in unità minori o timestamp in nanosecondi, questo non è sufficiente. BigInt copre interi di precisione arbitraria, ma JSON.stringify non sa come gestirli.

const data = {
  amount: 9007199254740993n, // maggiore di Number.MAX_SAFE_INTEGER
};

// Questo genera: TypeError: Do not know how to serialize a BigInt
JSON.stringify(data); // ERRORE

Le soluzioni alternative standard:

// Soluzione 1: Converti in stringa con un replacer
JSON.stringify(data, (key, value) =>
  typeof value === 'bigint' ? value.toString() : value
);
// '{"amount":"9007199254740993"}'

// Soluzione 2: toJSON() sul prototipo BigInt (monkey-patch — usa con cautela)
BigInt.prototype.toJSON = function () { return this.toString(); };
JSON.stringify(data); // '{"amount":"9007199254740993"}'

Sul lato del parsing, una funzione reviver può ripristinare i valori BigInt dalla loro rappresentazione stringa:

// Reviver per ripristinare BigInt durante il parsing
const revived = JSON.parse('{"amount":"9007199254740993"}', (key, value) => {
  if (key === 'amount') return BigInt(value);
  return value;
});
console.log(typeof revived.amount); // 'bigint'

Mantieni la conversione BigInt-in-stringa in un layer di serializzazione condiviso in modo che venga applicata in modo coerente. Lasciare che i BigInt fuoriescano nelle chiamate ad hoc a JSON.stringify ai margini di un codebase porta a errori imprevedibili difficili da tracciare.

5. Rilevamento dei Riferimenti Circolari

Un riferimento circolare si verifica quando un oggetto contiene un riferimento a se stesso o a un antenato nel grafo degli oggetti. Sono più comuni di quanto pensi: event emitter, nodi DOM, nodi fiber di React ed entità ORM hanno frequentemente back-reference.

// Esempio di riferimento circolare
const obj = { name: 'node' };
obj.self = obj; // obj fa riferimento a se stesso

JSON.stringify(obj); // genera: TypeError: Converting circular structure to JSON

Gestiscilo con un replacer personalizzato che tiene traccia degli oggetti visitati:

// Gestore manuale dei riferimenti circolari
function safeStringify(obj) {
  const seen = new WeakSet();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) return '[Circular]';
      seen.add(value);
    }
    return value;
  });
}

safeStringify(obj); // '{"name":"node","self":"[Circular]"}'

WeakSet è la struttura dati giusta qui: mantiene riferimenti agli oggetti senza impedire il garbage collection, e la ricerca è O(1). Questo pattern funziona anche nel middleware di logging dove vuoi che gli errori degradino in modo elegante invece di generare eccezioni durante la serializzazione.

Una variante comune: invece di contrassegnare i riferimenti circolari come [Circular], sostituiscili con una stringa di percorso come [Circular: $.config.parent] per rendere la posizione del riferimento esplicita durante il debugging.

6. Pretty-Print degli Array su una Sola Riga

Il terzo argomento di JSON.stringify è l'indentazione. Passare 2 o 4 espande tutto su più righe, il che è ottimo per gli oggetti ma può essere prolisso per array di primitivi come tag, ID o coordinate.

const mixed = {
  title: 'Report',
  tags: ['json', 'api', 'debug'],
  config: { indent: 2, sortKeys: true },
  count: 42,
};

// Default: tutto su più righe
JSON.stringify(mixed, null, 2);
// {
//   "title": "Report",
//   "tags": [
//     "json",
//     "api",
//     "debug"
//   ],
//   "config": {
//     "indent": 2,
//     "sortKeys": true
//   },
//   "count": 42
// }

Puoi post-elaborare l'output per comprimere gli array semplici su una sola riga:

// Replacer personalizzato per array su una sola riga
function prettyMixed(obj) {
  const raw = JSON.stringify(obj, null, 2);
  // Comprimi gli array che contengono solo primitivi su una sola riga
  return raw.replace(
    /\[\n\s+([\s\S]*?)\n\s+\]/g,
    (match, inner) => {
      const items = inner.split(',\n').map((s) => s.trim());
      if (items.every((s) => !/^[{\[]/.test(s))) {
        return '[' + items.join(', ') + ']';
      }
      return match;
    }
  );
}

Il risultato: gli oggetti rimangono su più righe per la leggibilità, gli array di primitivi si comprimono su una riga per la compattezza. Questo è il formato usato in molti file di configurazione e output di log strutturati dove sia gli umani che le macchine devono leggere gli stessi dati. Il JSON Formatter e il convertitore JSON in YAML gestiscono automaticamente questa compressione.

7. Streaming di JSON di Grandi Dimensioni

Caricare un file JSON di grandi dimensioni interamente in memoria prima del parsing è il singolo errore di prestazioni più comune nelle pipeline di elaborazione JSON. Una risposta JSON da 100 MB alloca almeno 100 MB di heap per la stringa raw, poi una seconda allocazione per l'oggetto analizzato. Per file sopra qualche megabyte, un parser in streaming elabora i dati in modo incrementale.

Su Node.js con una libreria di streaming:

// node --experimental-vm-modules (o usa una libreria JSON in streaming)
// Streaming nativo con la JSON Source Map proposal (Stage 3, 2026):

// Per ora, usa una libreria come @streamparser/json o jsonstream2
import { createReadStream } from 'fs';
import parser from '@streamparser/json';

const jsonParser = new parser.JSONParser();
jsonParser.onValue = ({ value, key, parent }) => {
  if (key === 'id') {
    console.log('ID trovato:', value);
  }
};

createReadStream('large.json').pipe(jsonParser);

Nel browser usando la Fetch Streams API:

// Streaming lato browser con Fetch + JSON stream decoder:
const response = await fetch('/api/large-data');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  // Elabora gli oggetti JSON completi dal buffer...
}

Lo streaming è più utile nelle pipeline di elaborazione batch, nei grandi endpoint di esportazione e negli script di analisi dei log. Per le tipiche risposte API sotto 1 MB, il JSON.parse standard è abbastanza veloce e molto più semplice. La soglia in cui lo streaming comincia a pagare in pratica è intorno a 5–10 MB, a seconda della complessità della struttura analizzata e della dimensione dell'heap nell'ambiente di destinazione.

Per esplorare la struttura di grandi risposte JSON prima di costruire un parser in streaming, incolla un campione nel JSON Formatter per vedere quali percorsi contengono i dati di cui hai bisogno, poi converti in CSV o YAML per ulteriori analisi.

Mettere Tutto Insieme

Queste sette tecniche coprono gli angoli più impattanti dell'API JSON:

  • Clone profondo via round-trip — veloce per dati semplici, conosci i limiti
  • Replacer — filtra le chiavi e maschera i valori sensibili al confine della serializzazione
  • Ordinamento delle chiavi — output deterministico per chiavi di cache, firme e asserzioni nei test
  • Gestione BigInt — serializza in stringa, ripristina durante il parsing
  • Rilevamento dei riferimenti circolari — replacer basato su WeakSet per serializzazione sicura
  • Pretty-print con array compatti — output leggibile senza spazi bianchi eccessivi
  • Streaming di JSON di grandi dimensioni — parsing incrementale per file e risposte API grandi

Per la documentazione completa degli argomenti replacer e space di JSON.stringify, vedi il riferimento MDN JSON.stringify. Per l'alternativa moderna al clone profondo, vedi la documentazione di structuredClone(), che copre tutti i tipi supportati e i casi limite.