How to extract "string" property names from an interface in Typescript?

6.4k views Asked by At

Typescript playground

I want to extract the string properties from SOME_OBJECT and get it as a union type. Therefore, in the example below, I expect STRING_KEYS to be of type "title" | "label"

interface SOME_OBJECT {
    title:      string,
    label:      string,
    someBool:   boolean,
    someDate:   Date,
    someNumber: number
}

type ExtractString<T> = keyof T extends string ? keyof T : never;

type STRING_KEYS = ExtractString<SOME_OBJECT>  // <----- THIS SHOULD BE "title" | "label"

This is what I'm getting:

enter image description here

I think I'm in the right direction here (by using conditional types), but I'm not quiet there yet. What is the best way to achieve this?

2

There are 2 answers

3
cbdeveloper On BEST ANSWER

Got the idea from the example below from Typescript Docs: Distributive Conditional Types

enter image description here

So I've adapted it, and it works:

interface SOME_OBJECT {
    title:      string,
    label:      string,
    someBool:   boolean,
    someDate:   Date,
    someNumber: number
}

type ExtractStringPropertyNames<T> = {
    [K in keyof T]: T[K] extends string ? K : never
}[keyof T]

type STRING_KEYS = ExtractStringPropertyNames<SOME_OBJECT>

enter image description here

Typescript playground

If possible, I'm still interested in see other ways of doing that. Especially if there are more straightforward ways of doing it, as I think this answer feels like a "hack", 'cause it does not make it very clear what the code is doing.


UPDATE (HOW IT WORKS)

I've studied a bit more to understand what the code is actually doing, and I think I've figured it out.

I've broken it into two steps:

STEP 1

In the first step, a new object/type will be created.

The keys for this new object will the same keys K in keyof T as from the keys of the generic type T (SOME_OBJECT in the example).

And for the values of those keys, we'll check in the generic type T, if the values for those properties K extends string. Which means that those values are assignable to string. For example: string, "some_hard_coded_string_type" and any will all pass the test.

For each property that passes the test, we will repeat its key K name as the value for them, otherwise will pass never. You can see the result in STEP_1_RESULT below.

enter image description here

STEP 2

In this step, we'll simply get that object generated from the step 1, and ask Typescript to return all of its possible values, by doing type STEP_2<T> = T[keyof T].

Remember that keyof T represents the union of all properties from T. So Typescript will return a union with all possible values for the STEP_1_RESULT object when called with the members of the keyof T union.

In our example, it will return "title" | "label" | never | never | never.

But since the never types are meaningless inside a union type, those values are discarded and we are left with "title" | "label".

enter image description here

1
Georgios Kampitakis On

I am not really sure about the conditional types. What I would probably try to use here is the Pick and I would do something like

interface SOME_OBJECT {
    title:      string,
    label:      string,
    someBool:   boolean,
    someDate:   Date,
    someNumber: number
}

type ExtractString = Pick<SOME_OBJECT, "title" | "label">;

type STRING_KEYS = ExtractString