Reputation: 59
What's proper way to access interface/class properties using strings?
I have following interface
interface Type {
nestedProperty: {
a: number
b: number
}
}
I'd like to set nested property using array iteration like that:
let myType:Type = ...
["a", "b"].forEach(attributeName => myType.nestedProperty[attributeName] = 123)
TS complains that "nestedProperty" doesn't have string index type. If I add if typeguard (e.g. if (attributeName === "a"))
compiler is happy but I really don't want to go if (...===... || ...===... || ... ) {
route.
I also don't want to use indexed type:
interface Type<T> {
[index:string]: <T>
a: <T>
b: <T>
}
Since it's not dynamic structure and properties could have different types.
I'm sure there is some elegant way to do it but can't seem to find it anywhere in documentation/Stack Overflow/web.
Should I write custom guard returning union type predicate for that? Something like that?
(attribute: string): attribute is ('a' | 'b') { ... }
Upvotes: 1
Views: 98
Reputation: 9357
I'd go with:
interface Type {
nestedProperty: {[key in ('a' | 'b')]: number}
}
let myType:Type = {
nestedProperty: {
a: 1,
b: 2,
}
};
(["a", "b"] as Array<'a'|'b'>).forEach(attributeName => myType.nestedProperty[attributeName] = 123)
Given the problem, if you don't wanna declare an additional type, this could be a way. But I like the things more explicitly declared, like in the accepted answer.
Upvotes: 0
Reputation: 4602
You have to explicitly tell TypeScript that the array you're using consists only of the properties allowed as keys in the nestedProperty
property.
interface Type {
nestedProperty: {
a: number
b: number
}
}
// Create a type alias for convenience. The type itself
// is a list of keys allowed in the `nestedProperty`.
type NestedAccessors = Array<keyof Type['nestedProperty']>
// Now TS is happy to do anything with the list since it
// can guarantee you're using proper values.
(["a", "b"] as NestedAccessors).forEach(attributeName => myType.nestedProperty[attributeName] = 123)
Upvotes: 2