Search code examples
typescriptmapped-types

TypeScript mapped types: from list of both objects and strings to a key-value object


I'm trying to write a mapped type that takes a list which can contain both objects ("definitions") and strings. String values are limited to a set of strings that refer to existing hard coded definitions. The resulting type should be an object where the keys are collected from that input list;

  • for list elements of type string, use that string
  • for list elements of type object, use the name property from that object

Here is my current code:



type CustomDefinition = {
    name: string;
    units: string | null;
};

const predefinitions = {
    'AUTOPILOT_AIRSPEED_ACQUISITION': {
        name: 'AUTOPILOT_AIRSPEED_ACQUISITION',
        units: 'Bool',
        settable: false,
    },
    'AUTOPILOT_AIRSPEED_HOLD': {
        name: 'AUTOPILOT_AIRSPEED_HOLD',
        units: 'Bool',
        settable: false,
    },
    'AUTOPILOT_AIRSPEED_HOLD_CURRENT': {
        name: 'AUTOPILOT_AIRSPEED_HOLD_CURRENT',
        units: 'Bool',
        settable: false,
    },
} as const;

type PredefinitionName = keyof typeof predefinitions;

type PredefinedValuesResult<SimVars extends PredefinitionName[]> = {
    [VarName in SimVars[number]]: string;
};

type CustomValuesResult<SimVars extends CustomDefinition[]> = {
    [VarName in SimVars[number]['name']]: string;
};

function getPredefinedValue<ReqValues extends PredefinitionName[]>(requestedValues: ReqValues): PredefinedValuesResult<ReqValues> {
    return {} as PredefinedValuesResult<ReqValues>; 
}

function getCustomValues<ReqValues extends CustomDefinition[]>(requestedValues: ReqValues): CustomValuesResult<ReqValues> {
    return {} as CustomValuesResult<ReqValues>; 
}

const predfinedValuesResult = getPredefinedValue([
    'AUTOPILOT_AIRSPEED_ACQUISITION', 
    'AUTOPILOT_AIRSPEED_HOLD_CURRENT',
]);

const customValues = getCustomValues([
    { name: 'CUSTOM_VARIABLE', units: 'degrees' }
]);

console.log(
    predfinedValuesResult.AUTOPILOT_AIRSPEED_ACQUISITION, 
    predfinedValuesResult.AUTOPILOT_AIRSPEED_HOLD_CURRENT, 
);

console.log(
    customValues.CUSTOM_VARIABLE, 
);

// Un-implemented functionality:

const customAndPredefinedValues = getCustomAndPredefinedValues([
    'AUTOPILOT_AIRSPEED_ACQUISITION',
    { name: 'CUSTOM_VARIABLE', units: 'degrees' }
]);

console.log(
    customAndPredefinedValues.AUTOPILOT_AIRSPEED_ACQUISITION, // should autocomplete
    customAndPredefinedValues.CUSTOM_VARIABLE, // should autocomplete
)

Using PredefinedValuesResult and CustomValuesResult alone works fine, but I want to allow the user to specify a list of both custom definitions and referring strings.

Would this be possible?


Solution

  • You could use a conditional type to examine the input array elements and use them directly if they are a key of predefinitions, or to use their name property if they are a CustomDefinition. For example:

    type Predefinitions = typeof predefinitions;
    
    declare function getCustomAndPredefinedValues<
        const T extends CustomDefinition | keyof Predefinitions>(
            t: T[]
        ): { [K in (
            T extends keyof Predefinitions ? T :
            T extends CustomDefinition ? T["name"] :
            never
        )]: string };
    

    Now your function behaves as desired:

    const customAndPredefinedValues = getCustomAndPredefinedValues([
        'AUTOPILOT_AIRSPEED_ACQUISITION',
        { name: 'CUSTOM_VARIABLE', units: 'degrees' }
    ]);
    /* const customAndPredefinedValues: {
        AUTOPILOT_AIRSPEED_ACQUISITION: string;
        CUSTOM_VARIABLE: string;
    } */
    

    Playground link to code