Skip to content

[Bug] Code Generation Attempt Unintendedly Triggered #1303

Open

Description

@jlambright

Given the abstract class (as well as a mixin version of the code) any sub-classes seem to trigger code generation attempts. However, I'm creating a wrapper class that consumes Built generated classes. This seems like buggy behavior. Either that, or it highlights the need for some kind of @built_ignore() annotation.

abstract class BuiltJsonSerializable<T extends Built<T, V>,
    V extends Builder<T, V>> {
  Serializer<T> get serializer;

  Map<String, dynamic> toJsonMap() => jsonDecode(toJsonString());

  String toJsonString() => standardSerializers.toJson<T>(serializer, toModel());

  T? toModel();

  T? modelFromJsonString(String serialized) {
    try {
      T? model = standardSerializers.fromJson<T>(serializer, serialized);
      return model;
    } on DeserializationError catch (e) {
      Get.log('[${T.toString()} DESERIALIZATION ERROR] ${e.toString()}');
      return null;
    }
  }
}

Activity

  1. davidmorgan commented on Feb 22, 2024

    @davidmorgan
    Collaborator

    The trigger for codegen is supposed to be implementing Built.

    Could you please give an example of a class that triggers codegen that you expect to not trigger codegen?

    Thanks :)

  2. jlambright commented on Feb 22, 2024

    @jlambright
    Author

    Below you'll see one of the sub-classes. When I run dart run build_runner build (as I do have some codegen in this project), it throws the following errors.

    [SEVERE] built_value_generator:built_value on lib/data/wrappers/wrappers.dart:
    Error in BuiltValueGenerator for /dsk360_command/lib/data/wrappers/wrappers.dart.
    Please make the following changes to use built_value serialization:
    <I'm skipping a bunch of errors since they're almost all the same.>
    10. Declare TicketWrapper.serializer as: static Serializer<TicketWrapper> get serializer => _$ticketWrapperSerializer; got @override Serializer<TicketModel> get serializer => _$ticketWrapperSerializer;
    
    part of 'wrappers.dart';
    
    TicketWrapper ticketFromJson(String str) => TicketWrapper.fromJsonString(str);
    TicketModel? ticketToModel(TicketWrapper data) => data.toModel();
    String ticketToJson(TicketWrapper data) => standardSerializers
        .toJson<TicketModel>(TicketModel.serializer, data.toModel());
    
    List<TicketWrapper> ticketsFromModelList(List<TicketModel>? tickets) {
      if (tickets == null) {
        return [];
      } else {
        return tickets
            .asMap()
            .entries
            .map((entry) => TicketWrapper.fromModel(
                ticketModel: entry.value, number: entry.key))
            .toList();
      }
    }
    
    Map<String, TicketWrapper> ticketsMapFromTicketModelList(
        List<TicketModel>? tickets) {
      if (tickets == null) {
        return <String, TicketWrapper>{};
      } else {
        return Map.fromEntries(
            tickets.map<MapEntry<String, TicketWrapper>>((ticketModel) {
          final TicketWrapper ticket =
              TicketWrapper.fromModel(ticketModel: ticketModel);
          return MapEntry(ticket.uuid, ticket);
        }));
      }
    }
    
    Map<String, TicketWrapper> ticketsMapFromList(List<TicketWrapper>? tickets) =>
        {for (TicketWrapper ticket in tickets ?? []) ticket.uuid: ticket};
    
    List<TicketModel?> ticketWrapperListToModel(
        List<TicketWrapper> ticketWrappers) {
      return ticketWrappers
          .map((ticketWrapper) => ticketWrapper.toModel())
          .toList();
    }
    
    List<Map<String, dynamic>> toJsonMapTicketList(List<TicketWrapper> tickets) =>
        tickets.map((ticket) => ticket.toJsonMap()).toList();
    
    class TicketWrapper
        extends BuiltJsonSerializable<TicketModel, TicketModelBuilder> {
      @override
      Serializer<TicketModel> get serializer => TicketModel.serializer;
    
      late final int dataHash;
    
      late final String uuid;
      late final DateTime lastModified;
      late final ContactWrapper contact;
      late final TicketStatusEnum status;
      late final Map<String, NoteWrapper> notes;
      List<NoteWrapper> get notesList => notes.values.toList();
      List<String> get notesUuids => notes.keys.toList();
    
      late final Map<String, TagWrapper> tags;
      List<TagWrapper> get tagList => tags.values.toList();
      List<String> get tagUuids => tags.keys.toList();
    
      List<String> get tagNames => [for (TagWrapper tag in tagList) tag.name];
      bool hasTagByUuid(String tagUuid) => tagUuids.contains(tagUuid);
    
      bool get isMaintenance => tagNames.contains("Maintenance");
      bool get isSecurity => tagNames.contains("Security");
    
      bool get hasImageUrls => contact.hasLatestUrls;
      String? get latestImageUrl => contact.latestImageUrl;
      List<String>? get latestImageUrls => contact.latestImageUrls;
      BoundingSphereWrapper get boundingSphere => contact.boundingSphere;
    
      @override
      int get hashCode => toJsonString().hashCode;
    
      TicketWrapper(
          {required this.uuid,
          required this.contact,
          DateTime? lastModified,
          this.status = TicketStatusEnum.unknown,
          this.notes = const {},
          this.tags = const {}})
          : lastModified = lastModified ?? epochTime;
    
      TicketWrapper.fromModel({TicketModel? ticketModel, int? number}) {
        uuid = ticketModel?.uuid ?? _uuid.v4();
        lastModified = ticketModel?.lastModified ?? epochTime;
        contact = ContactWrapper.fromModel(ticketModel?.contact);
        status = TicketStatusEnum.fromDkpQualityEnum(ticketModel?.status);
        tags = fromTagModelList(ticketModel?.tags.toList());
        notes = fromNoteModelList(ticketModel?.notes?.toList());
      }
    
      TicketWrapper.createEmptyWrapper() {
        uuid = _uuid.v4();
        lastModified = epochTime;
        contact = ContactWrapper.createEmptyWrapper();
        status = TicketStatusEnum.unknown;
        tags = {};
        notes = {};
      }
    
      TicketWrapper.fromJsonString(String serialized) {
        try {
          TicketModel? ticketModel =
              standardSerializers.fromJson<TicketModel>(serializer, serialized);
          TicketWrapper.fromModel(ticketModel: ticketModel);
        } on AssertionError {
          throw ArgumentError.notNull(serialized);
        }
      }
    
      @override
      Map<String, dynamic> toJsonMap() => jsonDecode(toJsonString());
    
      @override
      TicketModel? toModel() {
        return modelFromJsonString(jsonEncode({
          "uuid": uuid,
          "lastModified": lastModified,
          "contact": contact.toModel(),
          "status": status == TicketStatusEnum.unknown ? "Open" : status.value,
          "notes": noteWrapperListToModel(notesList),
          "tags": tagWrapperListToModel(tagList)
        }));
      }
    
      @override
      bool operator ==(Object other) {
        return super.hashCode == other.hashCode;
      }
    }
  3. davidmorgan commented on Feb 22, 2024

    @davidmorgan
    Collaborator

    Oh, I see the problem.

    The serializer codegen triggers for a class if any of its supertypes starts with Built, and there is a field called serializer.

    I have no idea why it's like this, the value type generation only triggers if the interface is exactly Built.

    That looks like it should just be fixed, until then you can work around by renaming your class to anything that doesn't start with Built or renaming serializer. (Whoops).

  4. self-assigned this
    on Feb 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions