I currently have a function to which I can pass an array of objects, where the objects all have the _id
key. From that object the function creates a lookup object, which has the _id
fields as keys and the corresponding objects as values.
function createLookupById<S extends string, T extends object & { _id: S }>(source: Array<T>) {
return Object.fromEntries(source.map((el) => [el._id, el]));
}
/*
Example input: [
{ _id: "foo", otherKey: "value" }, { _id: "bar", yetAnotherKey: "abc" }
]
Output: {
"foo": { _id: "foo", otherKey: "value" },
"bar": { _id: "bar", yetAnotherKey: "abc" }
}
*/
How can I make this function even more generic, so that I can specify the key which it shall use for creating the lookup (currently fixed to _id
) while still keeping the type checking?
You could add a parameter, keyFn: (v: V) => string
–
function createLookup<V>(
source: Array<V>,
keyFn: (t: V) => string
): { [k: string]: V; } {
return Object.fromEntries(source.map((el) => [keyFn(el), el]));
}
console.log(createLookup(
[
{ _id: "foo", otherKey: "value" },
{ _id: "bar", yetAnotherKey: "abc" },
],
el => el._id,
))
{
"foo": {
"_id": "foo",
"otherKey": "value"
},
"bar": {
"_id": "bar",
"yetAnotherKey": "abc"
}
}
View it on typescript playground.
One limitation you are experiencing is Object.fromEntries
will only create an object where the keys can be string
–
interface ObjectConstructor {
/**
* Returns an object created by key-value entries for properties and methods
* @param entries An iterable object that contains key-value entries for properties and methods.
*/
fromEntries<T = any>(
entries: Iterable<readonly [PropertyKey, T]>
): { [k: string]: T; }; // ⚠️ k: string
...
I might suggest you use a more suitable lookup type, like Map –
function createLookup<K,V>(source: Array<V>, keyFn: (t: V) => K): Map<K,V> {
return new Map(source.map((el) => [keyFn(el), el]))
}
Now the _id
could be a number
or any other type –
const data = [
{ _id: 123, otherKey: "value" },
{ _id: 456, yetAnotherKey: "abc" },
]
const numDict = createLookup(
data,
el => el._id,
)
console.log(numDict)
Map (2) {
123 => {
"_id": 123,
"otherKey": "value"
},
456 => {
"_id": 456,
"yetAnotherKey": "abc"
}
}
console.log(numDict.get(123))
{
"_id": 123,
"otherKey": "value"
}
You could add another generic parameter to specify your lookup key.
function createLookupById<
K extends string,
S extends string,
T extends { [Key in K]: S },
>(key: K, source: T[]) {
return Object.fromEntries(source.map((el) => [el[key], el]));
}
const result1 = createLookupById("_id", [
{ _id: "foo", otherKey: "value" },
{ _id: "bar", yetAnotherKey: "abc" },
]);
const result2 = createLookupById("customId", [
{ customId: "foo", otherKey: "value" },
{ customId: "bar", yetAnotherKey: "abc" },
]);