I'm trying to learn TS and I came to a weird case scenario.
I have an object with a property set as non-enumerable:
let person = {
name: 'Harry'
}
Object.defineProperty(person,'salary',{enumerable: false, value : 15});
console.log(person.salary); // 15
This works at runtime, however, the compiler is going to error:
Property 'salary' does not exist on type '{ name: string; }'
which is expected.
I could bypass this setting the type of person as any, but it doesn't feel like a clean solution.
Is there another way to do it? The non-null assertion operator doesn't work for this.
I'd appreciate if anyone can share some knowledge about this.
CodePudding user response:
While you could change the type annotation for person
, maybe like:
let person: { name: string; salary?: number } = {
name: 'Harry'
};
or even:
let person = {
name: 'Harry'
} as { name: string; salary: number };
The safest method is probably (ab)using assertion functions in version 3.7:
function defineProperty<
O,
Property extends PropertyKey,
Value
>(o: O, key: Property, attributes: PropertyDescriptor & ThisType<any> & { value: Value }): asserts o is O & Record<Property, Value> {
Object.defineProperty(o, key, attributes);
}
It's essentially a wrapper function for Object.defineProperty
that tells TypeScript, if this function doesn't throw an error, then the type of the given value o
is O & Record<Property, Value>
.
So when you use it, you get a type like this:
defineProperty(person,'salary',{enumerable: false, value : 15});
person
// ^? { name: string; } & Record<"salary", number>
And if you really want to, you can also make the resulting type cleaner by changing the assertion to this:
asserts o is (O & Record<Property, Value> extends infer T extends O ? { [K in keyof T]: T[K] } : O)
As you can see here when hovering over person
, the type is now { name: string; salary: number; }
instead of an ugly intersection.