entity_mapper 0.5.0
entity_mapper: ^0.5.0 copied to clipboard
Lightweight code generator for Clean Architecture focused projects. Automatically creates type-safe Entity ↔ Model mapping methods with dart_mappable-style patterns.
// Comprehensive runnable example for the entity_mapper package.
//
// Run from the package root:
//
// dart run build_runner build
// dart run example/main.dart
//
// Each section below demonstrates one capability of the generator and prints
// the result so you can see entity ↔ model conversion in action.
import 'package:entity_mapper/entity_mapper.dart';
part 'main.entity_mapper.dart';
// ────────────────────────────────────────────────────────────────────────
// Domain entities — pure Dart, no annotations, no codegen.
// ────────────────────────────────────────────────────────────────────────
class User {
const User({required this.id, required this.name, required this.age});
final String id;
final String name;
final int age;
}
class Address {
const Address({required this.street, required this.city});
final String street;
final String city;
}
class UserWithAddress {
const UserWithAddress({
required this.id,
required this.name,
required this.address,
});
final String id;
final String name;
final Address address;
}
class UserWithAddresses {
const UserWithAddresses({required this.id, required this.addresses});
final String id;
final List<Address> addresses;
}
class UserMaybeAddress {
const UserMaybeAddress({required this.id, required this.name, this.address});
final String id;
final String name;
final Address? address;
}
class UserMaybeAddresses {
const UserMaybeAddresses({required this.id, this.addresses});
final String id;
final List<Address>? addresses;
}
class NullablePerson {
const NullablePerson({this.id, this.name, this.age});
final String? id;
final String? name;
final int? age;
}
/// Entity with only two fields. A model targeting this can declare extra
/// fields — entity_mapper passes `null` for required-nullable extras and lets
/// optional extras use their defaults.
class PartialUser {
const PartialUser({required this.id, required this.name});
final String id;
final String name;
}
// ────────────────────────────────────────────────────────────────────────
// Data models — annotated with @MapToEntity. The companion
// `main.entity_mapper.dart` file is generated by build_runner.
// ────────────────────────────────────────────────────────────────────────
@MapToEntity(User)
class UserModel with UserEntityMappable {
const UserModel({required this.id, required this.name, required this.age});
final String id;
final String name;
final int age;
}
@MapToEntity(Address)
class AddressModel with AddressEntityMappable {
const AddressModel({required this.street, required this.city});
final String street;
final String city;
}
@MapToEntity(UserWithAddress)
class UserWithAddressModel with UserWithAddressEntityMappable {
const UserWithAddressModel({
required this.id,
required this.name,
required this.address,
});
final String id;
final String name;
final AddressModel address; // non-list nested model
}
@MapToEntity(UserWithAddresses)
class UserWithAddressesModel with UserWithAddressesEntityMappable {
const UserWithAddressesModel({required this.id, required this.addresses});
final String id;
final List<AddressModel> addresses; // list of nested models
}
@MapToEntity(UserMaybeAddress)
class UserMaybeAddressModel with UserMaybeAddressEntityMappable {
const UserMaybeAddressModel({
required this.id,
required this.name,
this.address,
});
final String id;
final String name;
final AddressModel? address; // nullable nested model
}
@MapToEntity(UserMaybeAddresses)
class UserMaybeAddressesModel with UserMaybeAddressesEntityMappable {
const UserMaybeAddressesModel({required this.id, this.addresses});
final String id;
final List<AddressModel>? addresses; // nullable list of nested models
}
@MapToEntity(NullablePerson)
class NullablePersonModel with NullablePersonEntityMappable {
const NullablePersonModel({this.id, this.name, this.age});
final String? id;
final String? name;
final int? age;
}
/// Model with a required-nullable field that has no counterpart on the
/// `PartialUser` entity. The generator passes `null` for `extra` when
/// constructing the model from the entity. (A non-nullable required field
/// in the same position would fail at codegen.)
@MapToEntity(PartialUser)
class PartialUserExtraModel with PartialUserExtraEntityMappable {
const PartialUserExtraModel({
required this.id,
required this.name,
required this.extra,
});
final String id;
final String name;
final String? extra;
}
// ────────────────────────────────────────────────────────────────────────
// Demonstration
// ────────────────────────────────────────────────────────────────────────
void main() {
// 1. Primitives — entity → model → entity round-trip.
const user = User(id: 'u1', name: 'Alice', age: 30);
final userModel = UserEntityMapper.toModel(user);
print('1. Primitives');
print(' entity: id=${user.id} name=${user.name} age=${user.age}');
print(
' model: id=${userModel.id} name=${userModel.name} age=${userModel.age}',
);
// 2. Non-list nested model.
const userWithAddr = UserWithAddress(
id: 'u2',
name: 'Bob',
address: Address(street: 'Main', city: 'Metropolis'),
);
final uwaModel = UserWithAddressEntityMapper.toModel(userWithAddr);
print('\n2. Nested model (non-list)');
print(
' model.address is an AddressModel: ${uwaModel.address.runtimeType}',
);
print(
' address: street=${uwaModel.address.street} city=${uwaModel.address.city}',
);
// 3. List of nested models.
const userWithList = UserWithAddresses(
id: 'u3',
addresses: [
Address(street: 'Main', city: 'A'),
Address(street: 'Oak', city: 'B'),
],
);
final uwlModel = UserWithAddressesEntityMapper.toModel(userWithList);
print('\n3. List of nested models');
print(
' model has ${uwlModel.addresses.length} AddressModels: '
'${uwlModel.addresses.map((a) => "${a.street}/${a.city}").join(", ")}',
);
// 4. Nullable nested model — both populated and null.
const filledNested = UserMaybeAddress(
id: 'u4',
name: 'Carol',
address: Address(street: 'Pine', city: 'Gotham'),
);
const emptyNested = UserMaybeAddress(id: 'u5', name: 'Dave');
final filledModel = UserMaybeAddressEntityMapper.toModel(filledNested);
final emptyModel = UserMaybeAddressEntityMapper.toModel(emptyNested);
print('\n4. Nullable nested model');
print(' populated → model.address.street = ${filledModel.address?.street}');
print(' null → model.address is null: ${emptyModel.address == null}');
// 5. Nullable list of nested models.
const filledList = UserMaybeAddresses(
id: 'u6',
addresses: [Address(street: 'Elm', city: 'X')],
);
const noList = UserMaybeAddresses(id: 'u7');
final filledListModel = UserMaybeAddressesEntityMapper.toModel(filledList);
final noListModel = UserMaybeAddressesEntityMapper.toModel(noList);
print('\n5. Nullable list of nested models');
print(
' populated → ${filledListModel.addresses?.length} item(s) of type '
'${filledListModel.addresses?.first.runtimeType}',
);
print(' null → addresses is null: ${noListModel.addresses == null}');
// 6. Fully-nullable primitives.
const allNull = NullablePerson();
const someNull = NullablePerson(id: 'p1', age: 42);
final allNullModel = NullablePersonEntityMapper.toModel(allNull);
final someNullModel = NullablePersonEntityMapper.toModel(someNull);
print('\n6. Nullable primitives');
print(
' all-null → id=${allNullModel.id} name=${allNullModel.name} '
'age=${allNullModel.age}',
);
print(
' partial → id=${someNullModel.id} name=${someNullModel.name} '
'age=${someNullModel.age}',
);
// 7. Required-nullable model field with no entity counterpart.
// `PartialUser` has only id+name; `PartialUserExtraModel` declares an
// extra `required` nullable field. The generator inserts `null` so the
// required keyword on the model is satisfied.
const partial = PartialUser(id: 'u8', name: 'Eve');
final partialModel = PartialUserExtraEntityMapper.toModel(partial);
print('\n7. Required-nullable model field absent from entity');
print(' model.extra = ${partialModel.extra} (filled by the generator)');
// 8. The `toEntity()` mixin method matches the static `toEntity` call.
const m = UserModel(id: 'u9', name: 'Frank', age: 50);
final viaMixin = m.toEntity();
final viaStatic = UserEntityMapper.toEntity(m);
print('\n8. Mixin vs static toEntity');
print(' viaMixin.id == viaStatic.id: ${viaMixin.id == viaStatic.id}');
print(
' mappers are singletons: '
'${identical(UserEntityMapper.ensureInitialized(), UserEntityMapper.ensureInitialized())}',
);
}