AutoMapper TypeScript

repository·main·Indexed 21 days ago

https://github.com/nartc/mapper

An object-to-object mapping library for TypeScript applications that allows developers to define mapping rules between classes, POJOs, and ORM entities. It includes a core engine (@automapper/core) and various strategies such as @automapper/classes, @automapper/pojos, @automapper/mikro, and @automapper/sequelize. The library provides utilities like MapperPickType, MapperOmitType, and MapperIntersectionType, as well as a transformer plugin to automate metadata injection for classes.

Tokens
43.8K
Snippets
174
Records
217
Agent score
75%

What's inside automapper

  1. Overview of AutoMapper TypeScript packages

    main

    AutoMapper TypeScript is a monorepo containing the @automapper/core engine and several official plugins for different data types and frameworks.

    Core Packages

    • @automapper/core: The central engine for mapping.
    • @automapper/classes: Support for mapping between class instances.
    • @automapper/classes/mapped-types: Utility types for mapped classes.
    • @automapper/classes/transformer-plugin: A plugin for class transformers.
    • @automapper/pojos: Support for mapping Plain Old JavaScript Objects (POJOs).
    • @automapper/mikro: Integration for MikroORM.
    • @automapper/sequelize: Integration for Sequelize.
    • @automapper/nestjs: Integration for the NestJS framework.

    Integration and Testing

    • integration-test: Contains integration tests for Core and official plugins.
    • nestjs-integration-test: Contains integration tests specifically for NestJS.
  2. Overview of AutoMapper TypeScript

    main
    AutoMapper TypeScript is an object-to-object mapper designed to handle property matching, nested models, and flattening. It allows you to map TypeScript objects by convention and then explicitly configure only the differences, keeping your mapping code focused on unique transformations rather than boilerplate.
  3. Explore the AutoMapper TypeScript API reference

    main

    The AutoMapper TypeScript API reference provides exact signatures and exported types for all public packages. Use the following categories to find specific technical details:

    • Core: Covers mapper creation, mapping configuration, member functions, conventions, and error types.
    • Classes: Details decorator metadata and the classes mapping strategy.
    • POJOs: Provides metadata helpers and the POJO (Plain Old JavaScript Object) mapping strategy.
    • MikroORM: Documentation for the MikroORM-aware classes strategy.
    • Sequelize: Documentation for the Sequelize-aware classes strategy.
    • NestJS: Covers module setup, dependency injection, profiles, interceptors, and pipes.
  4. New features in AutoMapper 9

    main

    AutoMapper 9 introduced several key improvements to mapping capabilities and error handling:

    Asynchronous Mapping

    • map and mutation APIs now await promise-returning member resolvers and lifecycle callbacks.
    • NestJS MapInterceptor and MapPipe now await asynchronous object and array mappings.

    Lifecycle and Collections

    • beforeMapArray() and afterMapArray() allow for configuring lifecycle work across entire collections.

    Error Handling

    • Specific error types are now available for better inspection: AutoMapperError, MappingNotFoundError, and MapMemberError.

    Performance and Flexibility

    • Compiled Mapping Plans: Mapping plans are compiled during mapping creation to reduce per-object overhead.
    • Class Identifiers: Now support abstract classes and classes with non-public constructors.
    • Logging: AutoMapperLogger supports additional log levels, repeatable configuration, restoration, and reset.
  5. typeConverter vs convertUsing()

    main

    Choose between typeConverter and convertUsing() based on the scope of the conversion required:

    • typeConverter: Use this when you want the conversion logic to apply to every matching property pair in a mapping that shares the specified source and destination metadata types.
    • convertUsing(): Use this when the conversion should apply to only one specific destination member rather than globally across the mapping.
  6. How auto-flattening works with naming conventions

    main

    Auto-flattening allows AutoMapper to map nested source properties to a single destination property by matching the destination name against a path through the source model. The configured naming convention determines how the destination name is split into path segments.

    For example, with CamelCaseNamingConvention enabled, a destination property named customerName is split into customer and name. AutoMapper will then attempt to map from source.customer.name.

    If a flattened name is ambiguous or does not follow the configured convention, use forMember() with mapFrom() to explicitly define the mapping.

    class Customer {
      @AutoMap()
      name!: string;
    }
    
    class Order {
      @AutoMap(() => Customer)
      customer!: Customer;
    
      @AutoMap()
      total!: number;
    }
    
    class OrderDto {
      @AutoMap()
      customerName!: string;
    
      @AutoMap()
      total!: number;
    }
    
    // Setup with CamelCase to enable auto-flattening
    const mapper = createMapper({
      strategyInitializer: classes(),
      namingConventions: new CamelCaseNamingConvention(),
    });
    
    createMap(mapper, Order, OrderDto);
    
    const dto = mapper.map(order, Order, OrderDto);
    // Result: { customerName: '...', total: ... }
  7. Group mappings with MappingProfile

    main

    A MappingProfile is a function used to organize and group related mapping declarations. It receives a mapper instance as an argument and uses it to register mappings via createMap. This allows you to modularize your mapping logic into reusable units that can be registered during application startup.

    Note: When registering profiles, the order of registration matters if one mapping depends on another mapping defined in a different profile.

    import type { MappingProfile } from '@automapper/core';
    
    export const bioProfile: MappingProfile = (mapper) => {
      createMap(
        mapper,
        Bio,
        BioDto,
        typeConverter(Date, String, (date) => date.toDateString()),
      );
    };
    
    // Registering the profile
    addProfile(mapper, bioProfile);
  8. Use Asynchronous Mapping in AutoMapper 9

    main

    In v9, asynchronous mapping is a first-class citizen. Unlike v8, which only deferred the operation, v9 APIs actually collect and await asynchronous work within:

    • mapAsync() and mapArrayAsync()
    • mutateAsync() and mutateArrayAsync()
    • mapFrom() selectors and resolvers
    • mapWithArguments() selectors and resolvers
    • beforeMap() and afterMap() callbacks

    Warning: If a member selector or callback returns a Promise, calling the synchronous map() or mapArray() will now throw an error. You must use the async variants to prevent promises from being silently assigned to destination members.

    createMap(
      mapper,
      User,
      UserDto,
      forMember(
        (destination) => destination.displayName,
        mapFrom(async (source) => loadDisplayName(source.id)),
      ),
      afterMap(async (_, destination) => {
        destination.permissions = await loadPermissions(destination.id);
      }),
    );
    
    const dto = await mapper.mapAsync(user, User, UserDto);
  9. What are Mapping Profiles and how to use them

    main

    A MappingProfile is a way to group related mapping declarations for a specific feature or domain. It is a function that accepts a mapper instance and uses createMap() to register multiple mappings at once. This helps organize your code and keeps mapping logic modular.

    To use a profile, define it as a function and then register it with the mapper using addProfile() during your application's startup phase.

    const userProfile: MappingProfile = (mapper) => {
      createMap(mapper, Address, AddressDto);
      createMap(mapper, User, UserDto);
      createMap(mapper, User, UserSummaryDto);
    };
    
    // Register the profile during startup
    addProfile(mapper, userProfile);
  10. How mappings work in AutoMapper

    main

    A Mapping is a unidirectional contract between a source identifier and a destination identifier. Because mappings are unidirectional, you must explicitly create a mapping for each direction if you need to map both ways between two types.

    Each source/destination pair is unique within a single mapper instance. You can also implement self-mapping (mapping a type to itself) using a single identifier.

    // To allow bidirectional mapping, you must create both directions explicitly
    createMap(mapper, User, UserDto);
    createMap(mapper, UserDto, User);
  11. How self-mappings work with nested values

    main
    If a parent mapping contains a type on both the source and destination metadata (e.g., a property in the source is of type Person and the corresponding property in the destination is also of type Person), AutoMapper will automatically use the registered self-mapping for that nested value.
  12. When to use convertUsing() vs type converters

    main

    Choosing between convertUsing() and type converters depends on the scope of the conversion:

    • Use Type Converters: When the same metadata conversion should apply to every matching property pair across your entire mapping configuration (e.g., always converting Date to string).
    • Use convertUsing(): When the conversion logic is specific to one destination member and should not be applied globally.