Esercizio: tipizzare il JSON
Esercizio: tipizzare il JSON
Pensa allo standard dei container per il trasporto navale: qualsiasi cosa tu debba spedire — mobili, elettronica, frutta — deve entrare in uno dei pochi formati standard di container ISO. Non esistono forme personalizzate: o il tuo carico rientra in una di quelle sagome, oppure non puoi spedirlo su quella nave. JSON funziona in modo simile: qualunque dato tu voglia serializzare, deve rientrare in un numero molto ristretto di "forme" ammesse dallo standard.
Mettiamo alla prova quello che abbiamo imparato finora, definendo un tipo che descriva qualsiasi valore JSON valido.
Ecco la sezione rilevante della specifica:
Un valore JSON DEVE essere un
- oggetto
- array
- numero
- stringa,
oppure uno dei seguenti tre nomi letterali:
- false
- true
- nullEcco il tuo punto di partenza:
type JSONObject = any;
type JSONArray = any;
type JSONValue = any;
////// NON MODIFICARE IL CODICE SOTTO QUESTA RIGA //////
function isJSON(arg: JSONValue) {}
// Casi di test POSITIVI (devono passare)
isJSON("ciao");
isJSON([4, 8, 15, 16, 23, 42]);
isJSON({ saluto: "ciao" });
isJSON(false);
isJSON(true);
isJSON(null);
isJSON({ a: { b: [2, 3, "foo"] } });
// Casi di test NEGATIVI (devono fallire)
// @ts-expect-error
isJSON(() => "");
// @ts-expect-error
isJSON(class {});
// @ts-expect-error
isJSON(undefined);
// @ts-expect-error
isJSON(BigInt(143));
// @ts-expect-error
isJSON(isJSON);Prova a definire JSONObject, JSONArray e JSONValue in modo che tutti i casi di test positivi passino, e tutti quelli negativi generino effettivamente un errore di compilazione (nota il commento // @ts-expect-error: dice al compilatore "mi aspetto che la riga seguente fallisca" — se invece la riga non fallisce, sarà @ts-expect-error stesso a generare un errore).
Qualche spunto per ragionarci prima di guardare la soluzione:
- Un valore JSON è quasi sempre definito in termini di altri valori JSON (un array contiene valori JSON, un oggetto ha valori JSON come proprietà). Che tipo di costrutto ti serve per questo?
- Ricorda quello che abbiamo visto nel capitolo precedente sui tipi ricorsivi.
- I "letterali"
true,falseenullnon sono soloboolean: sono tipi letterali specifici. Ma nel nostro caso, dato che vogliamo accettare qualsiasi booleano e qualsiasinull, possiamo semplicemente usarebooleanenull.
Attenzione, spoiler! Clicca per rivelare la soluzione
type type JSONPrimitive = string | number | boolean | nullJSONPrimitive = string | number | boolean | null;
type type JSONObject = {
[k: string]: JSONValue;
}
JSONObject = { [k: stringk: string]: type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue };
type type JSONArray = JSONValue[]JSONArray = type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue[];
type type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue = type JSONArray = JSONValue[]JSONArray | type JSONObject = {
[k: string]: JSONValue;
}
JSONObject | type JSONPrimitive = string | number | boolean | nullJSONPrimitive;
////// NON MODIFICARE IL CODICE SOTTO QUESTA RIGA //////
function function isJSON(arg: JSONValue): voidisJSON(arg: JSONValuearg: type JSONValue = JSONPrimitive | JSONObject | JSONArrayJSONValue) {}
// Casi di test POSITIVI (devono passare)
function isJSON(arg: JSONValue): voidisJSON("ciao");
function isJSON(arg: JSONValue): voidisJSON([4, 8, 15, 16, 23, 42]);
function isJSON(arg: JSONValue): voidisJSON({ saluto: stringsaluto: "ciao" });
function isJSON(arg: JSONValue): voidisJSON(false);
function isJSON(arg: JSONValue): voidisJSON(true);
function isJSON(arg: JSONValue): voidisJSON(null);
function isJSON(arg: JSONValue): voidisJSON({ a: {
b: (string | number)[];
}
a: { b: (string | number)[]b: [2, 3, "foo"] } });
// Casi di test NEGATIVI (devono fallire)
// @ts-expect-error
function isJSON(arg: JSONValue): voidisJSON(() => "");
// @ts-expect-error
function isJSON(arg: JSONValue): voidisJSON(class {});
// @ts-expect-error
function isJSON(arg: JSONValue): voidisJSON(var undefinedundefined);
// @ts-expect-error
function isJSON(arg: JSONValue): voidisJSON(var BigInt: BigIntConstructor
(value: bigint | boolean | number | string) => bigint
BigInt(143));
// @ts-expect-error
function isJSON(arg: JSONValue): voidisJSON(function isJSON(arg: JSONValue): voidisJSON);La chiave qui è la mutua ricorsione tra JSONValue, JSONObject e JSONArray: JSONValue è definito come "o un primitivo, o un array di JSONValue, o un oggetto i cui valori sono JSONValue". TypeScript è perfettamente in grado di gestire questo tipo di riferimento circolare tra type alias, come abbiamo visto nel capitolo precedente.
Nota anche perché i casi negativi falliscono tutti: una funzione, una classe e undefined non compaiono da nessuna parte nella nostra union (non sono valori JSON validi), e BigInt è un tipo numerico che JSON non è in grado di rappresentare nativamente.
