I want to check whether an object implement all the properties of a given interface. Because the interface in TS are only compile-time constructs, I need to have an list of all the properties to check against at runtime. I used this answer : https://stackoverflow.com/a/60932900/10061195 to create the following code:
interface Test {
a: number,
b: number,
}
type CheckMissing<T extends readonly any[], U extends Record<string, any>> = {
[K in keyof U]: K extends T[number] ? never : K
}[keyof U] extends never ? T : T & "Error: missing keys"
type CheckDuplicate<T extends readonly any[]> = {
[P1 in keyof T]: "_flag_" extends
{ [P2 in keyof T]: P2 extends P1 ? never :
T[P2] extends T[P1] ? "_flag_" : never }[keyof T] ?
[T[P1], "Error: duplicate"] : T[P1]
}
// throw an error if the given object does not have all the properties of the given interface
function checkInterface<I, S extends (keyof I)[]>(val: any, keys: S & CheckMissing<S, I> & CheckDuplicate<S>): val is I {
const missing = keys.filter(k => val[k] === undefined);
if (missing.length > 0) {
throw new Error(`Missing properties: ${missing}`);
}
return true;
}
let val: unknown = { a: 5, b: 7 };
// ensure that the object val is of type Test
if (checkInterface<Test, ["a", "b"]>(val, ["a", "b"])) {
console.log(val.a);
}
It does exactly what I want except for the fact that I need to specify the property list twice (once as a template parameter and once as an argument). Is there a way to only provide the list once ?
I tried removing the list in parameters but it seems like it's not possible to retrieve the list from the template parameter afterward. And I cannot enforce the CheckDuplicate
and CheckMissing
types if I remove the template parameter.