google/built_value.dart

Serialization does not serialize derived values

hambergerpls opened this issue · 2 comments

Hi, I'm trying to serialize a class that derives values from its members. One example is given below where a response contains a list of items and the total of the items. When it is serialized, the field that contains the total of the items is not included in the result. I had to manually add a code to serialize the total to the generated serializer class.

See the example code below.

abstract class PaginatedResponseData<T extends BaseModel>
    implements
        Built<PaginatedResponseData<T>, PaginatedResponseDataBuilder<T>> {
  BuiltList<T>? get items;
  @memoized
  int get total => (items ?? BuiltList([])).length;
  int? get page;
  int? get size;
  @memoized
  int? get pages => (total / (size?.toInt() ?? 50)).ceil();
  int? get next_page => (page ?? 1) < pages! ? (page ?? 1) + 1 : null;
  int? get previous_page => (page ?? 1) > 1 ? (page ?? 1) - 1 : null;

  PaginatedResponseData._();
  factory PaginatedResponseData(
          [void Function(PaginatedResponseDataBuilder<T>) updates]) =
      _$PaginatedResponseData<T>;

  Map<String, Object?>? toJson() {
    final specifiedType = FullType(PaginatedResponseData, [FullType(T)]);
    return serializers.serialize(this, specifiedType: specifiedType)
        as Map<String, Object?>?;
  }

  factory PaginatedResponseData.fromJson(Map<String, dynamic> json) {
    final specifiedType = FullType(PaginatedResponseData, [FullType(T)]);
    return serializers.deserialize(
      json,
      specifiedType: specifiedType,
    ) as PaginatedResponseData<T>;
  }

  static Serializer<PaginatedResponseData> get serializer =>
      _$paginatedResponseDataSerializer;
}

class _$PaginatedResponseDataSerializer
    implements StructuredSerializer<PaginatedResponseData<BaseModel>> {
  @override
  final Iterable<Type> types = const [
    PaginatedResponseData,
    _$PaginatedResponseData
  ];
  @override
  final String wireName = 'PaginatedResponseData';

  @override
  Iterable<Object?> serialize(
      Serializers serializers, PaginatedResponseData<BaseModel> object,
      {FullType specifiedType = FullType.unspecified}) {
    final isUnderspecified =
        specifiedType.isUnspecified || specifiedType.parameters.isEmpty;
    if (!isUnderspecified) serializers.expectBuilder(specifiedType);
    final parameterT =
        isUnderspecified ? FullType.object : specifiedType.parameters[0];

    final result = <Object?>[];
    Object? value;
    value = object.items;
    if (value != null) {
      result
        ..add('items')
        ..add(serializers.serialize(value,
            specifiedType: new FullType(BuiltList, [parameterT])));
    }
    // ========= Added manually =========
    value = object.total;
    if (value != null) {
      result
        ..add('total')
        ..add(serializers.serialize(value, specifiedType: const FullType(int)));
    }
    // ========= Added manually =========
    value = object.page;
    if (value != null) {
      result
        ..add('page')
        ..add(serializers.serialize(value, specifiedType: const FullType(int)));
    }
    value = object.size;
    if (value != null) {
      result
        ..add('size')
        ..add(serializers.serialize(value, specifiedType: const FullType(int)));
    }
    return result;
  }

I understand that we can create our own serializer to do this, but I think it would be better if there is an annotation to determine whether we want the derived values to be serialized. There is the @BuiltValueField annotation, but with serialize: true, it still does not serialize the derived values.

One way to do this would be to turn the getters into normal fields that are set with a builder finalizer hook.

https://pub.dev/documentation/built_value/latest/built_value/BuiltValueHook/finalizeBuilder.html

This is not quite equivalent, the computation will be on construction instead of lazily, and they end up as normal fields so they will appear on the builder even though setting them won't do anything because the hook will override them.

But maybe it works for your use case?

Thanks.

@dave26199 Yes, I think that is a much better solution for my use case. Thanks!