Are CustomMappers global state?
Closed this issue · 2 comments
I have the following code:
import 'package:dart_mappable/dart_mappable.dart';
part 'model.mapper.dart';
/// This class should use the configured format for DateTime encoding.
/// The default is "Formatted ISO String in UTC Timezone"
@MappableClass()
class DefaultMapperModel with DefaultMapperModelMappable {
final DateTime dateTime;
DefaultMapperModel({required this.dateTime});
}
@MappableClass(includeCustomMappers: [_CustomDateTimeMapper()])
class CustomMapperModel with CustomMapperModelMappable {
final DateTime dateTime;
CustomMapperModel({required this.dateTime});
}
class _CustomDateTimeMapper extends SimpleMapper<DateTime> {
const _CustomDateTimeMapper();
@override
DateTime decode(dynamic value) {
return DateTime.parse(value as String);
}
@override
dynamic encode(DateTime self) {
// some custom formatting, the format does not matter
// yyyyMMdd HH:mm:ss
return '${self.year}${_zeroPad(self.month)}${_zeroPad(self.day)} ${_zeroPad(self.hour)}:${_zeroPad(self.minute)}:${_zeroPad(self.second)}';
}
String _zeroPad(int value) {
return value.toString().padLeft(2, '0');
}
}
If you use it like this:
import 'package:dart_mapable_test/model.dart';
void main(List<String> arguments) {
final dt = DateTime.utc(2024, 5, 17, 9, 8, 10, 11, 12);
print(dt.toIso8601String());
final a = DefaultMapperModel(dateTime: dt);
final b = CustomMapperModel(dateTime: dt);
print(a.toJson());
print(b.toJson());
print(a.toJson()); // a again
}
You get the following output:
2024-05-17T09:08:10.011012Z
{"dateTime":"2024-05-17T09:08:10.011012Z"}
{"dateTime":"20240517 09:08:10"}
{"dateTime":"20240517 09:08:10"}
Is this intended behavior? My expectation was that the DateTime
values of DefaultMapperModel
are always converted to ISO-8601 (following the config) and that only CustomMapperModel
uses my custom format mapper. But the call to toJson() on a CustomMapperModel
seems to alter some sort of global state.
The documentation hints that the annotation is an alternative way to MapperContainer.globals.use(CustomMapper())
, and in fact, if the annotation is used, the model's ensureInitialized()
method adds the mapper to the global container.
-@MappableClass(includeCustomMappers: [])
+@MappableClass(includeCustomMappers: [_CustomDateTimeMapper()])
class CustomMapperModel with CustomMapperModelMappable {
final DateTime dateTime;
diff --git a/lib/model.mapper.dart b/lib/model.mapper.dart
class CustomMapperModelMapper extends ClassMapperBase<CustomMapperModel> {
static CustomMapperModelMapper ensureInitialized() {
if (_instance == null) {
MapperContainer.globals.use(_instance = CustomMapperModelMapper._());
- MapperContainer.globals.useAll([]);
+ MapperContainer.globals.useAll([_CustomDateTimeMapper()]);
}
return _instance!;
}
So the behavior is intentional and my expectation was wrong.
Still, I find it kinda misleading that the annotation alters the global container, since one could argue that the scope of the annotation should only be the annotated class itself.
Is there a way to apply a CustomerMapper only to the class and not to others? Or do I have to use hooks on the fields?
rtfm ... Hooks are the way to go, just as the documentation says ;)
This gives you the possibility to use different custom mappers on the same type, especially on a field-by-field level.