Reputation: 17355
Is there a better method than the following for looping through the keys of an object and return a new object with the same key but the values transformed and ensuring the resulting type contains that same keys as the input.
const env = {
KEY_1: "VALUE_1",
KEY_2: "ANOTHER_VALUE_2"
};
function mapValuesToLength<TEnv extends Record<string, string>>(
env: TEnv
): Record<keyof TEnv, number> {
const result: Partial<Record<keyof TEnv, number>> = {};
for (const envKey in env) {
result[envKey] = env[envKey].length;
}
return result as Record<keyof TEnv, number>;
}
// expecting result = {KEY_1: 7, KEY_2: 15}
const result = mapValuesToLength(env);
// expecting type resultType = { KEY_1: number; KEY_2: number; }
type resultType = typeof result
Upvotes: 3
Views: 1990
Reputation: 6049
I find it no problem with your imperative implementation of converting one object to another object. It's simple to read and understand what's for loop is doing.
We can use array.reduce()
functional approach for this implementation as well. But first, we need to convert the object into an array then we need to reduce the obtained array to our required object of values number type. I don't like my web page to do extra computation which will lead to slow performance (not much but slower than simply for in
).
I'd like to modify your code in terms of types.
const env = {
KEY_1: "VALUE_1",
KEY_2: "ANOTHER_VALUE_2",
};
// I don't like to put types inline which are too long.
type InputString<InputType> = Record<keyof InputType, string>;
type OutputNumber<InputType> = Record<keyof InputType, number>;
// no need to give a return type as typescript will correctly infer from the implementation.
function mapValuesToLength<TEnv extends InputString<TEnv>>(env: TEnv) {
// type the result here using `as`
const result = {} as OutputNumber<TEnv>;
for (let envKey in env) {
result[envKey] = env[envKey].length;
}
return result;
}
// expecting result = {KEY_1: 7, KEY_2: 15}
const result = mapValuesToLength(env);
// expecting type resultType = { KEY_1: number; KEY_2: number; }
type resultType = typeof result;
Upvotes: 0
Reputation: 3425
How about the following approach (single line):
const env = {
KEY_1: "VALUE_1",
KEY_2: "ANOTHER_VALUE_2"
};
type InputType = Record<string, string>;
type OutputType<T> = Record<keyof T, number>;
function mapValuesToLength<TEnv extends InputType>(env: TEnv): OutputType<TEnv> {
return Object
.entries(env)
.reduce((acc, [key, value]) => ({ ...acc, [key]: value.length }), {} as OutputType<TEnv>);
}
const result = mapValuesToLength(env);
console.log(result);
Steps:
Object.entries()
: returns an array of a given object's own enumerable string-keyed property [key, value] pairs.Array.prototype.reduce()
: executes a reducer function on each element of the array.
({ ...acc, [key]: value.length })
: returns the current accumulator (result object) with the new element (current) key: value
(length).{} as OutputType<TEnv>
: initilialize the accumulator as an empty object having the correct output type.Upvotes: 1