automap

pub pub points License: MIT CI

A lightweight object mapper for Dart. It maps objects of different classes automatically, through JSON serialization, or manually, through custom mapping expressions.

Installation

Add it to your pubspec.yaml:

dependencies:
  automap: ^0.1.0

Usage

Auto map

Implement AutoMapperModel on the source type and register an expression that builds the target from the serialized source:

import 'package:automap/automap.dart';

class AutoSource implements AutoMapperModel {
  const AutoSource(this.x);

  final int x;

  @override
  Map<String, dynamic> toAutoJson() => {'x': x};
}

class AutoTarget {
  const AutoTarget(this.x);

  final int x;

  static AutoTarget fromAutoJson(Map<String, dynamic> json) =>
      AutoTarget(json['x'] as int);
}

void main() {
  AutoMapper.I.addAutoMap<AutoSource, AutoTarget>(AutoTarget.fromAutoJson);
  final target = AutoMapper.I.map<AutoSource, AutoTarget>(const AutoSource(5));
  print(target.x); // 5
}

Manual map

For full control, register a manual map expression:

class ManualSource {
  const ManualSource(this.x);

  final int x;
}

class ManualTarget {
  const ManualTarget(this.x);

  final int x;
}

void main() {
  AutoMapper.I.addManualMap<ManualSource, ManualTarget>(
    (source, mapper, params) => ManualTarget(source.x),
  );
  final target = AutoMapper.I.map<ManualSource, ManualTarget>(
    const ManualSource(5),
  );
  print(target.x); // 5
}

Mapping parameters

Manual map expressions receive an optional params map:

AutoMapper.I.addManualMap<Person, PersonDto>(
  (source, mapper, params) => PersonDto(
    '${source.name} ${params['suffix']}',
    source.age + (params['bonus'] as int),
  ),
);

final dto = AutoMapper.I.map<Person, PersonDto>(
  const Person('Ada', 36),
  {'suffix': 'Lovelace', 'bonus': 1},
);

Nested mapping

The expression also receives the calling AutoMapper instance, so child members can be mapped with registered maps:

AutoMapper.I
  ..addAutoMap<Person, PersonDto>(PersonDto.fromAutoJson)
  ..addManualMap<Team, TeamDto>(
    (source, mapper, params) =>
        TeamDto(mapper.map<Person, PersonDto>(source.leader)),
  );

final teamDto = AutoMapper.I.map<Team, TeamDto>(const Team(Person('Ada', 36)));

Error handling

Every exception thrown by this package extends the sealed AutoMapperException class, so you can handle all failure modes exhaustively with pattern matching:

try {
  final dto = AutoMapper.I.map<Person, PersonDto>(person);
} on AutoMapperException catch (e) {
  switch (e) {
    case MapException(:final cause, :final stackTrace):
      print('The mapping expression failed: $cause');
    case MapDoesNotExistException(:final source, :final destination):
      print('No map registered for $source -> $destination');
    case MapDuplicateException(:final source, :final destination):
      print('A map for $source -> $destination already exists');
  }
}

Note that exceptions thrown inside your own mapping expressions are wrapped in a MapException that keeps the original cause and stack trace, while programming errors (Error instances, such as type errors) propagate untouched so bugs are never masked.

Limitations

This package does not use reflection or code generation. Automatic mapping works through the AutoMapperModel contract (toAutoJson plus a constructor-like expression), so mapping always goes through an intermediate JSON-like map.

License

MIT

Libraries

automap