During the migration of my project to TypeScript, I encountered a challenge with a simple utility function:
function mapObject(obj, mapperFn) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => mapperFn(key, value))
);
}
This function is designed to transform keys and values of an object, as demonstrated below:
let obj = { a: 1, b: 2 };
let mappedObj = mapObject(obj, (key, value) => [
key.toUpperCase(),
value + 1,
]);
mappedObj; // { A: 2, B: 3 }
Edit To clarify, the output object may not have identical values to the input object. For example, the transformation could result in the output object having strings as values instead of numbers: { A: '2', B: '3' }
, or it could simply return null for each entry: { A: null, B: null }
. Only string keys are considered in both input and output objects for simplicity and consistency.
I would like the `mapperFn` function to preserve the type of values in object entries, allowing for type narrowing based on keys, similar to how `GetEntryOf` behaves in the following example:
type GetEntryOf<T> = {
[K in keyof T]: [K, T[K]];
}[keyof T];
interface TestI {
str: string;
num: number;
}
let entry: GetEntryOf<TestI>;
if (entry[0] === 'str') {
entry[1]; // string
} else if (entry[0] === 'num') {
entry[1]; // number
} else {
entry; // never
}
My current implementation using the `key in obj
` operator still struggles with correctly narrowing the type of `value` inside the `mapperFn` function:
function mapObject<K>(
obj: K,
mapperFn: (key: keyof K, value: K[typeof key]) => [string, unknown]
): Record<string, unknown> {
const newObj = {} as Record<string, unknown>;
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const value = obj[key];
const [newKey, newValue] = mapperFn(key, value);
newObj[newKey] = newValue;
}
}
return newObj;
}
interface TestI {
str: string;
num: number;
}
let test: TestI;
mapObject(test, (key, value) => {
if (key === 'str') {
return [key, value]; // value should be string
} else if (key === 'num') {
return [key, value]; // value should be number
} else {
return [key, value]; // value should be never
}
});
The issue lies in the incorrect type narrowing of `value`, which contrasts with the behavior of `GetEntryOf`. Is there a way to achieve the same type narrowing within the `mapperFn` function in TypeScript, similar to how `GetEntryOf` operates?
Note: The concept of type narrowing discussed here is inspired by a solution on Stack Overflow. While it introduces constraints, such as potentially having additional properties, it serves our purpose effectively.