
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
om-data-mapper
Advanced tools
High-performance TypeScript/JavaScript data mapper and validator with JIT compilation faster than class-transformer/class-validator, zero dependencies, TC39 decorators, drop-in replacement for class-transformer and class-validator
High-performance TypeScript/JavaScript data mapper with JIT compilation for ultra-fast object transformations. Features a modern Decorator API with JIT compilation that delivers up to 42.7x better performance than class-transformer, while providing a clean, declarative syntax and zero runtime dependencies.
class-transformer:
import 'reflect-metadata'; // Extra dependency
import { plainToClass, Expose, Transform } from 'class-transformer';
class UserDTO {
@Expose({ name: 'firstName' })
name: string;
@Transform(({ value }) => value >= 18)
@Expose()
isAdult: boolean;
}
const user = plainToClass(UserDTO, data); // 326K ops/sec
om-data-mapper:
import { Mapper, Map, MapFrom, plainToInstance } from 'om-data-mapper';
@Mapper<Source, UserDTO>()
class UserMapper {
@Map('firstName')
name!: string;
@MapFrom((src: Source) => src.age >= 18)
isAdult!: boolean;
}
const user = plainToInstance(UserMapper, data); // 4.3M ops/sec (13.2x faster!)
Key Differences:
17.28x faster than class-transformer on average!
| Scenario | class-transformer | om-data-mapper | Performance Gain |
|---|---|---|---|
| Simple Transformation | 326K ops/sec | 4.3M ops/sec | 12.3x faster |
| Complex Nested | 154K ops/sec | 6.7M ops/sec | 42.7x faster |
| Array (100 items) | 5.2K ops/sec | 69K ops/sec | 12.3x faster |
| Custom Logic | 333K ops/sec | 4.8M ops/sec | 13.4x faster |
📊 See Transformer Usage Guide for detailed performance comparisons
Install om-data-mapper using npm:
npm install om-data-mapper
Or using yarn:
yarn add om-data-mapper
Or using pnpm:
pnpm add om-data-mapper
import { Mapper, Map, MapFrom, plainToInstance } from 'om-data-mapper';
// 1. Define your types
type UserSource = {
firstName: string;
lastName: string;
age: number;
email: string;
};
type UserDTO = {
fullName: string;
email: string;
isAdult: boolean;
};
// 2. Create a mapper class with decorators
@Mapper<UserSource, UserDTO>()
class UserMapper {
@MapFrom((src: UserSource) => `${src.firstName} ${src.lastName}`)
fullName!: string;
@Map('email')
email!: string;
@MapFrom((src: UserSource) => src.age >= 18)
isAdult!: boolean;
}
// 3. Transform your data
const source = {
firstName: 'John',
lastName: 'Doe',
age: 30,
email: 'john@example.com',
};
const result = plainToInstance<UserSource, UserDTO>(UserMapper, source);
console.log(result);
// { fullName: 'John Doe', email: 'john@example.com', isAdult: true }
That's it! Full TypeScript type safety, no boilerplate, clean code.
17.28x faster than class-transformer isn't just a number—it's real-world impact:
Before (class-transformer):
import 'reflect-metadata'; // ❌ Extra dependency
import { plainToClass, Expose, Transform } from 'class-transformer';
class UserDTO {
@Expose({ name: 'first_name' }) // ❌ Verbose configuration
firstName: string;
@Transform(({ value }) => value.toUpperCase()) // ❌ Wrapper objects
@Expose()
name: string;
}
const result = plainToClass(UserDTO, data); // ❌ Legacy decorators
After (om-data-mapper):
import { Mapper, Map, MapFrom, plainToInstance } from 'om-data-mapper';
@Mapper<Source, UserDTO>() // ✅ TC39 Stage 3 decorators
class UserMapper {
@Map('first_name') // ✅ Simple, clear
firstName!: string;
@MapFrom((src: Source) => src.name.toUpperCase()) // ✅ Direct access
name!: string;
}
const result = plainToInstance(UserMapper, data); // ✅ Type-safe
| Feature | class-transformer | om-data-mapper |
|---|---|---|
| Performance | Baseline | 17.28x faster |
| Dependencies | reflect-metadata required | Zero dependencies |
| Bundle Size | ~50KB | ~15KB (70% smaller) |
| Decorators | Legacy (experimental) | TC39 Stage 3 (standard) |
| Type Safety | Runtime only | Compile-time for transformers |
| JIT Compilation | ❌ | ✅ Optimized code generation |
| Null Safety | Manual | Automatic optional chaining |
| Error Handling | Throws exceptions | Structured error reporting |
// ✅ Type-safe mapper definition
@Mapper<UserSource, UserDTO>()
class UserMapper {
@Map('firstName') // String paths - runtime validation
name!: string; // TypeScript validates target type
@MapFrom((src: UserSource) => src.firstName) // ← Full type checking!
fullName!: string; // ← TypeScript knows src type and validates return type
}
// ✅ Type-safe transformers
@MapFrom((src: UserSource) => src.age) // ← Autocomplete for 'src' properties
age!: number; // ← TypeScript validates return type matches field type
// ⚠️ Note: String paths in @Map() are validated at runtime, not compile-time
// For compile-time safety, use @MapFrom() with typed functions
om-data-mapper provides a drop-in replacement for class-transformer with 17.28x better performance and zero dependencies.
npm install om-data-mapper
// Before
import 'reflect-metadata';
import { plainToClass, Expose, Type } from 'class-transformer';
// After
import { plainToClass, Expose, Type } from 'om-data-mapper/class-transformer-compat';
Your existing code works exactly the same, but 17.28x faster on average!
Benefits:
import { Mapper } from 'om-data-mapper';
type User = {
firstName: string;
lastName: string;
age: number;
};
type UserDTO = {
fullName: string;
isAdult: boolean;
};
const userMapper = Mapper.create<User, UserDTO>({
fullName: (user) => `${user.firstName} ${user.lastName}`,
isAdult: (user) => user.age >= 18,
});
const { result, errors } = userMapper.execute({
firstName: 'John',
lastName: 'Doe',
age: 30,
});
console.log(result); // { fullName: 'John Doe', isAdult: true }
Note: The Decorator API is recommended for new projects due to better performance and developer experience.
om-data-mapper delivers exceptional performance through JIT compilation and modern decorator implementation.
17.28x faster on average! See Transformer Usage Guide for detailed comparisons.
| Scenario | class-transformer | om-data-mapper | Improvement |
|---|---|---|---|
| Simple Transformation | 326K ops/sec | 4.3M ops/sec | 12.3x faster |
| Complex Nested | 154K ops/sec | 6.7M ops/sec | 42.7x faster |
| Array (100 items) | 5.2K ops/sec | 69K ops/sec | 12.3x faster |
| Custom Logic | 333K ops/sec | 4.8M ops/sec | 13.4x faster |
Performance is almost identical to hand-written code:
| Scenario | OmDataMapper | Vanilla | Overhead |
|---|---|---|---|
| Simple Mapping | 946M ops/sec | 977M ops/sec | 3% ⚡ |
| Complex Transformations | 21M ops/sec | 39M ops/sec | 89% |
Key Takeaways:
Simple Mapping (4 fields, nested access):
// Source → Target mapping
{ id, name, details: { age, address } } → { userId, fullName, age, location }
OmDataMapper: 945,768,114 ops/sec ±1.02% (100 runs)
Vanilla: 977,313,179 ops/sec ±2.51% (96 runs)
Complex Transformations (nested objects, arrays, custom functions):
// Multiple nested levels, array operations, custom transformers
OmDataMapper: 20,662,738 ops/sec ±1.36% (95 runs)
Vanilla: 38,985,378 ops/sec ±1.89% (96 runs)
Benchmarks located in /benchmarks directory. Run npm run bench to test on your machine.
We use automated benchmarks to track performance regressions:
Run benchmarks locally:
# Run class-transformer comparison
npm run bench:compat
# Run core benchmarks
npm run bench:core
# Run all benchmarks
npm run bench
Map properties directly or with transformations:
import { Mapper, Map, MapFrom, plainToInstance } from 'om-data-mapper';
type Source = { firstName: string; lastName: string; age: number };
type Target = { name: string; isAdult: boolean };
@Mapper<Source, Target>()
class UserMapper {
@Map('firstName') // Direct mapping
name!: string;
@MapFrom((src: Source) => src.age >= 18) // Custom transformation
isAdult!: boolean;
}
const result = plainToInstance(UserMapper, { firstName: 'John', lastName: 'Doe', age: 30 });
// { name: 'John', isAdult: true }
Access deeply nested properties with ease:
type Source = {
user: {
profile: {
email: string;
address: { city: string; street: string };
};
};
};
type Target = {
email: string;
city: string;
street: string;
};
@Mapper<Source, Target>()
class ProfileMapper {
@Map('user.profile.email') // Nested path with automatic null-safety
email!: string;
@Map('user.profile.address.city')
city!: string;
@Map('user.profile.address.street')
street!: string;
}
Combine multiple mappers for complex transformations:
type AddressSource = { street: string; city: string };
type AddressDTO = { fullAddress: string };
type UserSource = { name: string; address: AddressSource };
type UserDTO = { userName: string; location: AddressDTO };
@Mapper<AddressSource, AddressDTO>()
class AddressMapper {
@MapFrom((src: AddressSource) => `${src.street}, ${src.city}`)
fullAddress!: string;
}
@Mapper<UserSource, UserDTO>()
class UserMapper {
@Map('name')
userName!: string;
@MapWith(AddressMapper) // Compose with another mapper
@Map('address')
location!: AddressDTO;
}
const result = plainToInstance(UserMapper, {
name: 'John',
address: { street: '123 Main St', city: 'New York' }
});
// { userName: 'John', location: { fullAddress: '123 Main St, New York' } }
Transform arrays with built-in support:
type Source = {
users: Array<{ id: number; name: string }>;
};
type Target = {
userIds: number[];
userNames: string[];
};
@Mapper<Source, Target>()
class CollectionMapper {
@MapFrom((src: Source) => src.users.map(u => u.id))
userIds!: number[];
@MapFrom((src: Source) => src.users.map(u => u.name))
userNames!: string[];
}
Chain multiple decorators for complex logic:
@Mapper<Source, Target>()
class AdvancedMapper {
@MapFrom((src: Source) => src.value)
@Transform((val: number | undefined) => val !== undefined ? val * 2 : undefined)
@Default(0) // Fallback value
result!: number;
@Map('email')
@Transform((email: string) => email.toLowerCase())
normalizedEmail!: string;
}
Built-in error handling with tryTransform:
const mapper = new UserMapper();
// Safe transformation - returns errors instead of throwing
const result = mapper.tryTransform(source);
if (result.errors.length > 0) {
console.error('Transformation errors:', result.errors);
} else {
console.log('Success:', result.result);
}
🎉 NEW: om-data-mapper now includes a full API compatibility layer for class-transformer using modern TC39 Stage 3 decorators!
Simply replace your class-transformer imports:
// Before (class-transformer)
import { plainToClass, Expose, Type } from 'class-transformer';
// After (om-data-mapper)
import { plainToClass, Expose, Type } from 'om-data-mapper/class-transformer-compat';
import { plainToClass, Expose, Type, Transform } from 'om-data-mapper/class-transformer-compat';
class Address {
@Expose()
street: string;
@Expose()
city: string;
}
class User {
@Expose()
id: number;
@Expose()
@Transform(({ value }) => value.toUpperCase())
name: string;
@Expose()
@Type(() => Address)
address: Address;
@Exclude()
password: string;
}
const plain = {
id: 1,
name: 'john',
address: { street: '123 Main St', city: 'New York' },
password: 'secret'
};
const user = plainToClass(User, plain);
console.log(user.name); // 'JOHN'
console.log(user.address instanceof Address); // true
console.log(user.password); // undefined
// API Response
type ApiUser = {
id: number;
first_name: string;
last_name: string;
email_address: string;
created_at: string;
is_active: boolean;
};
// Frontend Model
type User = {
id: number;
fullName: string;
email: string;
createdDate: Date;
active: boolean;
};
@Mapper<ApiUser, User>()
class UserApiMapper {
@Map('id')
id!: number;
@MapFrom((src: ApiUser) => `${src.first_name} ${src.last_name}`)
fullName!: string;
@Map('email_address')
email!: string;
@MapFrom((src: ApiUser) => new Date(src.created_at))
createdDate!: Date;
@Map('is_active')
active!: boolean;
}
// Usage
const apiResponse = await fetch('/api/users/1').then(r => r.json());
const user = plainToInstance(UserApiMapper, apiResponse);
type UserEntity = {
id: number;
username: string;
passwordHash: string;
email: string;
profile: {
firstName: string;
lastName: string;
avatar: string | null;
};
createdAt: Date;
updatedAt: Date;
};
type UserDTO = {
id: number;
username: string;
email: string;
fullName: string;
avatarUrl: string;
memberSince: string;
};
@Mapper<UserEntity, UserDTO>()
class UserEntityMapper {
@Map('id')
id!: number;
@Map('username')
username!: string;
@Map('email')
email!: string;
@MapFrom((src: UserEntity) => `${src.profile.firstName} ${src.profile.lastName}`)
fullName!: string;
@MapFrom((src: UserEntity) => src.profile.avatar || '/default-avatar.png')
avatarUrl!: string;
@MapFrom((src: UserEntity) => src.createdAt.toISOString())
memberSince!: string;
}
// Usage in service
class UserService {
async getUser(id: number): Promise<UserDTO> {
const entity = await db.users.findById(id);
return plainToInstance(UserEntityMapper, entity);
}
}
type FormData = {
email: string;
password: string;
confirmPassword: string;
age: string; // From input field
terms: string; // 'on' or undefined
};
type RegistrationData = {
email: string;
password: string;
age: number;
agreedToTerms: boolean;
};
@Mapper<FormData, RegistrationData>()
class RegistrationMapper {
@Map('email')
@Transform((email: string) => email.toLowerCase().trim())
email!: string;
@Map('password')
password!: string;
@MapFrom((src: FormData) => parseInt(src.age, 10))
age!: number;
@MapFrom((src: FormData) => src.terms === 'on')
agreedToTerms!: boolean;
}
// Usage
const formData = new FormData(form);
const registration = plainToInstance(RegistrationMapper, Object.fromEntries(formData));
Complete documentation is available in both English and Russian:
📖 Documentation Index - Start here for complete guides
User Guides:
Internal Architecture:
📖 Индекс документации - Начните отсюда для полных руководств
Руководства пользователя:
Внутренняя архитектура:
@Mapper<Source, Target>(options?) - Class decorator to define a mapper@Map(sourcePath) - Map from source property (supports nested paths)@MapFrom(transformer) - Custom transformation function@Transform(transformer) - Post-process mapped value@Default(value) - Default value if source is undefined@MapWith(MapperClass) - Use nested mapper for complex objects@Ignore() - Exclude property from mappingplainToInstance<S, T>(MapperClass, source) - Transform single objectplainToClass<S, T>(MapperClass, source) - Alias for plainToInstanceplainToInstanceArray<S, T>(MapperClass, sources) - Transform array of objectstryPlainToInstance<S, T>(MapperClass, source) - Safe transformation with error handlingcreateMapper<S, T>(MapperClass) - Create reusable mapper instanceFor complete API documentation, see the Transformer Usage Guide.
We welcome contributions! Please see our Contributing Guide for details on:
This repository has automated code coverage protection enabled. All pull requests must maintain or improve the current code coverage percentage to be merged.
See the Coverage Protection Guide for details on how to ensure your PR passes coverage checks.
If you discover a security vulnerability, please follow our Security Policy for responsible disclosure.
om-data-mapper is distributed under the MIT license. See the LICENSE file in the root directory of the project for more information.
FAQs
High-performance TypeScript/JavaScript data mapper and validator with JIT compilation faster than class-transformer/class-validator, zero dependencies, TC39 decorators, drop-in replacement for class-transformer and class-validator
The npm package om-data-mapper receives a total of 2 weekly downloads. As such, om-data-mapper popularity was classified as not popular.
We found that om-data-mapper demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.