#Create a function that get a class instance and returns an object based on the class constructor

12 messages · Page 1 of 1 (latest)

rapid kite
#

It's difficult for me to formulate the title well, let me show you what I want to achieve. First the types:

interface Entity {
    id: number;
    name: string | null;
}

class EntityDto {
    id: number;
    name: string | null;
    
    constructor(entity: Entity) {
        this.id = entity.id;
        this.name = entity.name;
    }
}

There will be many classes like EntityDto (ie, UserDto). Later, I need to "unpack" the original object from the dto, like this:

function unpackEntity(dto: EntityDto): Entity {
  // logic for creating an Entity object
}

There is clear relationship between the dto and the returned object for all functions like this. I want typescript to infer the return type based on the dto passed.

I know how to get the object type based on class:

type ConstructorArgument<T> = T extends new (...args: infer P) => any ? P[0] : never;

for example:

type ThisIsEntity = ConstructorArgument<typeof EntityDto>

I want to create a type for the "unpacker" function, like so:

type UnpackDto = <TDto>(dto: TDto) => ConstructorArgument<TDto>

So that I can create:

const unpackEntity: UnpackDto = (dto: EntityDto) => {
  // ... logic
}

const entity = unpackEntity(entityDto) // correctly has type of Entity

I can't figure out how to make typescript do what I want

visual sedge
#

i wrote this, but now i'm wondering whether unpackEntity is supposed to take the class or an instance:

leaden muskBOT
#
mkantor#0

Preview:```ts
...
type ConstructorArgument<T> = T extends new (
...args: infer P
) => any
? P[0]
: never

type UnpackDto = <TDto>(
dto: TDto
) => ConstructorArgument<TDto>

const unpackEntity: UnpackDto = dto => {
// ... logic
throw "not implemented"
}

const entity = unpackEntity(EntityDto)
...```

visual sedge
#

(that takes the class)

#

if instead it's supposed to take an instance, here's one idea:

leaden muskBOT
#
mkantor#0

Preview:```ts
...
type ConstructorArgument<T> = T extends new (
...args: infer P
) => any
? P[0]
: never

type UnpackDto = <
TDtoConstructor extends new (
...args: never[]
) => unknown

(
dto: InstanceType<TDtoConstructor>
) => ConstructorArgument<TDtoConstructor>

const unpackEntity: UnpackDto = dto => {
// ... logic
throw "not implemented"
}

const entityDto = new EntityDto({id: 1, name: "one"})
const entity =
unpackEntity<typeof EntityDto>(entityDto)
...```

visual sedge
#

there's no type-level way to look up the constructor from an instance, so you need to specify that somewhere. in the above playground i'm doing that by explicitly passing typeof EntityDto as the type parameter for unpackEntity which may not be what you want. you could do other things like maintaining a separate type-level map of DTO instance type => entity type

rapid kite
#

ok, thanks. Shame though, seems like it should be possible

visual sedge
#

what should be possible? inferring a constructor type from an instance?

#

consider this:

leaden muskBOT
#
interface SomeInterface {
    foo: string
}

class SomeClass implements SomeInterface {
    foo = 'foo'
    static a = 'a'
}
class AnotherClass implements SomeInterface {
    foo = 'foo'
    static b = 'b'
}

type ConstructorFor<T> = 'imagine this is possible to implement'

const f = <T extends SomeInterface>(instance: T) => {
    type X = ConstructorFor<T>
    // which class should `X` be here?
}

f(new SomeClass())
f(new AnotherClass())
f({ foo: 'foo' })
coral carbon
#

You cannot go from a class instance to its constructor, because instance of a class can be created from many constructors.