Search code examples
typescriptmapped-types

Typescript: list of keys into the object with the same keys and values based on key content


We need to type the function, which requires a list of keys (strings) and produces object with the same keys where values are string | undefined when key starts with ? or otherwise just string.

export function useParams<T extends string>(
  expected: T[]
): { [key in T as NonOptional<T>]: string } {
  // ... our custom implementation
  return null as any;
}

type NonOptional<T extends string> = T extends `?${infer Inner}` ? Inner : T;

// Should produce type { a: string; b: string | undefined }
const result = useParams(['a', '?b']);

However, I'm not sure how to type the output.


Solution

  • You may write it iterating over tuple's keys:

    export function useParams<T extends readonly string[]>(
      expected: T
    ): { [K in T[number] as NonOptional<K>]: string | OrUndefined<K> } {
      return null as any;
    }
    
    type NonOptional<T extends string> = T extends `?${infer Inner}` ? Inner : T;
    type OrUndefined<T extends string> = T extends `?${string}` ? undefined : never
    
    const result = useParams(['a', '?b'] as const);
    

    playground link


    Added section to address the problem described in comments:

    I'm pretty sure developers will forgot to add as const and then it will stop working correctly.

    To allow passing non-readonly tuples you may use variadic tuple types:

    export function useParams<T extends string[]>(
      expected: [...T]
    ): { [K in T[number] as NonOptional<K>]: string | OrUndefined<K> } {
      return null as any;
    }
    
    type NonOptional<T extends string> = T extends `?${infer Inner}` ? Inner : T;
    type OrUndefined<T extends string> = T extends `?${string}` ? undefined : never
    
    const result = useParams(['a', '?b']);
    

    playground link