Search code examples
typescripttypescasting

how to declare types for recursive object which inclues known unknown keys


I want to declare the type for this kind of recursive object where name and link is a mandatory field

{
          1: {
            name: 'Youtube',
            link: '/inventory/youtube',
            1: {
              name: 'home',
              link: '/inventory/youtube/home'
            }
          },
          2: {
            name: 'Network Infrastructure',
            link: '/inventory/network-infrastructure',
          }
} 

I have tried to do like this but getting error (Property 'name' of type 'string' is not assignable to 'string' index type 'propTypes'),

type propTypes = {
    name: string;
    link: string;
    [key: string | number]: propTypes
  }; 

Solution

  • If the unknown keys are any possible string at all, then what you want isn't really possible in TypeScript at the moment.

    An string index signature like {[key: string]: PropTypes} means: if a property with a key of type string exists, then it must have a value of type PropTypes. But that means the link and name properties would also need to be of type PropTypes and not string like you want. That's where the error comes from.

    You might want to say "every string key except "link" and "name" should have a value of type PropTypes. But there's currently no way to express that. There's a longstanding open issue at microsoft/TypeScript#17867 asking for such "hybrid" or "rest" index signatures. Who knows when or if something like that will ever be implemented. For now, there's no specific type in TypeScript that works this way.

    There are various workarounds but they are all somewhat complicated. See How to define Typescript type as a dictionary of strings but with one numeric "id" property for an overview of the different possible approaches there.


    However, in your case it seems that the unknown keys are specifically numeric strings like "1" or "2" and not arbitrary strings. In this case, you could use a number index signature instead:

    type PropTypes = {
        name: string;
        link: string;
        [key: number]: PropTypes // okay
    };
    

    This is all right because "name" and "link" are not numeric strings. It's no longer a contradiction to say that the name and link properties should have string values, while all numeric-like properties should have PropTypes values. And thus your recursive object meets this definition:

    const prop: { [key: number]: PropTypes } = {
        1: {
            name: 'Youtube',
            link: '/inventory/youtube',
            1: {
                name: 'home',
                link: '/inventory/youtube/home'
            },
            2: {
                name: 'explore',
                link: '/inventory/youtube/explore',
                1: {
                    name: 'Indias Factory Customisation',
                    link: '/inventory/youtube/Indias-first-Factory-Customisation'
                },
                2: {
                    name: 'YouTube Mix',
                    link: '/inventory/youtube/YouTube-Mix'
                }
            }
        },
        2: {
            name: 'Network Infrastructure',
            link: '/inventory/network-infrastructure',
        }
    };
    

    Note that prop is not of type PropTypes, because it lacks the name and link properties. But since each property and nested subproperty does have those properties, you can say that prop is of type {[key: number]: PropTypes}.

    Playground link to code