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
165 stars 23 forks source link

`@MappableClass(ignoreNull: true)` ignoreNull, doesn't work after execution of `beforeEncode()` method from applied Hook. #233

Closed RohanSenguptaMantisPro closed 2 months ago

RohanSenguptaMantisPro commented 2 months ago
@MappableClass(ignoreNull: true)
class SearchUserQueryParams with SearchUserQueryParamsMappable {
  const SearchUserQueryParams({
    this.page,
    this.limit,
    this.country,
    this.accountStatus,
    this.field,
    this.query,
  });

  final int? page;
  final int? limit;
  @MappableField(hook: CustomEmptyStringToNullHook())
  final String? country;
  final AccountStatusType? accountStatus;
  @MappableField(hook: NoneToNullEnumHook())
  final SearchOptionType? field;
  @MappableField(hook: CustomEmptyStringToNullHook())
  final String? query;
// Other updateable fields
}

Problem :

Here, ignoreNull works in normal conditions, but when used with hooks , after the hooks are executed and converted empty strings to Null , then when used toMap() it doesn't ignore the Null values which were converted by the CustomEmptyStringToNullHook.

hook implementation :

class CustomEmptyStringToNullHook extends MappingHook {
  const CustomEmptyStringToNullHook();

  @override
  Object? beforeEncode(Object? value) {
    // debugPrint('------------ [NullableHook] : working. $value : ');
    return (value is String && value.isEmpty) ? null : value;
  }
}  

They are not working together.

So similarly as @schultek mentioned : ( quoted below )

There are multiple ways this could be implemented using hooks.

One way could work like this:


@MappableClass(hook: RemoveIgnoredFields())
class Person {
  final String? name;
  // Add this hook to every field that should be ignored if null
  @MappableClass(hook: IgnoreNull())
  final String? age;

  Person(this.name, this.age);
}

class IgnoreNull extends MappingHook {
  const IgnoreNull();

  @override
  Object? afterEncode(Object? value) {
    if (value == null) {
      return _ignore;
    }
    return value;
  }
}

class RemoveIgnoredFields extends MappingHook {
  const RemoveIgnoredFields();

  @override
  Object? afterEncode(Object? value) {
    if (value is Map) {
       return {...value}..removeWhere((_, v) => v == _ignore);
    }
    return value;
  }
}

final _ignore = Object();

_Originally posted by @schultek in https://github.com/schultek/dart_mappable/issues/110#issuecomment-1644629076_

This is the working solution now, using customHooks to replicate the functionality of ignoreNull:true

class IgnoreNullClassHook extends MappingHook {
  const IgnoreNullClassHook();

  @override
  Object? afterEncode(Object? value) {
    // debugPrint('------------ [IgnoreNullClassHook] : working. $value : ');

    if (value is Map) {
      return value
        ..removeWhere((_, v) {
          return (v == null);
        });
    }
    return value;
  }
}

applying this hook to the SearchUserQueryParams class.

schultek commented 2 months ago

Yes, the null check is done before the hook is applied. Doing the custom hook as you showed is the best way.

I also don't plan to change this, as it would affect more things and is difficult to do. Especially since there is already a good solution with the hook.

RohanSenguptaMantisPro commented 2 months ago

Indeed, using Custom Hooks works well. I just needed to figure out the order of executions. It would be great if this information were added to the documentation under the Annotation Properties section.

Thanks for the review.

schultek commented 2 months ago

Good idea, I will update the docs