Issue
I have a function like this one:
function myFunc(input: A | B | C) {
let key: keyof A | keyof B | keyof C;
for(key in input)
{
let temp = input[key];
console.log(temp);
}
}
with the definitions for A B and C being as follows:
interface A {
u: number,
t: string,
s: boolean,
r: boolean
}
interface B {
w: string,
v: string
}
interface C {
z: string,
y: number,
x: boolean
}
And I tried using the method in this post to create a variable that can be used to loop through the properties of the input object regardless of which interface it is an instance of, however I'm getting the following error:
TS2405: The left-hand side of a 'for...in' statement must be of type 'string' or 'any'.
However if I try to define 'key' like so:
let key:any;
I get a different error:
TS7053: Element implicitly has an 'any' type because expression of type 'any' can't be used to index type 'A | B | C'.
Is there a way to define 'key' such that I can make this just one function, or would I have to split this up into 3 functions, 1 for each interface?
Solution
TypeScript doesn't make it very easy to loop over keys of an object in a way that lets you index with those keys, as describe in How to iterate over keys of a generic object in TypeScript?. Most of the techniques in the answers there work for various use cases, but the one you have here with a union of interfaces won't work, because the keyof
operator when acting on a union gives the intersection of the keys, meaning just the keys that appear in all the union members. This is the safest thing for the compiler to do, but it loses track of the correlation between the object type and its key.
The language can't really handle using union types in an arbitrary way; the general issue is described in microsoft/TypeScript#30581. If you want to perform a single action that works for a union of types, you can refactor to use generics instead (which is part of the solution to ms/TS#30581 described in microsoft/TypeScript#47109):
function myFunc<T extends A | B | C>(input: T) {
let key: keyof T;
for (key in input) {
let temp = input[key]; // okay
// ^? let temp: T[keyof T]
console.log(temp);
}
}
Another approach you can take is to widen your input type from A | B | C
to the type Record<string, any>
or the equivalent {[k: string]: any}
. This type has an index signature meaning that it will allow any key whatsoever, and the property type at that key is given the intentionally loose any
type:
function myFunc(_input: A | B | C) {
const input: { [k: string]: any } = _input;
for (const key in input) {
let temp = input[key]; // okay
// ^? any
console.log(temp);
}
}
Neither way is perfect. Depending on your needs you might want to just give up on a for...in
loop and use the Object.entries()
method instead, which gives you both keys and values at once:
function myFunc(input: A | B | C) {
Object.entries(input).forEach(([key, temp]) => {
temp
// ^? any
console.log(temp);
})
}
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.