Reputation: 2146
The code
const obj = {};
if ('a' in obj) console.log(42);
Is not typescript (no error). I see why that could be. Additionally, in TS 2.8.1 "in" serves as type guard.
But nevertheless, is there an way to check if property exists, but error out if the property is not defined in the interface of obj?
interface Obj{
a: any;
}
I'm not talking about checking for undefined...
Upvotes: 41
Views: 50901
Reputation: 28128
You don't get an error because you use a string to check if the property exists.
You will get the error this way:
interface Obj{
a: any;
}
const obj: Obj = { a: "test" };
if (obj.b) // this is not allowed
if ("b" in obj) // no error because you use string
If you want type checking to work for string properties you could add index signatures using this example
Upvotes: 37
Reputation: 3495
You can implement your own wrapper function around hasOwnProperty that does type narrowing.
function hasOwnProperty<T, K extends PropertyKey>(
obj: T,
prop: K
): obj is T & Record<K, unknown> {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
I found this solution here: TypeScript type narrowing not working when looping
Usage:
const obj = {
a: "what",
b: "ever"
} as { a: string }
obj.b // Type error: Property 'b' does not exist on type '{ a: string; }'
if (hasOwnProperty(obj, "b")) {
// obj is no longer { a: string } but is now
// of type { a: string } & Record<"b", unknown>
console.log(obj.b)
}
The limitation with this approach is that you only get a Record back with the single key added that you specified. This might be fine for some needs, but if you need a more general solution then I suggest a library like Zod which can validate a complex object and give you the full type: https://github.com/colinhacks/zod
Upvotes: 11
Reputation: 719
The following handle
function checks hypothetical server response typesafe-way:
/**
* A type guard. Checks if given object x has the key.
*/
const has = <K extends string>(
key: K,
x: object,
): x is { [key in K]: unknown } => (
key in x
);
function handle(response: unknown) {
if (
typeof response !== 'object'
|| response == null
|| !has('items', response)
|| !has('meta', response)
) {
// TODO: Paste a proper error handling here.
throw new Error('Invalid response!');
}
console.log(response.items);
console.log(response.meta);
}
Playground Link. Function has
should probably be kept in a separate utilities module.
Upvotes: 12