Reputation: 652
I'm currently looping over an object's keys and transferring the values to another object.
interface From {
[key: string]: string;
}
let from: From = {
prop1: "foo",
prop2: "23",
};
interface To {
[key: string]: string | number | boolean | undefined;
prop1: string;
prop2: number;
prop3: boolean;
}
let to: To = {} as To;
const initEnv = async () => {
const keys: string[] = Object.keys(from);
for (let i = 0; i < keys.length; i++) {
let key: string = keys[i];
let keyType = typeof (key as keyof To); // This only returns "string". Which kind of makes sense to me
let keyType = typeof keyof key; // SyntaxError: ',' expected
let keyType: typeof to[key]; // Error: 'key' refers to a value, but is being used as a type
to[key] = from[key];
}
};
I would want to be able to, say switch the value, so I don't want to just extract the type of the key. I want to assign it to a variable for use in the code, thus at runtime, as a string for instance.
So I think things like this wouldn't work.
let keyType2: typeof env[key]; // Error: 'key' refers to a value, but is being used as a type
Maybe, but the question is then; what do I assign to this variable?
The reason for all this, is that I want to convert the from variables
to the correct type before, assigning them to the to
object.
So yeah, basically, my question is how I would extract the type (as a string, at runtime, dynamically) from the key
. Or is it even possible in the first place? And if it isn't why not? I like understanding things.
Thanks for putting up with my bad english, as well.
Upvotes: 0
Views: 2525
Reputation: 328292
There are no interface
s at runtime; TypeScript's type system is erased from the emitted JavaScript. Your from
and to
values will be evaluated like this at runtime:
let from = {
prop1: "foo",
prop2: "23",
};
let to = {};
There's no From
or To
, and no way to use To
to figure out how to coerce from
's properties into the right types. The type system has no runtime effects.
The usefulness of TypeScript's type system comes from describing what will happen at runtime and not from affecting things at runtime. Imagine how you would have to write your code in pure JavaScript, and then give types to that code. Here's one way I might do it. Instead of a To
interface, let's make a To
object whose properties are functions that coerce inputs to other types:
const To = {
prop1: String,
prop2: Number,
prop3: Boolean
}
This is enough information to proceed at runtime.
Now, if you were going to build to
manually, the compiler would be able to understand that the resulting value has a prop1
property of type string
and a prop2
property of type number
:
const toManual = { prop1: To.prop1(from.prop1), prop2: To.prop2(from.prop2) };
/* const toManual: { prop1: string; prop2: number; } */
But you don't want to do it manually; you'd like to write a loop that walks through the keys of from
and uses To
to produce properties of to
. This is harder for the compiler to understand, but with judicious use of type assertions and type annotations you can write an objMap
function that works programmatically:
function objMap<T, F extends { [K in keyof T]: (arg: T[K]) => any }>(
obj: T, fMap: F) {
const ret = {} as { [K in keyof T]: ReturnType<F[K]> };
const fM: { [K in keyof T]: (arg: T[K]) => ReturnType<F[K]> } = fMap;
(Object.keys(obj) as Array<keyof T>).forEach(<K extends keyof T>(k: K) => {
ret[k] = fM[k](obj[k]);
})
return ret;
}
The objMap
function takes an object obj
and a mapping object fMap
which has at least all the same keys as obj
and whose properties are functions that map obj
's properties. The return type of objMap
is an object whose properties are all the returned values of the fMap
function for each property in obj
. The actual work is being done by ret[k] = fM[k](obj[k]);
. It's the programmatic equivalent of ret.prop1 = To.prop1(from.prop1);
and ret.prop2 = To.prop2(from.prop2)
.
Let's see if it works:
const to = objMap(from, To);
/* const to: { prop1: string; prop2: number; } */
console.log(JSON.stringify(to));
// {"prop1":"foo","prop2":23}
That looks correct; the type of to
is inferred by the compiler to be {prop1: string, prop2: number}
, and the actual value of to
is computed at runtime to be {prop1: "foo", prop2: 23}
.
Upvotes: 1