typescripttypescript-generics

How do I write a generic that takes an interface where the keys are anything but the values are forced to be keys?


Let's say I want to make a function called doThing that takes a generic. When using this function, I intend to manually input the generic with an interface which could have any keys, but the values should always be strings. I would like TypeScript to give me an error if any of the interface's values are not strings, like this:

function doThing<T extends ???> {
  // do a thing
}

interface GoodInterface {
  foo: string
  bar?: string
}

interface BadInterface {
  foo: number
  bar: string
}

doThing<GoodInterface>() // should work
doThing<BadInterface>() // should give a type error

Note that in GoodInterface, the optional bar key with string value is still accepted.

What would the generic have to extend to achieve this behavior? My initial thought was to extend Record<string, string>, but that gave me the error Index signature for type 'string' is missing in type 'GoodInterface' when I tried. I could add the index signature to the interfaces I plan to use, but I would prefer not to.


Solution

  • This one right here works

    Partial is used to indicate that T can extend types with optional properties.

    Record is the object type, keyof T are the keys of the generic type T (the keys of the interface that will be passed as a generic argument), string means that the values of properties can only be strings.

    declare function doThing<T extends Partial<Record<keyof T, string>>>(): void;
    
    interface GoodInterface {
      foo: string
      bar?: string
    }
    
    interface BadInterface {
      foo: number
      bar: string
    }
    
    doThing<GoodInterface>() // Works
    doThing<BadInterface>() // Types of property 'foo' are incompatible.