[TS] Differentiate class instance from pojo

I'm looking for some type Differentiator which will satisfy the following type checks;
// types
type Differentiator = ...

type IsClassInstance<T> = T extends Differentiator ? true : false;

// test data

class MyClass {}

class ClassWithProps {
foo!: string;
bar() {}
}

// tests
type TestEmptyObject = IsClassInstance<{}> // expect: false
type TestPrimitiveKey = IsClassInstance<{ foo: string }> // expect: false
type TestFunctionKey = IsClassInstance<{foo(): void }> // expect: false
type TestArrowKey = IsClassInstance<{ foo: () => void }> // expect: false
type TestMyClass = IsClassInstance<MyClass> // expect: true
type TestClassWithProps = IsClassInstance<ClassWithProps> // expect: true
type TestBuiltin = IsClassInstance<Date> // expect: true
// types
type Differentiator = ...

type IsClassInstance<T> = T extends Differentiator ? true : false;

// test data

class MyClass {}

class ClassWithProps {
foo!: string;
bar() {}
}

// tests
type TestEmptyObject = IsClassInstance<{}> // expect: false
type TestPrimitiveKey = IsClassInstance<{ foo: string }> // expect: false
type TestFunctionKey = IsClassInstance<{foo(): void }> // expect: false
type TestArrowKey = IsClassInstance<{ foo: () => void }> // expect: false
type TestMyClass = IsClassInstance<MyClass> // expect: true
type TestClassWithProps = IsClassInstance<ClassWithProps> // expect: true
type TestBuiltin = IsClassInstance<Date> // expect: true
I've tried the following for Differentiator, but none of them distinguish class instances from POJOs, or even builtin classes from POJOs, the only thing I've got to work is a long union of every builtin class, which fails to identify custom classes.
type Differentiator = new (...args: any[]) => any
type Differentiator = InstanceType<new (...args: any[]) => any>
type Differentiator = Record<string, unknown>
type Differentiator = Exclude<Record<string, unknown>, { constructor: Function }>
type Differentiator = new (...args: any[]) => any
type Differentiator = InstanceType<new (...args: any[]) => any>
type Differentiator = Record<string, unknown>
type Differentiator = Exclude<Record<string, unknown>, { constructor: Function }>
Is this even possible? Does TS have a mechanism to differentiate object literals from classes? I know that JS technically sees them as the exact same thing but I would have thought that TS would be capable of analysing this, even if it isn't meaningful at runtime. My reason for doing this is that I have a function which takes in any data, and returns the same shape but modified, eg if you want to convert all compatible strings into Dates that can be done in a type-safe way. The only issue is, if there's a class instance in the object the function will run through every single key on that class and attempt to modify it, as will the types. I'm pretty sure I can solve that at runtime, but I can't figure it out in TS types. It would be so much easier if the constructor property actually inferred the correct constructor rather than Function.
1 Reply
Joao
Joao2y ago
I think you might find better luck with a detailed answer if you ask over on the TypeScript Discord server: https://discord.gg/typescript
Want results from more Discord servers?
Add your server