Let me explain my idea in this example. Lets say I have this class:
class Class1 {
f1!: string;
f2!: string;
f3!: number;
f4!: Date;
}
If I need a type with the members of types string
from that class.
I can do for example
type Class1Stings = Omit<Class1, 'f3' | 'f4'>;
But I need to know exact properties names for this. I want to have something like this:
type Class1Stings = KeepOnlyTypes<Class1, string>;
// or
type Class1Stings = RemoveTypes<Class1, number | Date>;
Is this possible?
============= Edit:
CodePudding user response:
It's a bit tricky, but you can do it by first finding the keys for those properties (using a trick I picked up here), then using Pick
as you indicated.
type PickStringProps<Source extends object> = Pick<Source, {
[Key in keyof Source]: Source[Key] extends string ? Key : never;
}[keyof Source]>;
// Usage:
class Class1 {
f1!: string;
f2!: string;
f3!: number;
f4!: Date;
}
type Class1Strings = PickStringProps<Class1>;
// ^?
Playground link (Explanation below.)
Alternatively, here's a reusable generic version:
// A type that picks out the keys for properties from `Source` that are
// assignable to `PickType`
type KeysByType<Source extends object, PickType> = {
[Key in keyof Source]: Source[Key] extends PickType ? Key : never;
}[keyof Source];
// A type that picks the properties from `Source` that are assignable to `Picktype`
type PickByType<Source extends object, PickType> =
Pick<Source, KeysByType<Source, PickType>>;
(We could combine the two above, but this is a bit clearer.)
Usage:
class Class1 {
f1!: string;
f2!: string;
f3!: number;
f4!: Date;
}
type Class1Strings = PickByType<Class1, string>;
// ^?```
Copying from my answer here, here's how KeysByType
works — there are two parts to it:
The
{/*...*/}
part maps each property ofSource
into a new mapped type where the type of the property is the key itself ornever
. Suppose we had:type Example = { a: string; b: string; c: number; };
Just the first part of
KeysByType<Example, string>
creates this anonymous type:{ a: "a"; b: "b"; c: never; }
Then the
[keyof Source]
part at the end creates a union of the types of those properties, which in theory would be"a" | "b" | never
, butnever
is always dropped from union types, so we end up with"a" | "b"
— the keys ofExample
for properties with array types.
Then to create PickByType
, we use Pick
as you indicated.
In a comment you've asked why this doesn't seem to be working here:
function assignDates<T extends Class1>(
obj: T,
source: Record<KeysByType<T, Date>, string> | undefined,
keys: KeysByType<T, Date>[]
): void {
if (source) {
for (const key of keys) {
if (source[key] !== undefined) {
obj[key] = new Date(source[key]);
// ^^−−− Type 'Date' is not assignable to type 'T[KeysByType<T, Date>]'.(2322)
}
}
}
}
(Note that I've updated that a bit from your original: I changed the name json
to source
since its value isn't JSON [that would be a string], and fixed the type so you don't need the as any
on source[key]
: its values are strings, not Dates, and from the code it appears to be optional.)
The answer is: It is working, but the assignDates
function isn't typesafe, because although Class1
may have only Date
properties (specifically), subclasses of it can refine those into a a Date
subtype:
class MyDate extends Date {
myNiftyMethod() { /*...*/ }
}
class Class2 extends Class1 {
f4: MyDate;
// ...
}
Class2
fits the type constraint extends Class1
so you could pass it into assignDates
, but f4
isn't just a Date
anymore, it's a MyDate
, and so you can't assign a Date
to it.
Of course, a non-generic version of assignDate
has that same Class2
problem, but doesn't raise an error on the obj[key]
assignment. I expect this is some kind of limitation of TypeScript.
Given that the non-generic version doesn't have an error, you might just tell TypeScript to ignore the error, but you might want to post a question (since we're pretty far afield of the original question you asked here) asking whether there's a way to write a generic version in a typesafe way. (If you do, please link to it in the comments below; I'll be interested to see the answers.)