I am looking for the most idiomatic way to handle a "Data-to-Behavior" mapping in TypeScript.
The Scenario: I receive a raw data object from a user that follows a Tagged Union pattern. Let's use a Vehicle analogy:
interface BaseVehicle {
id: string;
}
interface CarData extends BaseVehicle {
type: 'car';
doors: number;
}
interface TruckData extends BaseVehicle {
type: 'truck';
capacity: number;
}
type VehicleData = CarData | TruckData;
The Goal: I want to "hydrate" these plain data objects into specific classes that add behavior (e.g. a .drive() method). Each subclass will implement .drive() differently based on the properties of that vehicle type. Something like this:
abstract class Vehicle<T extends BaseVehicle> {
constructor(protected readonly data: T) {}
abstract drive(): void;
}
The Challenges:
- Naming Conventions: What is the best way to name the "Data" vs. the "Class"? I want the user-facing data interface to have a clean, intuitive name, but I also want my internal classes to be logically named. What is preferred in TS?
- How do I best implement a factory that takes a
VehicleDataunion and returns the correct class instance?
Is there maybe a different cleaner or more "TS-native" approach that I am not thinking of?