Fuglsetrampen
Fuglsetrampen

Reputation: 111

How can I define an interface for a object/array containing key/value pairs as well as nested objects?

I'm typing up a localization-library, and I'm aiming to have it strongly typed (as it will be re-used in multiple angular-applications) and - at the same time - backwards compatible so that we won't have to re-write all existing localization-files.

However, the structure of said localization-files is causing me a bit of a headache. Per example:

{
  'any-random-key': 'This is a string',
  'another-random-key': {
    'random-key': 'This is a string'
  },
  'yet-another-random-key': {
    'random-key-2': 'This is a string',
    'random-key-3': {
      'random-key-4': 'This is a string',
      'random-key-5': {
        'random-key-6': 'This is a string'
      }
    }
  },
  'and-yet-another-random-key': {
    'random-key-6': {
      'random-key-7': {
        'random-key-8': 'This is a string'
      },
      'random-key-9': 'This is a string'
    }
  }
}

Now - I suppose I could say that the service accepts translations: any or translations: object - but that's a little too random (no pun intended) for my liking.

So I tried using two different interfaces:

export interface ITranslation {
  [s: string]: string;
}

export interface ITranslations {
  [s: string]: ITranslation;
}

However that fails on any-random-key saying: Type 'string' is not assignable to type 'ITranslation'

So I tweak my ITranslations-interface so that it becomes

export interface ITranslations {
  [s: string]: ITranslation | string;
}

Which fixes the above error, but introduces a new one on 'and-yet-another-random-key' saying Property ''and-yet-another-random-key'' is incompatible with index signature.

At this point I am a little stumped. Is what I am trying to achieve (strong typing of the legacy structure) simply not plausible?

Upvotes: 0

Views: 1152

Answers (2)

Terry
Terry

Reputation: 66228

For any arbitrary level of nesting (in other words, your data object can be as many levels deep as you want), you can simply self-reference the interface as such:

/** @interface */
export interface ITranslations {
  [s: string]: ITranslations | string;
}

See the above example on TypeScript playground.


If you want to only allow 3-level deep nesting, then the interface will have to be verbose: TypeScript does not allow you to define how "deep" (i.e. the degree of nesting):

/** @interface */
export interface ITranslations<T = string> {
  [s: string]: T | string;
}

/** @type */
export type ITranslationsMax3Levels = ITranslations<ITranslations<ITranslations<ITranslations>>>;

const data: ITranslationsMax3Levels = { ... }

See the above example on TypeScript playground.

Upvotes: 2

Fuglsetrampen
Fuglsetrampen

Reputation: 111

Seems the solution was easier than I dared hope for:

export interface ITranslations {
  [s: string]: ITranslations | ITranslation | string;
}

Upvotes: 1

Related Questions