Search code examples
typescriptmapped-types

allow only one property in mapped type


lets say there is this type:

type A = {
  prop1: string;
  prop2: string;
}

Than it is possible to construct a type B, such that all Properties of A must be in B like so:

type B = {
  [k in keyof A]: number
}

But how can I construct B, such that only one Property of A must be there?


Solution

  • Ok, so we can make it but its not trivial. The simple is to find if our type has at least one key.

    type AtLeastOneProp<X, Pattern> = (keyof X & keyof Pattern) extends never ? never : X
    // use
    type B = AtLeastOneProp<{x: string}, {x: string, y: string}> // 🟢 correct has at least one prop - x
    type C = AtLeastOneProp<{w: string}, {x: string, y: string}> // 🔴  never as has no props
    type D = AtLeastOneProp<{x: string, y: number, z: number}, {x: string, y: string}> // 🟢 ok has more than one prop
    

    What we do here is we check if intersection of keys gives as any result. If so we return the original type.

    The more difficult is to enforce that only one key can exists in our type.

    type IsSingleVariantOrMore<U extends PropertyKey, Result extends 0 | 1 | 'more' = 0, NextResult extends 0 | 1 | 'more' = Result extends 0 ? 1 : Result extends 1 ? 'more' : 'more'> = 
      ({
        [K in U]: Exclude<U, K> extends never ? NextResult : IsSingleVariantOrMore<Exclude<U, K>, NextResult>
      })[U]
    type HasOnlyOneProp<X, Pattern> = IsSingleVariantOrMore<keyof X & keyof Pattern> extends 1 ? X : never;
    // use
    type E = HasOnlyOneProp<{x: string, z: number}, {x: string, y: string}>; // 🟢 correct has only x 
    type F = HasOnlyOneProp<{x: string, y: number}, {x: string, y: string}>; // 🔴 wrong it has two props D is never
    
    

    What I am doing here is using IsSingleVariantOrMore which calculates if we have variant with one possible element or more. The function HasOnlyOneProp simply checks if we have 1 if so it means type has only one prop in common, any other result means that type has no intersected props or have more than one.

    Full code in the playground.