Reputation: 1763
Given the following:
class MyClass {}
class MyOtherClass { something!: number; }
type HasClasses = {
foo: MyClass;
bar: string;
doo: MyClass;
coo: {x: string;};
boo: MyOtherClass;
};
type RemovedClasses = RemoveClassTypes<HasClasses>;
I want RemovedClasses
to equal { bar: string; coo: {x: string;} }
. How can I do that?
I have tried what I thought would work but it didnt for some reason:
type ClassesKeys<T> = {
[P in keyof T]: T[P] extends new () => any ? P : never;
}[keyof T]
type RemoveClassTypes<T> = Omit<T, ClassesKeys<T>>;
Upvotes: 1
Views: 49
Reputation: 1075249
You're saying you want to remove properties from the type based on whether they relate to classes, leaving non-class
object types. That's why in your example you're removing foo
and doo
(both MyClass
) and boo
(MyOtherClass
), but not coo
({x: string;}
).
I don't think you can.
TypeScript's type system is structural (based on the shape of things), not nominal (based on their names). An object is just an object, it doesn't say whether it was created via new X()
or Object.create()
or an object literal — not at the type level, and largely not at the runtime JavaScript level either. What matters is the properties and methods that it has; its shape.
For example, look at this:
class Example {
something: string;
constructor(s: string) {
this.something = s;
}
method(): void {
}
}
let ex1 = new Example("ex1");
let ex2 = {
something: "ex2",
method(): void {
}
};
From the type perspective, there's no difference between ex1
and ex2
, even though at a runtime perspective there is a minor difference (ex1
inherits its method
, while ex2
has it as an own property).
Now assume you have these declarations:
let ex3: Example;
let ex4: { something: string; method(): void; };
All of these assignments are valid:
ex3 = ex1; // Works
ex3 = ex2; // Works
ex4 = ex1; // Works
ex4 = ex2; // Works
ex1 = ex4; // Works
ex2 = ex4; // Works
The types of those things are all compatible. The instance type Example
declared via a class
construct, the type inferred from the object initializer for ex2
, and the inline type assigned to ex4
, they're all compatible with each other.
One red herring would be that objects from classes have an inherited constructor
property, maybe we can use that! But you can't, because even ex2
above has an inherited constructor
property (from Object.prototype.constructor
, which is Object
), and separately I think TypeScript treats the constructor
property a bit specially (but can't point to any docs on that and may be mistaken).
So I'm afraid I don't think you can do what you've said you want to do.
Upvotes: 1