Search code examples
typescripttemplate-literalsconditional-typesmapped-types

Mapped Type with Prefix for each Property


I would like to create a type that only allows properties that start with a given prefix like that:

const example = {
  '#prop1': () => {},
  '#prop2': () => {}
} 

So each Property must be prefixed by a # in that case. I have played around with »Template Literal Types« within »Mapped Types« like so:

interface WithD {
  [s: `#${string}`]: () => void
}

But that yields this error:

An index signature parameter type must be either 'string' or 'number'.

Is there any way to make that happen?


Solution

  • Like @Nadia Chibrikova said, there is no way to build WithD without knowing example value up front.

    The only way to handle it - is to know (be able to infer) the type you want to validate.

    
    type Validation<T extends Record<string, any>> = {
      [Prop in keyof T]: Prop extends `#${string}` ? T[Prop] : never
    }
    
    type Assert<T, U> = T extends U ? U extends T ? true : false : false
    
    
    const example1 = {
      '#prop1': () => { },
      '#prop2': () => { },
    }
    
    const example2 = {
      ...example1,
      'prop3': () => { }
    }
    
    type Example1 = typeof example1;
    type Example2 = typeof example2;
    
    
    type Result1 = Assert<Example1, Validation<Example1>> // true
    type Result2 = Assert<Example2, Validation<Example2>> // false
    
    
    const isValid = <T extends Validation<T>>(arg: T) => { }
    isValid(example1) // ok
    isValid(example2) // expected error
    

    Playground

    Hence, TS should infer your type either from function argument or from type generic parameter