typescript
techniques
type-definitions
common-problems

Dealing With Property Not Matching Index Signature

There is an interesting case in TypeScript when defining type definitions for objects. Imagine you have a large object where you know some of the properties are 100% static and always available. It may have the following shape:

type Translations = {
  common: {
    appName: string;
    header: string;
    footer: string;
  };
  // There will be other properties...
};

const obj: Translations = {
  common: {
    appName: `4markdown`,
    header: `Some text`,
    footer: `Some text`,
  },
  // There will be other properties...
};

How do you define a type definition for such an object? BTW, in this article, we will fix the common error message prompted by TypeScript: "Property is not matching index signature." Let's dive into the topic!

Understanding Index Signature Error

Your first idea might be to use the following syntax:

type TranslationNodeValue = Record<string, string>;

type Translations = {
  common: {
    appName: string;
    header: string;
    footer: string;
  };
  home: "Home page";
} & TranslationNodeValue;

const obj: Translations = {
  common: {
    appName: `4markdown`,
    header: `Some text`,
    footer: `Some text`,
  },
  home: "Home page",
};

However, this will produce a common error that frustrates many newcomers to TypeScript.

Property Is Not Matching Index Error Property Is Not Matching Index Signature Error

In the Translations type, you have a property called common, which is an nested object. At the top, you've defined the TranslationNodeValue type, which is a simple object with string values. By creating the Translations type through an intersection (using the & operator) of these types, you're essentially saying: "Combine the shared parts of the two types to create a new type."

Intersection On Diagram Intersection Displayed On Diagram

But, in our case, there is nothing common between the common property type and TranslationNodeValue type. So, this is how it looks (take a look at the empty space between) - this is our intersection result - an empty set.

Empty Set Visualized Empty Set On Diagram

So, now we know that the two types we want to merge into one with the intersection operator don't have anything in common. The TypeScript error is a little bit more developer-friendly and less mathematical - it just says that the common property is not compatible with Record<string, string>, and that's true.

Fixing Index Signature Error

To ensure a smooth developer experience when working with such objects, we need to modify the type definition for the TranslationNodeValue type. It must include the properties we already know, such as those defined for the common property, as well as any new ones we want to include.

type TranslationNodeValue = Record<string, string | Record<string, string>>;

That's all there is to it! This change will work nicely from now on. Take a look at the gif below:

Type Hints Behavior After Fix

Here is the final showcase:

type TranslationNodeValue = Record<string, string | Record<string, string>>;

type Translations = {
  common: {
    appName: string;
    header: string;
    footer: string;
  };
  home: string;
  other: string;
} & TranslationNodeValue;

const obj: Translations = {
  common: {
    appName: `4markdown.com`,
    header: `Some text`,
    footer: `Some text`,
  },
  home: "Home page",
  other: "Test",
  otherOther: {
    other: "test",
  },
};

So, what exactly did we do? We added a shared part for both types. Now, the TranslationNodeValue type includes the shape of the common property type - an object specified as Record<string, string>. Thanks to that, there is now a shared part between them.

It's really important to understand that after adding new properties with different values that are known from the start, such as someOtherProp: number, you will need to add number to the type definition you're intersecting with. Whenever a new property is added to the base object (the properties you know), you need to include the type values of each in the wider type (TranslationNodeValue).

Adding New Properties Adding New Properties And Updating Types

Summary

Now you know how to create fancy type definitions for complex object structures thanks to the intersection operator. While it's used in rare cases, it can dramatically improve type definitions across your project. Consider using this technique if you know some of the object properties, but others may have unknown keys and the same repetitive value signature.

Author avatar
About Authorpraca_praca

👋 Hi there! My name is Adrian, and I've been programming for almost 7 years 💻. I love TDD, monorepo, AI, design patterns, architectural patterns, and all aspects related to creating modern and scalable solutions 🧠.