1

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') { ... }
3
  • I'm not sure whether your Type interface was just a generic example. If it isn't, you can go with interface Type {nestedProperty: {[key: string]: number};} Commented Aug 31, 2019 at 9:12
  • Yeah. That was one of "solutions" but I don't want Type to be dynamic structure where I can put anything there. I want to stick to set of defined properties. I've edited question to be more precise Commented Aug 31, 2019 at 9:14
  • And what about interface Type {nestedProperty: {[key in ('a' | 'b')]: number};}? Commented Aug 31, 2019 at 9:39

2 Answers 2

2

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)
Sign up to request clarification or add additional context in comments.

3 Comments

keyof! Haven't seen that one in documentation yet :) Looks really clean. Thank you very much. TypeScripting for few days so far. Totally in love. Now even more :)
As a follow up: Is there a way to turn NestedAccessor to array so I could just go with NestedAccessorsSomeHowTurnedIntoArray.forEach( accessor => ... )?
You can't access interfaces in the run-time, after the app is compiled to JavaScript. The best you can do is to access properties of the existing object that implements the Type interface. const arr = Object.keys(myType.nestedProperty) as NestedAccessors will get the key values and then you can do arr.forEach(...).
0

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.

1 Comment

Thanks for showing some extra "type fun" :) Also casting during access looks okay-ish: myType.nestedProperty[attributeName as keyof Type['nestedProperty']] = ...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.