I'm trying to implement a helper map for objects that looks like this:
import { objectEntries, objectFromEntries } from 'ts-extras';
export const map = <K extends PropertyKey, V>(
obj: Record<K, V>,
fn: (key: K, value: V, index?: number) => [K, V]
) => {
const entries = objectEntries(obj);
const mappedEntries = entries.map(([key, value], index) => fn(key, value, index));
return objectFromEntries(mappedEntries);
};
But I'm getting the following error in argument key of fn call inside entries.map callback.
Argument of type '`${Exclude<K, symbol>}`' is not assignable to parameter of type 'K'.
'`${Exclude<K, symbol>}`' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'PropertyKey'.
Type 'string' is not assignable to type 'K'.
'string' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'PropertyKey'
I would really appreciate any ideas on how to solve this error. Thanks in advance!
CodePudding user response:
The type definition of objectEntries
from ts-extras
was written in a way to closely reflect the behavior of Object.entries()
. It knows that symbol
properties do not appear in the result set and filters them out with Exclude<K, symbol>
. It also knows that all keys returned from the functions are strings, so it wraps the type in a template literal type which converts number
to string
and number literals to string literals.
This results in the type `${Exclude<K, symbol>}`
. While this type is somewhat related to K
, the compiler correctly denies the assignment.
Changing the type of key
to `${Exclude<K, symbol>}`
to reflect the typing of objectEntries
should solve the issue.
export const map = <K extends PropertyKey, V>(
obj: Record<K, V>,
fn: (key: `${Exclude<K, symbol>}`, value: V, index?: number) => [K, V]
) => {
const entries = objectEntries(obj);
const mappedEntries = entries.map(
([key, value], index) => fn(key, value, index)
);
return objectFromEntries(mappedEntries);
};