schultek / dart_mappable

Improved json serialization and data classes with full support for generics, inheritance, customization and more.
https://pub.dev/packages/dart_mappable
MIT License
135 stars 20 forks source link

Added support for per-field custom mappers #169

Closed suhailhijry closed 4 months ago

suhailhijry commented 4 months ago

The changes allow for per-field custom mappers, this is helpful in the case someone (like me) needs to use multiple mappers for fields of the same type.

An example of this is strict APIs with different date and time formats that can exist in the same model. Below you'll find an example of how it can be used.

import 'package:dart_mappable/dart_mappable.dart';
import 'package:intl/intl.dart';

final _dateFormat = DateFormat('yyyy-MM-dd');
final _dateWithTimeFormat = DateFormat('yyyy-MM-dd HH:mm');

class DateMapper extends SimpleMapper<DateTime> {
  const DateMapper();

  @override
  DateTime decode(dynamic value) {
    return _dateFormat.parse(value as String);
  }

  @override
  dynamic encode(DateTime self) {
    return _dateFormat.format(self);
  }
}

class DateWithTimeMapper extends SimpleMapper<DateTime> {
  const DateWithTimeMapper();

  @override
  DateTime decode(dynamic value) {
    return _dateWithTimeFormat.parse(value as String);
  }

  @override
  dynamic encode(DateTime self) {
    return _dateWithTimeFormat.format(self);
  }
}

@MappableClass()
class MyClass with MyClassMappable {
  @MappableField(customMapper: DateMapper())
  final DateTime dateOnly;

  @MappableField(customMapper: DateWithTimeMapper())
  final DateTime dateWithTime;

  const MyClass({
    required this.dateOnly,
    required this.dateWithTime,
  });
}

I also pumped both package versions to 4.2.1.

Thanks for the amazing package.

schultek commented 4 months ago

Hi, I very much appreciate your effort and will to contribute. Sadly I don't think this is the way I want to go about this problem.

The package has getting massively bigger over time to a point where its really hard to maintain and by now I have a rather strict policy on adding more stuff that increases the api surface of the package. Additionally here I think adding field-specific mappers is not even needed as this can already be done using MappingHooks (see below).

So sadly I will close this PR. I can understand it will be frustrating with the work you put in. As a general rule I always recommend opening an issue first to discuss ideas before putting any work in so things like this can be prevented.


To help you with this particular situation (supporting different date formats) here is a approach using MappingHooks:

import 'package:dart_mappable/dart_mappable.dart';
import 'package:intl/intl.dart';

class DateFormatHook extends MappingHook {
  const DateFormatHook(this.format);

  final String format;

  @override
  DateTime beforeDecode(Object? value) {
    return DateFormat(format).parse(value as String);
  }

  @override
  dynamic beforeEncode(Object? value) {
    return DateFormat(format).format(value as DateTime);
  }
}

@MappableClass()
class MyClass with MyClassMappable {
  @MappableField(hook: DateFormatHook('yyyy-MM-dd'))
  final DateTime dateOnly;

  @MappableField(hook: DateFormatHook('yyyy-MM-dd HH:mm'))
  final DateTime dateWithTime;

  const MyClass({
    required this.dateOnly,
    required this.dateWithTime,
  });
}

This works because when a custom hook returns an already decoded or encoded value, the normal de/encoding logic is skipped (in this case the default DateTime mapper is never involved). From the docs:

Tip: If the beforeDecode hook already returns an instance of the target type, the normal decoding logic is skipped. The same is true for the beforeEncode hook. This gives you the possibility to use different custom mappers on the same type, especially on a field-by-field level.

suhailhijry commented 4 months ago

No problem at all. Thanks for the detailed reply, will certainly look into hooks thoroughly next time.