omni_mapper 0.5.2
omni_mapper: ^0.5.2 copied to clipboard
Annotations for the OmniMapper code generator. Automatically generates type-safe object-to-object mapping code between DTOs, Models, and Entities.
OmniMapper #
A powerful, highly customizable code-generation library for Dart and Flutter that automatically generates object-to-object mapping code.
OmniMapper eliminates the boilerplate of manually writing conversion methods between your application layers (e.g., Model → Entity, DTO → ViewModel), keeping your codebase clean and reducing bugs.
Think of it as the AutoMapper/MapStruct for the Dart ecosystem.
Installation #
Add the following to your pubspec.yaml:
dependencies:
omni_mapper: ^0.1.0
dev_dependencies:
build_runner: ^2.4.0
omni_mapper_generator: ^0.1.0
Then run:
dart pub get
Quick Start #
1. Annotate your class #
import 'package:omni_mapper/omni_mapper.dart';
part 'user_model.g.dart';
class UserEntity {
final int id;
final String name;
UserEntity({required this.id, required this.name});
}
@OmniMapper(target: UserEntity)
class UserModel {
final int id;
final String name;
UserModel({required this.id, required this.name});
}
2. Run the generator #
dart run build_runner build -d
3. Use the generated code #
final model = UserModel(id: 1, name: 'John');
final entity = model.toEntity(); // Automatically mapped!
Mapping Approaches #
OmniMapper supports three mapping strategies to fit your architecture:
Approach A: Abstract Class (Centralized Mapper) #
@OmniMapper()
abstract class UserMapper {
UserEntity toEntity(UserModel model);
}
// Generates: class UserMapperImpl extends UserMapper { ... }
Approach B: Extension TO Target #
@OmniMapper(target: UserEntity)
class UserModel { ... }
// Generates: extension on UserModel { UserEntity toEntity() { ... } }
Approach C: Extension FROM Source #
@OmniMapper(from: UserEntity, methodName: 'toModel')
class UserModel { ... }
// Generates: extension on UserEntity { UserModel toModel() { ... } }
Multiple Mappings #
Map a single class to multiple targets using @OmniMappers:
@OmniMappers([
OmniMapper(target: UserEntity),
OmniMapper(from: UserEntity, methodName: 'toModel'),
])
class UserModel { ... }
Advanced Features #
Custom Field Mapping #
When source and target have different property names, you have two options depending on your control over the classes:
Option 1: @OmniField (Recommended when you own the class)
Place @OmniField directly on the property. This is the most ergonomic approach because the mapping rule stays right next to the variable declaration.
@OmniMapper(target: UserEntity)
class UserModel {
@OmniField(name: 'id') // Maps 'userId' to 'id'
final int userId;
// ...
}
Option 2: mappings with MappingRule (Recommended for external classes)
If you cannot modify the class (e.g., it belongs to a third-party package or is generated code), or if you need advanced custom expressions, use MappingRule inside @OmniMapper.
@OmniMapper(
target: UserEntity,
mappings: [
MappingRule('id', source: 'userId'), // source.userId → target.id
],
)
class UserModel {
final int userId;
// ...
}
Default Values #
Provide fallback values for target fields missing in the source:
Option 1: @OmniField
class UserModel {
@OmniField(defaultValue: '"active"')
final String status;
}
Option 2: mappings
@OmniMapper(
target: UserEntity,
mappings: [
MappingRule('status', defaultValue: '"active"'),
MappingRule('createdAt', defaultValue: 'DateTime.now()'),
],
)
Custom Type Converters #
Handle type mismatches with OmniConverter:
class DateTimeStringConverter extends OmniConverter<String, DateTime> {
const DateTimeStringConverter();
@override
DateTime convert(String source) => DateTime.parse(source);
}
@OmniMapper(
target: UserEntity,
converters: [DateTimeStringConverter],
)
class UserModel {
final String createdAt; // String → DateTime automatically
}
List Generation #
Automatically generates an extension on Iterable<Source> for batch mapping:
final models = [model1, model2, model3];
final entities = models.toEntityList(); // Returns List<UserEntity>
Enabled by default. Disable with
generateListMapper: false.
In-Place Updates #
Generates a method to update an existing target instance without creating a new one:
final existingEntity = UserEntity(id: 1, name: 'Old');
formModel.updateUserEntity(existingEntity);
// existingEntity.name is now updated — same object in memory!
Disabled by default. Enable with
generateUpdateMethod: true. Works with mutable fields only (non-final).
Ignoring Fields #
Skip specific fields during mapping:
Option 1: @OmniField
class UserModel {
@OmniField(ignore: true)
final String passwordHash;
}
Option 2: mappings
@OmniMapper(
target: UserEntity,
mappings: [
MappingRule('passwordHash', ignore: true),
],
)
Polymorphic Mapping (@SubclassMapping) #
Handle subclasses and inheritance dynamically based on the runtime type of the source object:
@OmniMapper(
target: BaseEntity,
subclasses: [
SubclassMapping(source: AdminModel, target: AdminEntity),
SubclassMapping(source: GuestModel, target: GuestEntity),
]
)
class BaseModel { ... }
Dependency Injection & Composition (uses) #
Reference existing mappers to handle complex nested fields automatically. The generated implementation will ask for these mappers in its constructor:
@OmniMapper()
abstract class AddressMapper {
AddressEntity toEntity(AddressModel model);
}
@OmniMapper(uses: [AddressMapper])
abstract class UserMapper {
final AddressMapper addressMapper;
UserMapper(this.addressMapper);
UserEntity toEntity(UserModel model);
}
Custom Field Expressions (MappingRule.custom) #
Use pure Dart code for extreme flexibility when mapping fields:
@OmniMapper(
target: UserEntity,
mappings: [
MappingRule('fullName', custom: 'firstName + " " + lastName'),
],
)
class UserModel { ... }
Multiple Sources Mapping #
Combine multiple source objects into a single target object by defining a centralized mapper:
@OmniMapper()
abstract class UserProfileMapper {
UserProfile toProfile(User user, Address address);
}
Automatic Enum Mapping #
Enums are mapped automatically if their values match by name. No additional configuration is required.
Deep Auto-Flattening #
Nested objects are automatically flattened if the target fields match the nested structure (e.g., source.user.address.street maps to target.street).
Strict Mode & IgnoreIfNull #
Enforce complete mappings and support PATCH-like updates:
@OmniMapper(
target: UserEntity,
strictMode: true, // Throws an error if any target field is unmapped
ignoreIfNull: true, // Null source fields won't overwrite target fields (useful for updates)
)
class UserModel { ... }
Recommended build.yaml #
To suppress lint warnings on generated files, add this to your project's build.yaml:
targets:
$default:
builders:
source_gen|combining_builder:
options:
ignore_for_file:
- type=lint
- coverage:ignore-file
Running the Generator #
# One-time build
dart run build_runner build -d
# Watch mode (rebuilds on file changes)
dart run build_runner watch -d
Contributing #
Contributions are welcome! Please file issues and pull requests on the GitHub repository.
License #
This project is licensed under the BSD 3-Clause License — see the LICENSE file for details.