OmniMapper

pub package License

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., ModelEntity, DTOViewModel), 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:

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;
  // ...
}

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),
  ],
)

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.

Libraries

omni_mapper