Reputation: 1092
We have following TypeScript code:
type Option = {
name: string;
};
export const filterOptions = <T extends string>(
options: Record<T, Option>,
): unknown[] =>
Object.entries(options)
.map(([key, entry]) => ({
key,
...entry, // <--- Error: Spread types may only be created from object types.
}))
.filter(() => {
// do filtering stuff...
});
The expected type of entry
is Option
, but TS doesn't recognise this and assumes it is unknown
. The issue seems to be the generic type T
, because changing the type of options
to Record<string, Option>
changes the type of entry
to Option
(as expected).
Here is a TS Playground Link
What are we getting wrong? Why is the type not recognised properly?
Upvotes: 3
Views: 2486
Reputation: 2734
I just checked the Object.entries()
typing:
/**
* Returns an array of key/values of the enumerable properties of an object
* @param o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
*/
entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][];
It appears, you can pass the value argument type to Object.entries()
explicitly. This way it regonizes the typings
const filterOptions = <T extends string>(
options: Record<T, Option>,
) => {
return Object.entries<Option>(options) // added option type here!
.map(([key, entry]) => ({
key,
...entry, // It knows for sure now..
}))
.filter(() => {
// do filtering stuff...
});
}
The Record
typings make use of symbols
/ numbers
and strings
as keys.
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
For example, this does work:
const d = Symbol('somedescripton')
const a: Record<string, Option> = {
'a': {name: 'strrst'},
b: {name:'sdfsdf'},
0: {name: 'srfsdfds'},
d: {name: 'sdfsdfd'}
}
The Object.entries()
will convert it to string
keys but it accepts still, Symbols
and numbers
aswell!!^
So for having a Record
type with only string
keys you would have it to type yourself for being able to omit the explicit casting:
type StringRecord<T extends string, K> = {[key in keyof T]: K }
const filterOptions = <T extends string>(
options: StringRecord<T, Option>,
) => {
return Object.entries(options)
.map(([key, entry]) => ({
key,
...entry, // works now..
}))
.filter(() => {
// do filtering stuff...
});
}
Upvotes: 2