Simplifying type mapping in TypeScript

When dealing with web applications, we often find the need to map types to other types. Whether it’s in a model-view-controller (MVC) architecture where we need to map view changes to model changes, or when we need to transform data based on specific conditions, this kind of logic is both common and essential. Handling these mappings can quickly become complicated and hard to manage, especially when dealing with different object structures and types.

This is where Mapsy comes in. It allows you to create powerful mapping logic using sophisticated utility types, making it easier to work with dynamic object structures in a clean and scalable way. By using Mapsy, you can avoid messy conditional statements and apply logic dynamically depending on the properties of the objects you are working with.

What Problem Does It Solve?

In many situations, we encounter objects that share common properties but differ in specific ways. For example, a user might have different types of vehicles, and you need to execute different logic depending on whether the vehicle is a car or a bike. Without Mapsy, handling this would typically involve a lot of conditional code, leading to bloated and error-prone logic. Mapsy helps by allowing you to map an object's properties to specific functions, ensuring clean and maintainable code, while TypeScript ensures type safety throughout the process.

Example: Mapping Logic Based on Vehicle Type

Let’s take a scenario where we have a Person who owns either a Car or a Bike. Based on the vehicle type, we want to generate a description of the person’s mode of transportation. Here’s how Mapsy can help simplify this logic:

import { mapsy, Narrow } from 'mapsy'

interface Person {
  name: string
  age: number
  vehicle: Vehicle
}

type Vehicle = Car | Bike

interface Car {
  details: {
    type: 'car'
    fuel: 'electric' | 'gas'
    model: string
    year: number
  }
  performance: {
    horsepower: number
    topSpeed: number // in km/h
  }
}

interface Bike {
  details: {
    type: 'bike'
    gear: 'fixed' | 'multi'
    brand: string
  }
  specifications: {
    weight: number // in kg
    frameMaterial: string
  }
}

type PersonWithCar = Narrow<{
  object: Person
  nested: ['vehicle']
  subset: ['details', 'type']
  reduce: 'car'
}>

type PersonWithBike = Narrow<{
  object: Person
  nested: ['vehicle']
  subset: ['details', 'type']
  reduce: 'bike'
}>

const describePerson = mapsy<{
  object: Person
  nested: ['vehicle']
  subset: ['details', 'type']
  params: []
  return: string
}>(['vehicle'], ['details', 'type'], {
  car: (person: PersonWithCar) => {
    return `This person drives a ${person.vehicle.details.fuel} car.`
  },
  bike: (person: PersonWithBike) => {
    return `This person rides a ${person.vehicle.details.gear} bike.`
  },
})

const personWithCar: PersonWithCar = {
  name: 'John',
  age: 25,
  vehicle: {
    details: {
      type: 'car',
      fuel: 'gas',
      model: 'Toyota Corolla',
      year: 2020,
    },
    performance: {
      horsepower: 132,
      topSpeed: 180,
    },
  },
}

console.log(describePerson(personWithCar)) // This person drives a gas car.

Why Use Mapsy?

  • Type Safety: It ensures that the correct logic is executed based on object types, with strong TypeScript support preventing errors at compile time.

  • Clean and Maintainable Code: It removes the need for complex conditionals and keeps your code organized.

  • Scalability: Adding new mappings for additional object types or properties is easy and doesn’t require refactoring the existing logic.

Conclusion

Mapsy is a flexible and powerful solution for managing type-based logic in your web applications. By allowing you to map types to specific behaviors in a clean, type-safe way, it helps you keep your codebase maintainable and scalable while avoiding repetitive conditional statements. Whether you’re working with polymorphic data types, complex models, or object transformation logic, mapsy makes the process simpler and more reliable.