How do I make sure that the specified keyof
a type extends an array?
For example I have this type:
type TestType = {
arrayKey: Array<number>,
notArrayKey: number
}
And I have this function that will access the specified key of the type:
const getKey = <T>(object: T, key: keyof T): Array<any> => {
return object[key]
}
How do I make sure that the only valid input of key
are arrayKey
as it extends Array
and not "notArrayKey
" which does not extends Array
?
CodePudding user response:
Behemoth's answer will provide good IntelliSense suggestions for the key if you choose to use the parameter order of (object, key)
.
More simply, if you reverse the parameters, you can use generic constraints to ensure that the object
argument conforms to a type having an array value at the key
argument provided:
function getValueInObj <
Key extends string,
Obj extends Record<Key, readonly any[]>,
>(key: Key, object: Obj): Obj[Key] {
return object[key];
}
const obj = {
arrayKey: [1, 2, 3],
nonArrayKey: '123',
};
getValueInObj('arrayKey', obj); // OK
getValueInObj('nonArrayKey', obj); /*
~~~
Argument of type '{ arrayKey: number[]; nonArrayKey: string; }' is not assignable to parameter of type 'Record<"nonArrayKey", readonly any[]>'.
Types of property 'nonArrayKey' are incompatible.
Type 'string' is not assignable to type 'readonly any[]'.(2345) */
Expanding on the other answer:
The type mapping can be performed in the generic constraint in order to provide a type parameter name to use in the return type.
In the example below, when the obj
value is provided as the first argument to the function, the generic type Obj
becomes the type of that object, and the type parameter Key
becomes a union of all of its properties which have value types that extend readonly any[]
(which means an immutable array with elements of any type). This union ends up looking like 'arrayKey' | 'anotherArrayKey'
, so the value provided to key
has to be one of those strings, or a compiler error diagnostic will be emitted.
function getValueInObj <
Obj extends Record<PropertyKey, any>,
Key extends keyof {
[
K in keyof Obj as Obj[K] extends readonly any[]
? K
: never
]: unknown; // The "unknown" type used here as the value doesn't matter
}, // because it goes unused: it could be any type
>(object: Obj, key: Key): Obj[Key] {
return object[key];
}
const obj = {
arrayKey: [1, 2, 3],
anotherArrayKey: ['a', 'b', 'c'],
nonArrayKey: '123',
};
getValueInObj(obj, 'arrayKey'); // number[]
getValueInObj(obj, 'nonArrayKey'); /*
~~~~~~~~~~~~~
Argument of type '"nonArrayKey"' is not assignable to parameter of type '"arrayKey" | "anotherArrayKey"'.(2345) */
CodePudding user response:
You could filter the keys of TestType
by their value with a Mapped Type. Basically this checks whether the value of a key extends Array<number>
(T[K] extends Array<number>
). An example for both keys of TestType
:
- Does
TestType.arrayKey
extendArray<number>
? --> Yes, valid key - Does
TestType.notArrayKey
extendArray<number>
? --> No, not a valid key because it has the typenumber
type TestType = {
arrayKey: Array<number>;
notArrayKey: number;
};
const test = {
arrayKey: [0],
notArrayKey: 0
};
const getKey = <T extends object>(
object: T,
key: {
[K in keyof T]: T[K] extends Array<number> ? K : never;
}[keyof T]
): T[keyof T] => {
return object[key];
};
getKey<TestType>(test, "arrayKey"); // works
getKey<TestType>(test, "notArrayKey");
// ~~~~~~~~~~~~ --> Argument of type
// '"notArrayKey"' is not assignable to parameter of type '"arrayKey"'
I'm not sure though how to define the return type of function as Array<number>
. Probably it's not possible because it cannot be guaranteed that T
actually has a property of type Array<number>
. But I'd love to be proved wrong!