I'm probably missing something obvious here, but I've been stuck on this one for a while.
Given the following predicate (I know it's not technically a predicate):
const hasProp = <K extends PropertyKey, T extends {}> (obj: T, prop: K): obj is T & Record<K, unknown> => {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
I want to do something like this:
const coreProps = ['id', 'module', 'name', 'auth'] as const;
for (const prop of coreProps) {
if (!hasProp(rawObject, prop)) {
throw new Error(`Missing property '${prop}'`);
}
}
However, this doesn't let me use e.g. rawObject.id afterwards. If I manually unroll the loop, it works as expected.
Am I doing something wrong, or is this a limitation of the TypeScript compiler?
I did come up with this alternative:
const hasProps = <Ks extends PropertyKey[], T extends {}> (obj: T, ...props: Ks): obj is T & { [K in Ks[number]]: unknown } => {
return props.every(prop => hasProp(obj, prop));
}
But I don't like this, because it doesn't return any information about which property is missing (if any).
I believe you should use assert function for this purpose:
const hasProp = <K extends PropertyKey, T>(obj: T, prop: K): obj is T & Record<K, unknown> => {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
const rawObject: any = {}
const coreProps = ['id', 'module', 'name', 'auth'] as const;
type Props = typeof coreProps;
function assert(value: unknown): asserts value is Record<Props[number], unknown> {
for (const prop of coreProps) {
if (!hasProp(rawObject, prop)) {
throw new Error(`Missing property '${prop}'`);
}
}
}
assert(arg);// <---- assert function
arg.id // ok
Playground
Assertion function examples
TS official docs
Hence, in order to make it work, you should wrap your loop into assert function. In this way, TS is able to infer the type of your object, otherwise it wont be able to do it because of mutable nature of objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With