A powerful, type-safe, extensible object mapping library for TypeScript and NestJS, built for real-world production use.
It supports decorators, caching, async mapping, validation, and more — all without the @automapper/core dependency.
- 🧩 Automatic mapping of DTOs using decorators
- ⚡ Caching support for high-performance repeated mappings
- 🌀 Async & sync mapping support
- 🧠 Type-safe mapping configurations
- 🪄 Deep clone and transformation utilities
- 🏗️ Nested, array, and enum mapping
- 💉 NestJS-friendly integration
- 🛠️ Mapper helpers (
mapFrom,ignore,transform, etc.) - 🧰 Validation rules per property
npm install @custom-automapper
# or
yarn add @custom-automapperimport { Mapper, AutoMap } from '@custom-automapper';
// DTOs
class SourceDTO {
@AutoMap()
name: string;
@AutoMap()
age: number;
}
class TargetDTO {
@AutoMap()
name: string;
@AutoMap()
age: number;
}
// Mapper
const mapper = new Mapper();
mapper.createMap(SourceDTO, TargetDTO);
const source = new SourceDTO();
source.name = 'John';
source.age = 30;
const target = mapper.map(source, TargetDTO);
console.log(target); // { name: 'John', age: 30 }You can configure global options like caching, cloning, or naming conventions in the constructor:
import { Mapper } from '@custom-automapper';
const mapper = new Mapper({
globalOptions: {
deepClone: true,
skipUndefined: true,
},
cache: {
enabled: true, // Enable caching globally
strategy: 'memory', // 'memory' (default) or custom
}
});mapper.setCacheEnabled(true);
console.log(mapper.isGlobalCacheEnabled()); // true
mapper.setCacheEnabled(false);You can explicitly define property mappings using mapFrom:
mapper.createMap(SourceDTO, TargetDTO, {
name: mapper.mapFrom(src => src.name.toUpperCase()), // transform source value
age: mapper.mapFrom('age') // map directly by key
});Generate reverse mapping automatically:
mapper.createMap(SourceDTO, TargetDTO, {
name: src => src.name,
age: src => src.age,
});
// Create reverse mapping (TargetDTO → SourceDTO)
mapper.createReverseMap(SourceDTO, TargetDTO);When mapping data that involves async transforms (e.g., fetching data or calling async functions):
await mapper.createMap(UserEntity, UserDTO, {
profileUrl: async src => await getProfileUrl(src.id)
});
const result = await mapper.mapAsync(userEntity, UserDTO);const users = [user1, user2, user3];
const dtos = mapper.mapArray(users, UserDTO);
// Or async version
const dtosAsync = await mapper.mapArrayAsync(users, UserDTO);Apply conditional mapping logic dynamically:
const target = mapper.mapConditional(source, TargetDTO, [
{
condition: src => src.age > 18,
map: src => ({ status: 'Adult' })
},
{
condition: src => src.age <= 18,
map: src => ({ status: 'Minor' })
}
]);Attach validation rules per class property and validate mapped objects.
mapper.addValidation(UserDTO, 'email', {
validate: value => value.includes('@'),
message: 'Email must contain @ symbol'
});
await mapper.validate(new UserDTO()); // throws if invalidDecorators like @AutoMap, @MapFrom, and @MapTo automatically handle property mapping.
class AddressDTO {
@AutoMap()
city: string;
}
class UserDTO {
@AutoMap()
name: string;
@AutoMap(() => AddressDTO)
address: AddressDTO;
}Get detailed insights into which properties were mapped or skipped.
const result = mapper.mapWithMetadata(source, TargetDTO);
console.log(result.metadata);
/*
{
mappedProperties: ['name', 'age'],
skippedProperties: [],
errors: [],
executionTime: 2
}
*/Each mapped source object is cached by reference using a WeakMap, minimizing re-computation on repeated mapping calls.
mapper.setCacheEnabled(true);
const src = new SourceDTO();
src.name = 'Cached User';
src.age = 25;
const first = mapper.map(src, TargetDTO);
const second = mapper.map(src, TargetDTO); // served from cache| Method | Description |
|---|---|
map() |
Maps an object synchronously |
mapAsync() |
Maps asynchronously |
mapArray() |
Maps an array synchronously |
mapArrayAsync() |
Maps an array asynchronously |
createReverseMap() |
Generates reverse mapping |
clear() |
Clears registry and cache |
getMappings() |
Lists registered mappings |
mapWithMetadata() |
Maps with runtime metadata |
class UserEntity {
@AutoMap()
id: number;
@AutoMap()
fullName: string;
@AutoMap()
email: string;
}
class UserDTO {
@AutoMap()
id: number;
@AutoMap()
name: string;
@AutoMap()
email: string;
}
const mapper = new Mapper({ cache: { enabled: true } });
mapper.createMap(UserEntity, UserDTO, {
name: src => src.fullName
});
const entity = { id: 1, fullName: 'Nishit Shiv', email: 'nishit@example.com' };
const dto = mapper.map(entity, UserDTO);
console.log(dto); // { id: 1, name: 'Nishit Shiv', email: 'nishit@example.com' }| Feature | Custom AutoMapper | @automapper/core |
|---|---|---|
| Decorator-driven mapping | ✅ | ✅ |
| Nested object mapping | ✅ | ✅ |
| Custom property mapping | ✅ | ✅ |
| Async mapping | ✅ | ✅ |
| Caching | ✅ | ❌ |
| Validation | ✅ | ❌ |
| DI integration | Manual | Built-in |
- Enhanced polymorphic mapping with type inference
- NestJS DI integration for singleton mappers
- Profiles for multi-context mapping
- Built-in transformations (
formatDate,uppercase, etc.)
Pull requests, discussions, and suggestions are welcome.
Open an issue or PR with a clear description of improvement.
MIT License
© 2025 Nishit Shiv