jepek
jepek

Reputation: 420

How to type accessing deep object properties - getting object keys dynamically

I would like to get proper typing for accessing object properties based on provided key names. I want to get keys of object one level below.

I have following object from which I want to access some data:

const source = {
  sth: {
    EXAMPLE: 'this is my example'
  },
  another: {
    TEST: 'this is my test value'
  }
};

Accessing function:

function getMessage(context : keyof typeof source, msgKey: string) : string {

    if(msgKey in source[context]) {
      return source[context][msgKey]
    }
}

By keyof typeof source I'm getting first-level keys - works like a charm.

How to get lower level keys? With msgKey: string of course I'm getting error:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ EXAMPLE: string; } | { TEST: string; }'. No index signature with a parameter of type 'string' was found on type '{ EXAMPLE: string; } | { TEST: string; }'

Of course, by getMessage('sth', 'EXAMPLE') I want to get 'this is my example'

Upvotes: 0

Views: 220

Answers (1)

Matei Radu
Matei Radu

Reputation: 2078

According to the compiler hint

No index signature with a parameter of type 'string' was found on type '{ EXAMPLE: string; } | { TEST: string; }'

You need to specify the index signature for the properties of source:

interface Source {
  [key: string]: {  // First level
    [key: string]: string; // Second level
  }
}

const source: Source = {
  sth: {
    EXAMPLE: 'this is my example'
  },
  another: {
    TEST: 'this is my test value'
  }
};

Now you don't even need to write keyof typeof source for the first level as it's already implied from the Source interface:

function getMessage(context: string, msgKey: string): string {
  if(msgKey in source[context]) {
    return source[context][msgKey]
  }
}

More levels deeper

From my understanding, there is no way to specify index signatures for any dynamic level of nesting, so you have to specify them explicitly for each level. However, you could make things a bit easier with generics:

type Indexed<T> = {
  [key: string]: T;
}

const source: Indexed<Indexed<string>> = {
  sth: {
    EXAMPLE: 'this is my example'
  },
  another: {
    TEST: 'this is my test value'
  }
};

Not the most elegant thing to read when your object has three or more levels of nesting but it's an option:

const source: Indexed<Indexed<Indexed<Indexed<string>>>> = {};

Upvotes: 1

Related Questions