skip to Main Content

This is part of my TypeScript code.

enum FirstEnum {
    firstEnumKey1 = 'firstEnumKey1',
    firstEnumKey2 = 'firstEnumKey2',
    firstEnumKey3 = 'firstEnumKey3',
    firstEnumKey4 = 'firstEnumKey4',
}

enum SecondEnum {
    secondEnumKey1 = 'secondEnumKey1',
    secondEnumKey2 = 'secondEnumKey2',
    secondEnumKey3 = 'secondEnumKey3',
}

// map first enum to second enum
const map: {[i in FirstEnum]: SecondEnum[] } = {
    'firstEnumKey1': [SecondEnum.secondEnumKey1],
    'firstEnumKey2': [],
    'firstEnumKey3': [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3],
    'firstEnumKey4': [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3],
};

I have two enums and a const which map them. I’m using the map to define some data that for each SecondEnum has an object. The object keys should be map keys which had that specific SecondEnum value in their array.
So now my result should be like this.

const getSecondEnumValuesBasedOnMap = {
    [SecondEnum.secondEnumKey1]: {
        [FirstEnum.firstEnumKey1]: 'some value',
    },
    [SecondEnum.secondEnumKey2]: {
        [FirstEnum.firstEnumKey3]: 'some value',
        [FirstEnum.firstEnumKey4]: 'some value',
    },
    [SecondEnum.secondEnumKey3]: {
        [FirstEnum.firstEnumKey3]: 'some value',
        [FirstEnum.firstEnumKey4]: 'some value',
    },
}

If I change my map my data should change based on it. for example if I add SecondEnum.secondEnumKey2 to FirstEnum.firstEnumKey2 my data should change like this:

// map first enum to second enum
const map: {[i in FirstEnum]: SecondEnum[] } = {
    'firstEnumKey1': [SecondEnum.secondEnumKey1],
    'firstEnumKey2': [SecondEnum.secondEnumKey2],
    'firstEnumKey3': [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3],
    'firstEnumKey4': [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3],
};

const getSecondEnumValuesBasedOnMap = {
    [SecondEnum.secondEnumKey1]: {
        [FirstEnum.firstEnumKey1]: 'some value',
    },
    [SecondEnum.secondEnumKey2]: {
        [FirstEnum.firstEnumKey2]: 'some value',
        [FirstEnum.firstEnumKey3]: 'some value',
        [FirstEnum.firstEnumKey4]: 'some value',
    },
    [SecondEnum.secondEnumKey3]: {
        [FirstEnum.firstEnumKey3]: 'some value',
        [FirstEnum.firstEnumKey4]: 'some value',
    },
}

I want to declare a type for my result so when I changed my map I get errors to update my result but I’m kind of new to typescript and I don’t know how.

3

Answers


  1. declare type like this:

    type GetSecondEnumValuesBasedOnMap = {
    [K in SecondEnum]: Partial<{ [L in FirstEnum]: string }>
    

    }

    then use like this:

    const getSecondEnumValuesBasedOnMap: GetSecondEnumValuesBasedOnMap = { [SecondEnum.secondEnumKey1]: { [FirstEnum.firstEnumKey1]: 'some value', }

    Login or Signup to reply.
  2. I think it would be better to declare the map as a type rather than a constant (assuming you do not also need to use it in your calculations).
    If you do that and also invert the keys and values to make the mapping easier, you can declare all the types like this:

    Playground Link

    type EnumTypeMap = {
      [SecondEnum.secondEnumKey1]: FirstEnum.firstEnumKey1;
      [SecondEnum.secondEnumKey2]: FirstEnum.firstEnumKey3 | FirstEnum.firstEnumKey4;
      [SecondEnum.secondEnumKey3]: FirstEnum.firstEnumKey3 | FirstEnum.firstEnumKey4;
    };
    
    type ValueEnumTypeMap = {
      [key in keyof EnumTypeMap]: {
        // Unfortunately I wasn't sure how else to set the type of the index as `EnumTypeMap[key]`
        // (loops through a single key and then changes the type to the want we need)
        [innerKey in keyof { key: key } as EnumTypeMap[key]]: string;
      };
    };
    
    const getSecondEnumValuesBasedOnMap: ValueEnumTypeMap = {
      [SecondEnum.secondEnumKey1]: {
        [FirstEnum.firstEnumKey1]: "some value",
      },
      [SecondEnum.secondEnumKey2]: {
        [FirstEnum.firstEnumKey2]: "some value", // Error
        [FirstEnum.firstEnumKey3]: "some value",
        [FirstEnum.firstEnumKey4]: "some value",
      },
      [SecondEnum.secondEnumKey3]: {
        [FirstEnum.firstEnumKey3]: "some value",
        [FirstEnum.firstEnumKey4]: "some value",
      },
    };
    
    Login or Signup to reply.
  3. First of all, we need to change the type of the map, since it is currently not having literal values for the SecondEnum and we won’t be able to determine which exact values are used.

    To prevent the compiler from widening the types we will use const assertion and to maintain type safety we will use satisfies operator:

    const map = {
      firstEnumKey1: [SecondEnum.secondEnumKey1],
      firstEnumKey2: [SecondEnum.secondEnumKey2],
      firstEnumKey3: [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3],
      firstEnumKey4: [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3],
    } as const satisfies {[K in FirstEnum]: readonly SecondEnum[]};
    
    // type EnumMap = {
    //   readonly firstEnumKey1: readonly [SecondEnum.secondEnumKey1];
    //   readonly firstEnumKey2: readonly [SecondEnum.secondEnumKey2];
    //   readonly firstEnumKey3: readonly [SecondEnum.secondEnumKey2, SecondEnum.secondEnumKey3];
    //   readonly firstEnumKey4: readonly [...];
    // }
    type EnumMap = typeof map;
    

    Next, let’s define a type that accepts a generic parameter constrained by SecondEnum and find the members of FirstEnum that have this specific item in the map. We are going to use mapped types to map through the map and key remapping to exclude those that are not suitable, and at the end return the keys of the map that have this second enum in them:

    type FindValue<T extends SecondEnum> = keyof  {
      [K in keyof EnumMap as T extends  EnumMap[K][number] ? K : never]:EnumMap[K]
    }
    

    Let’s define a type for the getSecondEnumValuesBasedOnMap. We will need to use mapped types and key remapping again to exclude those second enum members that are not used at all:

    {
      [K in SecondEnum as [FindValue<K>] extends [never] ? never : K]: {
        [P in FindValue<K>]: string
      }
    }
    

    Note that [] are really important in the [FindValue<K>] extends [never], since never is an empty set and is a supertype for every union and to make sure that this check works as expected we prevent the compiler from distributing the types using the [].
    And in the values, we are mapping through the result of FindValue<K> which returns as the keys where the second enum is used and assigns a value of type string:

    const getSecondEnumValuesBasedOnMap:{
      [K in SecondEnum as [FindValue<K>] extends [never] ? never : K]: {
        [P in FindValue<K>]: string
      }
    } = {
      [SecondEnum.secondEnumKey1]: {
        [FirstEnum.firstEnumKey1]: 'some value',
      },
      [SecondEnum.secondEnumKey2]: {
        [FirstEnum.firstEnumKey2]: 'some value',
        [FirstEnum.firstEnumKey3]: 'some value',
        [FirstEnum.firstEnumKey4]: 'some value',
      },
      [SecondEnum.secondEnumKey3]: {
        [FirstEnum.firstEnumKey3]: 'some value',
        [FirstEnum.firstEnumKey4]: 'some value',
      },
    };
    

    playground

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search