duffpl
duffpl

Reputation: 59

Accessing property using string

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

Answers (2)

julianobrasil
julianobrasil

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

Mateusz Kocz
Mateusz Kocz

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

Related Questions