Poiché il tipo è sconosciuto in fase di esecuzione, ho scritto il codice come segue per confrontare l'oggetto sconosciuto, non con un tipo, ma con un oggetto di tipo noto:
- Crea un oggetto campione del tipo giusto
- Specifica quali dei suoi elementi sono facoltativi
- Esegui un confronto approfondito del tuo oggetto sconosciuto con questo oggetto campione
Ecco il codice (interfaccia-agnostico) che uso per il confronto approfondito:
function assertTypeT<T>(loaded: any, wanted: T, optional?: Set<string>): T {
// this is called recursively to compare each element
function assertType(found: any, wanted: any, keyNames?: string): void {
if (typeof wanted !== typeof found) {
throw new Error(`assertType expected ${typeof wanted} but found ${typeof found}`);
}
switch (typeof wanted) {
case "boolean":
case "number":
case "string":
return; // primitive value type -- done checking
case "object":
break; // more to check
case "undefined":
case "symbol":
case "function":
default:
throw new Error(`assertType does not support ${typeof wanted}`);
}
if (Array.isArray(wanted)) {
if (!Array.isArray(found)) {
throw new Error(`assertType expected an array but found ${found}`);
}
if (wanted.length === 1) {
// assume we want a homogenous array with all elements the same type
for (const element of found) {
assertType(element, wanted[0]);
}
} else {
// assume we want a tuple
if (found.length !== wanted.length) {
throw new Error(
`assertType expected tuple length ${wanted.length} found ${found.length}`);
}
for (let i = 0; i < wanted.length; ++i) {
assertType(found[i], wanted[i]);
}
}
return;
}
for (const key in wanted) {
const expectedKey = keyNames ? keyNames + "." + key : key;
if (typeof found[key] === 'undefined') {
if (!optional || !optional.has(expectedKey)) {
throw new Error(`assertType expected key ${expectedKey}`);
}
} else {
assertType(found[key], wanted[key], expectedKey);
}
}
}
assertType(loaded, wanted);
return loaded as T;
}
Di seguito è riportato un esempio di come lo uso.
In questo esempio mi aspetto che JSON contenga una matrice di tuple, di cui il secondo elemento è un'istanza di un'interfaccia chiamata User
(che ha due elementi opzionali).
Il controllo del tipo di TypeScript assicurerà che il mio oggetto campione sia corretto, quindi la funzione assertTypeT verifica che l'oggetto sconosciuto (caricato da JSON) corrisponda all'oggetto campione.
export function loadUsers(): Map<number, User> {
const found = require("./users.json");
const sample: [number, User] = [
49942,
{
"name": "ChrisW",
"email": "example@example.com",
"gravatarHash": "75bfdecf63c3495489123fe9c0b833e1",
"profile": {
"location": "Normandy",
"aboutMe": "I wrote this!\n\nFurther details are to be supplied ..."
},
"favourites": []
}
];
const optional: Set<string> = new Set<string>(["profile.aboutMe", "profile.location"]);
const loaded: [number, User][] = assertTypeT(found, [sample], optional);
return new Map<number, User>(loaded);
}
È possibile richiamare un controllo come questo nell'implementazione di una protezione del tipo definita dall'utente.