Labhansh Agrawal
Labhansh Agrawal

Reputation: 537

Typescript: How do you filter a type's properties to those of a certain type?

I have a interface

export interface MyInterface {
    a: number;
    b: string;
    c: number;
}

I want to create a literal type of the property names for which value is of type number

I know how to get the type with all property names using

type MyType = keyof MyInterface // gives 'a'|'b'|'c'

I want to get just 'a'|'c'

Upvotes: 16

Views: 13703

Answers (4)

user14967413
user14967413

Reputation: 1416

You can also achieve this with key remapping via as.

type KeysMatching<T extends object, V> = keyof {
  [K in keyof T as T[K] extends V ? K : never]: any
}
type MyType = KeysMatching<MyInterface, number>
// MyType = 'a' | 'c'

If T[K] is not assignable to V, we exclude it from properties by specifying never type (See this answer for detailed description.)

Upvotes: 0

Daniel Tonon
Daniel Tonon

Reputation: 10432

I recommend installing the utility-types library.

npm i utility-types

It has a bunch of extra TypeScript utility types that make doing stuff like this much easier.

The particular utility type you would be interested in is PickByValueExact<ObjectType, ValueType>

import { PickByValueExact } from 'utility-types';

type Props = { req: number; reqUndef: number | undefined; opt?: string; };

// Expect: { req: number }
type Props = PickByValueExact<Props, number>;
// Expect: { reqUndef: number | undefined; }
type Props = PickByValueExact<Props, number | undefined>;

There is also the more lenient PickByValue<ObjectType, ValueType> utility

import { PickByValue } from 'utility-types';

type Props = { req: number; reqUndef: number | undefined; opt?: string; };

// Expect: { req: number }
type Props = PickByValue<Props, number>;
// Expect: { req: number; reqUndef: number | undefined; }
type Props = PickByValue<Props, number | undefined>;

Upvotes: -1

jcalz
jcalz

Reputation: 327799

You certainly can define such a type in TypeScript:

type KeysMatching<T extends object, V> = {
  [K in keyof T]-?: T[K] extends V ? K : never
}[keyof T];

type MyType = KeysMatching<MyInterface, number>;
// type MyType = "a" | "c"

In this, KeysMatching<T, V> returns the set of keys of T whose properties are assignable to V. It uses a conditional and mapped type along with a property lookup. For each key K in keyof T, it checks whether T[K] is assignable to V. If so, it returns the key K; if not, it returns never. So for your types it would be something like {a: "a", b: never, c: "c"}. Then we look up the property values and get a union of the types like "a" | never | "c" which reduces to "a" | "c", exactly as you wanted.

Do note that KeysMatching<T, V> only returns those property keys whose values match V when reading the property. Those that are exactly V or a subtype of V:

interface AnotherInterface {
  narrower: 1;
  exact: number;
  wider: string | number;
}

type AnotherType = KeysMatching<AnotherInterface, number>;
// type AnotherType = "narrower" | "exact"

If you want to get the keys which match V when writing a property of T... that is, exactly V or a supertype of V, then you need a different implementation of KeysMatching:

type KeysMatchingWrite<T extends object, V> = {
  [K in keyof T]-?: [V] extends [T[K]] ? K : never
}[keyof T];

type AnotherTypeWrite = KeysMatchingWrite<AnotherInterface, number>;
// type AnotherTypeWrite = "exact" | "wider"

Anyway, hope that helps. Good luck!

Link to code

Upvotes: 33

Lostfields
Lostfields

Reputation: 1467

don't think you can pick properties by type but if you know the properties you accept you can create a new type based on those like this;

type MyType = Pick<MyInterface, 'a' | 'c'>

I like this blog post that covers most of the types (Readonly, Partial, Required, Pick, Record, Extract, Exclude etc) you could use, but I know Omit has been introduced lately too.

I found this answer that explains it better; Exclude property from type

Upvotes: 1

Related Questions