Search code examples
typescriptobjecttypeskey-pairindex-signature

Typescript: add typing for response object containing both index signature and key-pairs


I am unsure of the best way to add typescript typings for this response object I am receiving from a backend service:

{
    de49e137f2423457985ec6794536cd3c: {
        productId: 'de49e137f2423457985ec6794536cd3c',
        title: 'item 1',
    },
    d6623c1a2b843840b14c32685c212395: {
        productId: 'd6623c1a2b843840b14c32685c212395',
        title: 'item 2',
    },
    ids: [
        'de49e137f2423457985ec6794536cd3c',
        'd6623c1a2b843840b14c32685c212395',
    ],
}

It contains an array of item ids string[] as well as the index signature [id: string]: Item.

Typescript doesn't seem to like having the index signature as well as the array in a single interface. For example:

interface ItemList {
    [id: string]: Item;
    ids: string[];
}

I know that when using an index signature the other properties need to return the same type. I am new to Typescript and I'm a bit unsure as to how to work with this data without moving the ids out of the item object?

interface ItemList {
    [id: string]: Item;
    ids: string[];
}
interface Item {
    productId: string;
    title: string;
}

const item: ItemList = {
    de49e137f2423457985ec6794536cd3c: {
        productId: 'de49e137f2423457985ec6794536cd3c',
        title: 'item 1',
    },
    d6623c1a2b843840b14c32685c212395: {
        productId: 'd6623c1a2b843840b14c32685c212395',
        title: 'item 2',
    },
    ids: [
        'de49e137f2423457985ec6794536cd3c',
        'd6623c1a2b843840b14c32685c212395',
    ],
};
console.log(item.ids.map((id: string) => item[id]));

Error

Property 'map' does not exist on type 'Item | string[]'.

Property 'map' does not exist on type 'Item'.


Solution

  • The simple fix here is to use an intersection type:

    type ItemList = {
        [id: string]: Item;
    } & {
        ids: string[];
    }
    interface Item {
        productId: string;
        title: string;
    }
    
    const item: ItemList = Object.assign({ // Can't build the object directly 
        de49e137f2423457985ec6794536cd3c: {
            productId: 'de49e137f2423457985ec6794536cd3c',
            title: 'item 1',
        },
        d6623c1a2b843840b14c32685c212395: {
            productId: 'd6623c1a2b843840b14c32685c212395',
            title: 'item 2',
        }
    }, {
        ids: [
            'de49e137f2423457985ec6794536cd3c',
            'd6623c1a2b843840b14c32685c212395',
        ],
    });
    console.log(item.ids.map((id: string) => item[id]));
    

    Intersection type allow the inconsistent, named property - index combination. (Note this is not strictly typesafe as item['ids'] does not return Item as expected, but it seems like a decent trade-off for this case)