typescripttypesindex-signature

How to type object with index signature and dynamic variable in TypeScript?


I am new to TypeScript, and have tried various ways to type this, but running into problems with index signatures. What should the interface look like?

interface MyConfig {
...
}

// someVar can be any string
let someVar = "dynamicKey";

// the structure of the object cannot change
const config: MyConfig = {
  myObj: {
    [someVar]: {
      firstProp: 'some text',
      secondProp: 'some text',
    },
    thirdProp: 'some text',
  },
};



Solution

  • If you know the exact value or values for someVar variable, you can have a strict interface like:

    interface MyConfig {
        myObj: {
            SomeKey: {
                firstProp: string
                secondProp: string
            },
            thirdProp: string
        }
    }
    

    Then you can use:

    const someVar = "SomeKey";
    
    const config: MyConfig = {
        myObj: {
            [someVar]: {
                firstProp: 'some text',
                secondProp: 'some text',
            },
            thirdProp: 'some text',
        },
    };
    

    But if you wish that someVar would be dynamic, it's a bit tricky. For that I would recommend you moving the dynamic part to a separate block, so you could use:

    interface MyConfig {
        myObj: {
            dynamic: {
                [key: string]: {
                    firstProp: string
                    secondProp: string
                }
            },
            thirdProp: string
        }
    }
    
    const someVar = "SomeKey";
    const config: MyConfig = {
        myObj: {
            dynamic: {
                [someVar]: {
                    firstProp: 'some text',
                    secondProp: 'some text',
                },
            },
            thirdProp: 'some text',
        },
    };
    
    

    And finally, if you have dynamic someVar and can't change the data structure. you can use following:

    interface MyConfig {
        myObj: ({
            [key: string]: {
                firstProp: string
                secondProp: string
            } | string
        } & {
            thirdProp: string
        })
    }
    const someVar: string = "SomeKey";
    
    const config: MyConfig = {
        myObj: {
            [someVar]: {
                firstProp: 'some text',
                secondProp: 'some text',
            },
            thirdProp: 'some text',
        },
    };
    
    // String
    console.log(config.myObj.thirdProp.trim())
    
    // Error, union string | object
    console.log(config.myObj.abc.firstProp)
    
    if (typeof config.myObj.abc === 'object') {
        // string
        console.log(config.myObj.thirdProp.trim())
        // string
        console.log(config.myObj.abc.firstProp.trim())
    }
    

    In this example, we use a typescript index signature + we specify known properties. You can also notice a strange thing - the index signature has union object | string. This is because of typescript limitation:

    As soon as you have a string index signature, all explicit members must also conform to that index signature. This is to provide safety so that any string access gives the same result.

    Reference: How to combine declared interface properties with custom Index Signature