Search code examples
typescriptkeyoftypescript-partial

Extract actual object keys from typed Partial object, instead of all possible keys


I am trying to work out how to extract a list of keys from an object, while at the same time limiting those keys so they belong to a certain superset of values.

I can either get the list of keys, OR limit the values to an allowed list, but I cannot work out how to do both at the same time.

For example:

// If I don't specify the type of the keys, then I can extract them easily.
const workdayAttributesAuto = {
    Monday: 123,
    Tuesday: 456,
    Floofday: -1,  // whoops, I can specify an invalid day though
};

// Now I can extract them to another type just fine.  Here only Monday
// and Tuesday are included, but I also get the unwanted invalid value.
export type workdayNamesAuto = keyof typeof workdayAttributesAuto;
// --> type workdayNamesAuto = 'Monday' | 'Tuesday' | 'Floofday';

So to avoid the invalid value, I can specify a type to restrict keys to only a list of allowed values. But in doing so, I can no longer extract the actual keys used, and I end up with all permitted keys instead:

type Days = 'Sunday' | 'Monday' | 'Tuesday'; // only these keys are allowed

const workdayAttributes: Partial<Record<Days, number>> = {
    // Sunday is intentionally omitted.
    Monday: 123,
    Tuesday: 456,
    //Floofday: 0,  // not allowed to specify invalid days, good
};

// But now the same type includes Sunday, when it wasn't one of the object keys.
export type workdayNames = keyof typeof workdayAttributes;
// --> type workdayNames2 = 'Sunday' | 'Monday' | 'Tuesday';

How can I tell keyof typeof to only get the keys actually specified in the object, instead of it getting all possible allowed values?


Solution

  • Use the new satisfies operator, new in TS 4.9:

    type Days = 'Sunday' | 'Monday' | 'Tuesday'; // only these keys are allowed
    
    const workdayAttributes = {
        // Sunday is intentionally omitted.
        Monday: 123,
        Tuesday: 456,
        // Floofday: 0,  // not allowed to specify invalid days
    } satisfies Partial<Record<Days, number>>;
    
    export type workdayNames = keyof typeof workdayAttributes;
    // --> type workdayNames = 'Monday' | 'Tuesday';