typescripttypedeftype-definition

How to define general interface for Object with constans?


In project I have a few Objects with string constants. For example:

export const Elements: Constants {
    DESCRIPTION: "DescriptionAutoField2",
    FORMULA_CALC: "FormulaCalcAutoField1",
    CHART_CODE: "ChartCodeAutoField1",
    CHART_CODE_DESCRIPTION: "ChartDescriptionAutoField1",
};

export const ChartAttr: Constants = {
    CHART: "Chart",
    CHART_DESCRIPTION: "ChartDescr",
};

export const SliceLink: Constants = {
    SLICE_CODE: "SliceCode",
    SLICE_DESC: "SliceDesc",
    SLICE_CALC: "SliceCalc",
};

I defined the interface like this but in this case imposible to check Object kyes during compilation.

 export interface Constants {
       [key: string]: string;
 }

My question is: how to define general interface "Constants" and have strict compilation based on defined Object properties? Is it posible to avoid "enum"?


Solution

  • There's no single concrete type like Constants that will match all of Elements, ChartAttr, and SliceLink, while at the same time remembering the exact keys and values of each. Instead, you can represent a generic type which matches your constraint and use a helper function to ensure that the values conform to it while outputting a strongly typed value that remembers individual key/value types:

    // helper function
    const asConstants = <S extends string, T extends Record<keyof T, S>>(
        c: T
    ): { readonly [K in keyof T]: T[K] } => c;
    

    I assumed you wanted the constant properties to be marked as readonly and that the type of each value should be a string literal and not just string.

    Now you define your constants via the function instead of an annotation:

    export const Elements = asConstants({
        DESCRIPTION: "DescriptionAutoField2",
        FORMULA_CALC: "FormulaCalcAutoField1",
        CHART_CODE: "ChartCodeAutoField1",
        CHART_CODE_DESCRIPTION: "ChartDescriptionAutoField1",
    });
    
    export const ChartAttr = asConstants({
        CHART: "Chart",
        CHART_DESCRIPTION: "ChartDescr",
    });
    
    export const SliceLink = asConstants({
        SLICE_CODE: "SliceCode",
        SLICE_DESC: "SliceDesc",
        SLICE_CALC: "SliceCalc",
    });
    

    If you inspect the types of Elements, ChartAttr, and SliceLink with your IDE's IntelliSense, you see:

    /*
    const Elements: {
        readonly DESCRIPTION: "DescriptionAutoField2";
        readonly FORMULA_CALC: "FormulaCalcAutoField1";
        readonly CHART_CODE: "ChartCodeAutoField1";
        readonly CHART_CODE_DESCRIPTION: "ChartDescriptionAutoField1";
    }
    
    const ChartAttr: {
        readonly CHART: "Chart";
        readonly CHART_DESCRIPTION: "ChartDescr";
    }
    
    const SliceLink: {
        readonly SLICE_CODE: "SliceCode";
        readonly SLICE_DESC: "SliceDesc";
        readonly SLICE_CALC: "SliceCalc";
    }
    */
    

    And if you try to pass a bad value to asConstants() you get an error:

    asConstants({
        NOT_A_STRING: 1, // error! number not a string        
    })
    

    Okay, hope that helps. Good luck!

    Link to code