MTCoster
MTCoster

Reputation: 6145

How can I narrow the typing of `T extends {}` to require `keyof T` to always be sting?

I currently have this mapping type, and it works great in a lot of situations:

type WithKeyPrefix<P extends string, T extends {}> = {
  [K in Extract<keyof T, string> as `${P}/${K}`]: T[K];
};

However, I can't get it to reject objects which contain non-string keys. I've tried a couple different solutions so far, but nothing that works as I'd like it to:

type WithKeyPrefix<P extends string, T extends {}> = {
  [K in Extract<keyof T, string> as `${P}/${K}`]: T[K];
} & {
  [K in Exclude<keyof T, string>]: never;
};
type WithKeyPrefix<P extends string, T extends Record<string, unknown>> = {
  [K in Extract<keyof T, string> as `${P}/${K}`]: T[K];
};

The first makes no apparent difference. The second produces this error when T is an interface with limited keys:

TS2344: Type 'Foo' does not satisfy the constraint 'Record<string, unknown>'.
  Index signature is missing in type 'Foo'.

Is there a way to constrain the type of T in the way I want?


Bonus question: Why is Extract<keyof T, string> needed even when T is constrained as Record<string, unknown>?

I came across this question already, but the solutions there are effectively workarounds which are perfectly valid in the context of that question, but cannot work in my situation; hence the new question.

Upvotes: 0

Views: 448

Answers (1)

aleksxor
aleksxor

Reputation: 8370

To reject objects with non-string properties you may rewrite your type as:

type WithKeyPrefix<
    P extends string,
    T extends object & (keyof T extends string ? {} : "Must have only string keys"),
> = {
    [K in Extract<keyof T, string> as `${P}/${K}`]: T[K];
};

Credits for string only keys constraint go to this great answer

playground link

As for Extract<keyof T, string> it's required because something that extends Record<string, any> is not guaranteed to have only string keys.

Upvotes: 1

Related Questions