Reputation: 537
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
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
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
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!
Upvotes: 33
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