Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Is there a way to get type information from an inherited generic abstract class in typescript?

Given a scenario where I want to be able to ‘tag’ data coming into my system from various sources how might I go about adding this source information onto any type T within my system given some ‘source’ function that provides this mapping

The shape of the data I want to append this tagged information onto doesn’t particularly matter – all I want users of my API to care about is providing a function that will use their type T to determine where the source is coming from and then a simple translation function of the new unioned type.

I’ve tried doing something like so:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

enum TAG {
  EXTERNAL_SYSTEM_A,
  EXTERNAL_SYSTEM_B,
  EXTERNAL_SYSTEM_C,
  UNKNOWN
}

interface TAGSource {
  sourceTypes: TAG | TAG[];
}

// This can be literally any type at all
interface ExternalEntity {
  name: string;
}

interface CalendarEntity{
  id: number;
}

type SourceTagged<T> = T & TAGSource;

abstract class SourceTagMapper {
  tagEntity<T>(entity: T): SourceTagged<T> {
    return { sourceTypes: this.mapSourceTypes(entity), ...entity };
  }
  protected abstract mapSourceTypes<T>(entity: T):  TAG | TAG[];
}

// Example user implementation
class ExternalEntitySourceTagMapper extends SourceTagMapper{
  mapSourceTypes<ExternalEntity>(entity: ExternalEntity): TAG | TAG[] {
    // Error: Property 'name' does not exist on type 'ExternalEntity'
    console.log(entity.name);
    // do some work to map external entity to a tag
    return TAG.EXTERNAL_SYSTEM_A;
  }
}

// Example user implementation
class CalendarEntitySourceTagMapper extends SourceTagMapper {
  mapSourceTypes<CalendarEntity>(entity: CalendarEntity): TAG | TAG[] {
    // Error: Property 'id' does not exist on type 'CalendarEntity'
    console.log(entity.id);
    return [TAG.EXTERNAL_SYSTEM_B, TAG.EXTERNAL_SYSTEM_C];
  }
}

But the problem with this approach is I get no type information in the mapSourceTypes function due to the erasure of type information at runtime. I’m struggling to come up with a design that gives users the ability to just supply a function for mapping and then provides a function for ‘providing’ this mapping back to them with the added type information.

I tried inheriting from an abstract class with a generic function but lost type information in my base classes but due to type erasure the properties no longer exist in my derived class. Any thoughts or consideration for improvement would be greatly appreciated.

>Solution :

The generic should be on the class, not the methods ! That allows you to extends a generic class with a specified type.

abstract class SourceTagMapper<T> {
  tagEntity(entity: T): SourceTagged<T> {
    return { sourceTypes: this.mapSourceTypes(entity), ...entity };
  }
  protected abstract mapSourceTypes(entity: T):  TAG | TAG[];
}

// Example user implementation
class ExternalEntitySourceTagMapper extends SourceTagMapper<ExternalEntity>{
  mapSourceTypes(entity: ExternalEntity): TAG | TAG[] {
    // Error: Property 'name' does not exist on type 'ExternalEntity'
    console.log(entity.name);
    // do some work to map external entity to an lvc type
    return TAG.EXTERNAL_SYSTEM_A;
  }
}

// Example user implementation
class CalendarEntitySourceTagMapper extends SourceTagMapper<CalendarEntity> {
  mapSourceTypes(entity: CalendarEntity): TAG | TAG[] {
    // Error: Property 'id' does not exist on type 'CalendarEntity'
    console.log(entity.id);
    return [TAG.EXTERNAL_SYSTEM_B, TAG.EXTERNAL_SYSTEM_C];
  }
}

Playground

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading