Search code examples
typescripttypescript-genericstype-constraintsmapped-types

Typescript mapped types - enum value as key, and interface's corresponding value as value


I have a set of preferences, defined as an enum of strings:

export enum PreferenceTypes {
    language = "language",
    unit = "unit",
}

I can then create an interface to define the shape of an expected object. The keys will be the values of the enum:

export type UnitTypes = "µg/m3" | "ppm" | "ppb";
export type LanguageTypes = "English" | "Spanish";

export interface PreferenceOptions {
    [PreferenceTypes.language]: {
        name: string;
        value: LanguageTypes;
    }[];
    [PreferenceTypes.unit]: {
        name: string;
        value: UnitTypes;
    }[];
}

Now I want to create a default preferences object, based on a user locale. I want the keys of this new object to be the local, and I want the values to be objects. Each object should have keys of type PreferenceTypes, and the values must be the type of value that corresponds to that PreferenceType in PreferenceOptions. I am trying to construct such a type constraint, but I am having a hard time:

PreferenceByLocale: {
    [key: string]: { [key in PreferenceTypes]?: string };
} = {
    /** Defaults for UK users */
    en: {
        language: "English",
        unit: "µg/m3",
    },
    /** Defaults for Spain users */
    es: {
        language: "Spanish",
        unit: "µg/m3",
    },
    /** Defaults for US users */
    us: {
        language: "English",
        unit: "ppm",
    },
};

I dont know how to say that the value of each of these objects should really be { [T extends key in PreferenceTypes]?: PreferenceOptions[T]['value'] } - that gives me TS errors. I am not sure if what I'm trying to do is possible, or if I'm overthinking my typing. For example, I should be able to get an error if I wrote something like this:

PreferenceByLocale: {
    [key: string]: { [key in PreferenceTypes]?: string };
} = {
    /** Defaults for mars users */
    mrs: {
        // I want this to error, as "Martian" does not exist on LanguageTypes
        language: "Martian", 
        unit: "µg/m3",
    },
}

Is such a thing possible?


Solution

  • Ok, I think I understand better what you want to do now. I made a revision like this.

    export enum PreferenceTypes {
    language = "language",
    unit = "unit",
    }
    
    export type UnitTypes = "µg/m3" | "ppm" | "ppb";
    export type LanguageTypes = "English" | "Spanish";
    
    export interface PreferenceOptions {
    [PreferenceTypes.language]: LanguageTypes;
    [PreferenceTypes.unit]: UnitTypes;
    }
    
    export interface PreferenceByLocale {
    [key : string]: PreferenceOptions;
    }
    
    const PreferenceByLocale: PreferenceByLocale = {
    /** Defaults for UK users */
    en: {
        language: "English",
        unit: "µg/m3",
    },
    /** Defaults for Spain users */
    es: {
        language: "Spanish",
        unit: "µg/m3",
    },
    /** Defaults for US users */
    us: {
        language: "English",
        unit: "ppm",
    },
    mrs: {
        language: "Unkown",
        unit: "sxsx"
      }
     };
    
     console.log(PreferenceByLocale);
    

    now it gives the following error for mrs:

    enter image description here

    I guess that's what you wanted to do. If that's what you want to do and the code is hard to understand, I can explain.