asnaeb
asnaeb

Reputation: 745

Exclude `{}` from conditional mapped type

We work on the following interface

interface A {
    a: string
    b: string
    c?: number
    d?: number
}

And we have a type which makes every key in T optional if their type is string and required if it is number

type B<T> = {                                      
    [K in keyof T as T[K] extends (number|undefined) ? K : never]-?: T[K]                      
} & {
    [K in keyof T as T[K] extends (string|undefined) ? K : never]+?: T[K] 
}

/* The resulting type will be:
type B<A> = {
    c: number;
    d: number;
} & {
    a?: string | undefined;
    b?: string | undefined;
}
*/

However, if we change the interface we are working on to only include one of the types specified in the condition, {}, which almost corresponds to any will be added to the resulting type

interface A1 {
    a: string
    b: string
}

/* The resulting type will be:
type B<A1> = {} & {
    a?: string | undefined;
    b?: string | undefined;
}
*/

This will allow assigning many unwanted types to B, defeating out purpose. For example

const b: B<A1> = "We don't want this to happen." // <-- We need an error here.

Question

How to prevent the resulting type from including {} ? I want B<A1> to result in the following type

{
    a?: string | undefined;
    b?: string | undefined;
}

Playground Link

I have simplified the type by removing the generic, so that resulting types are visible. You can check it here

Upvotes: 2

Views: 304

Answers (2)

tokland
tokland

Reputation: 67850

@Aplet123 points to https://github.com/microsoft/TypeScript/issues/42864 as the root of the problem. A workaround (probably there are others even simpler): a helper that forces it to be an object type:

type Obj<T> = { [K in keyof T]: T[K] };

const b: Obj<B<A1>> = "We don't want this to happen." // Error! Good.

Upvotes: 1

Filly
Filly

Reputation: 439

You can do something like this to solve your problem. By mapping over the object type C the empty object disappears, because it doesn't have any keys to map over to.playground link

export type Narrow<A> =
  | (A extends Narrowable ? A : never)
  | (A extends [] ? [] : never)
  | {
      [K in keyof A]: A[K] extends Function ? A[K] : Narrow<A[K]>;
    };


type C =Narrow< {                                      
    [K in keyof A1 as A1[K] extends (number | undefined) ? K : never]-?: A1[K]                      
} & {
    [K in keyof A1 as A1[K] extends (string | undefined) ? K : never]+?: A1[K] 
}>
/* Resulting type:
type C = {
    a?: string | undefined;
    b?: string | undefined;
}
 */

const b: C = "This string should not be assignable to B!" // error

Upvotes: 1

Related Questions