Can a string literal type and type "string" be mixed and used as key in a mapped type?

Viewed 129

I'd like to define a mapped type where the keys 'foo' and 'bar' are of type number and all other keys are of type string

My first intuition was to solve this with a conditional type like so:

type FooOrBar =  'foo' | 'bar';

type MyMappedType = {
        [key in FooOrBar | string]: key extends FooOrBar ? number : string;
}

var x: MyMappedType = {
    foo:  1,   // Type 'number' is not assignable to type 'string'.(2322)
    bar:  2,   // Type 'number' is not assignable to type 'string'.(2322)
    baz: 'I am a string'   // ok!
}

Example 1

The problem here is that typescript narrows the typealias FooOrBar | string down to type string in the key. So in fact typescript evaluates this type to

type MyMappedType = {
        [key in string]: key extends FooOrBar ? number : string;
}

so the condition key extends FooOrBar is always false and the type-definition is useless for my need.

My second intuition was to use an intersection type:

type FooOrBar =  'foo' | 'bar';

type MyMappedType = {
        [key in FooOrBar]:number;
} & {
        [key in string]:  string;
};

var x: MyMappedType = { //       Type 'number' is not assignable to type 'string'.(2322)
    foo:  1,
    bar:  2,   
    baz: 'I am a string'  
}

Example 2

But this doesn't work either.

Is there any possibility to define a type like this in typescript?

1 Answers

You could create properties for each of your indexing cases, e.g.

type numberLiterals = 'foo' | 'bar'

type myMappedType = {
    numbers: { [key in numberLiterals]: number; };
    strings: { [key: string]: string }; 
}

var x: myMappedType = {
    numbers: {
        foo: 1,
        bar: 2,
        // zzz: 5 -> not assignable
    },
    strings: {
        fo: '',
        fooo: ''
        // baar: 1 -> not assignable
    }
}

Playground

Related