6

Let's say I have an interface like this:

interface SomeInterface {
    field1: string;
    field2: number;
    field3: string;
    field4: SomeOtherType;
}

I'd like to have a type that can extract all the keys who's values are a specific type:

type ExtractFieldsOfType<Obj extends {}, Type> = // implementation

ExtractFieldsOfType<SomeInterface, string> // "field1" | "field3"
ExtractFieldsOfType<SomeInterface, number> // "field2"
ExtractFieldsOfType<SomeInterface, SomeOtherType> // "field4"

Is it possible to write something like this? I'm writing a function that operates on an object by key name, but it should only allow keys with specific values. So it'd be nice to express that through the type system.

Brian Schlenker
  • 4,966
  • 6
  • 31
  • 44

1 Answers1

13

You can do it in TypeScript 2.8, with conditional types.

I created support type AllowedFieldsWithType to make cleaner implementation.

type AllowedFieldsWithType<Obj, Type> = {
    [K in keyof Obj]: Obj[K] extends Type ? K : never
};

type ExtractFieldsOfType<Obj, Type> = AllowedFieldsWithType<Obj, Type>[keyof Obj]

type StringFields = ExtractFieldsOfType<SomeInterface, string> // "field1" | "field3"
type NumberFields = ExtractFieldsOfType<SomeInterface, number> // "field2"
type ObjectFields = ExtractFieldsOfType<SomeInterface, SomeOtherType> // "field4"

How it works?

First type:

type AllowedFieldsWithType<Obj, Type> = {
    [K in keyof Obj]: Obj[K] extends Type ? K : never
};

1) For each field in object, we check if it extends Type, then we return name of the key as it's type. If not (e.g. we search for string, but field2 is type number - we return never. It's special type that nothing can be assigned to.

AllowedFieldsWithType<SomeInterface, String> equals to: {
  field1: 'field1';
  field2: never;
  field3: 'field3';
  field4: never;
}

2) Then we have to filter out never properties, we can do it with Pick, but for this use case we need only key names.

3) Eventually we use someType[keyof Obj] which gather values of given types, skipping never.

I've written article which explains this topic in depth: Create a condition-based subset types

Piotr Lewandowski
  • 6,540
  • 2
  • 27
  • 32
  • You don't need the extra pick to filter out the never `A|never=A`. This works just as well type `ExtractFieldsOfType = AllowedFieldsWithType[keyof Obj]` – Titian Cernicova-Dragomir Jun 18 '18 at 02:24
  • @TitianCernicova-Dragomir yes indeed, for this use case we don't have to use `Pick` :) I simplified answer. – Piotr Lewandowski Jun 18 '18 at 19:24
  • 1
    @Piotr Lewandowski, I read your post at https://www.piotrl.net/typescript-condition-subset-types/ and that's awesome, thanks for writing it down. I've noticed that you added a "What this solution won’t solve?" section and I'd like to let you know that I found a way to handle the first item in that list "Nullable subtype"... you check it out https://stackoverflow.com/questions/74272393/how-to-distinguish-different-functions-signature-with-conditional-type-checks/74272675 (specifically the question code) and update your page if you want :) – awdk Nov 01 '22 at 23:41
  • @Piotr Lewandowski, in the `null` case we'd do `Condition | null` then... or `Condition | null | undefined` for optional keys that can also be null... – awdk Nov 01 '22 at 23:44