Reputation: 3494
I have an object that is of type MyObject
, and it has two string properties.
interface MyObject {
a: number,
b: string,
}
const myObject = {
a: 5,
b: 'str'
}
Then I have a function that takes a string, and I want to be able to access the property on the aforementioned object specified by the string parameter. What I am trying to do is use a type guard to check if the string is a key of the object, before I access the property. It is necessary to make some kind of check here because the parameter is just a string and the object doesn't have an index signature.
If I make a specific version to check for this specific type of object (MyObject) it works:
// specific version
const isValidPropertyForMyObject = (property: string): property is keyof MyObject => Object.keys(myObject).indexOf(property) !== -1
const getProperty1 = (property: string) => {
if (isValidPropertyForMyObject(property)) {
myObject[property]
}
}
However, what if I want to be able to pass in an object with a generic type, and a string parameter, and check that the property is in fact a key of the object? Here is my attempt:
const isValidMethodForHandler = <T extends { [i: string]: any }>(handler: T) => (
method: string
): method is keyof T => Object.keys(handler).indexOf(method) !== -1;
const getProperty = (property: string) => {
// const acceptedProperties = ["a", "b"];
// if (acceptedProperties.indexOf(property) !== -1) {
// myObject[property]
// }
if (isValidMethodForHandler(myObject)(property)) {
myObject[property]
}
}
The issue is in the type guard:
A type predicate's type must be assignable to its parameter's type. Type 'keyof T' is not assignable to type 'string'. Type 'string | number | symbol' is not assignable to type 'string'. Type 'number' is not assignable to type 'string'.(2677)
Upvotes: 3
Views: 521
Reputation: 3494
The answer is based on this thread in TypeScript issues tracker here.
An explanation of the specific TypeScript error in the question above is in this other question
The solution for my example code is:
const isValidMethodForHandler = <T extends { [i: string]: any }>(handler: T) => (
method: string
): method is Extract<keyof T, string> => Object.keys(handler).indexOf(method) !== -1;
const getProperty = (property: string) => {
if (isValidMethodForHandler(myObject)(property)) {
myObject[property]
}
keyof
returns all known keys, and these are of type string | number | symbol
.
To get only the string properties, use Extract
.
Upvotes: 2
Reputation: 44416
You could certainly write:
interface MyObject {
a: number,
b: string,
}
const myObject = {
a: 5,
b: 'str'
}
const isValidPropertyForMyObject = (property: string): property is keyof MyObject =>
property in myObject;
Then you could do things like:
const f = <K extends keyof MyObject>(obj: MyObject, k: K): MyObject[K] => obj[k];
const g = <K extends keyof MyObject>(obj: MyObject, k: string | K): MyObject[K] | string =>
isValidPropertyForMyObject(k) ? f(obj, k) : "NOT LEGAL";
That assumes there is a constant myObject
that at runtime lists all the keys of MyObject
.
Upvotes: 0