ArgumentError (Invalid argument: Instance of ...) when converting to Json with class as a nested list

107 views Asked by At

I have a class called Groups that has a property that contains a list of Members.

To send this to Firebase, I have a GroupDto and MemberDto class that converts these values to and from the domain, and to and from JSON.

I noticed a recent issue where every time I try to save a group update, I'm getting this error that says:

ArgumentError (Invalid argument: Instance of 'MemberDto') enter image description here

It seems to happen as part of the method that converts the GroupDto to Json, when I look at the value that's being mapped, it seems to be storing those as a list of MemberDto instead of a list of mapped values: enter image description here

I have this list of MemberDtos set to also convert to Json, with explicitToJson equal to true.

Any idea what could be causing this?

Here are my classes for GroupDto and MemberDto:

@JsonSerializable(includeIfNull: false)
class GroupDto {
  @JsonKey(includeFromJson: false, includeToJson: false)
  final String? id;
  final String title;
  final String? description;
  final List<MemberDto>? members;

  GroupDto({
    this.id,
    required this.title,
    this.description,
    this.members,
  });

  factory GroupDto.fromDomain(Group group) {
    return GroupDto(
      id: group.id,
      title: group.title.getOrCrash(),
      description: group.description,
      members: group.members.isNotEmpty
          ? group.members.map((e) => MemberDto.fromDomain(e)).toList()
          : null,
    );
  }

  Group toDomain() {
    return Group(
      id: id,
      title: ItemTitle(title),
      description: description,
      members: members != null
          ? members!.map((dto) => dto.toDomain()).toList()
          : const [],
    );
  }

  factory GroupDto.fromFirestore(
          Map<String, dynamic> json, String documentId) =>
      _$GroupDtoFromJson(json).copyWith(id: documentId);

  factory GroupDto.fromJson(Map<String, dynamic> json) =>
      _$GroupDtoFromJson(json);

  Map<String, dynamic> toJson() => _$GroupDtoToJson(this);

  GroupDto copyWith({
    String? id,
    String? title,
    String? description,
    List<MemberDto>? members,
  }) {
    return GroupDto(
      id: id ?? this.id,
      title: title ?? this.title,
      description: description ?? this.description,
      members: members ?? this.members,
    );
  }
}

@JsonSerializable(includeIfNull: false, explicitToJson: true)
class MemberDto {
  final String userId;
  final MembershipStatus status;

  MemberDto({
    required this.userId,
    required this.status,
  });

  factory MemberDto.fromDomain(Member membership) {
    return MemberDto(
      userId: membership.userId,
      status: membership.status,
    );
  }

  Member toDomain() {
    return Member(
      userId: userId,
      status: status,
    );
  }

  factory MemberDto.fromJson(Map<String, dynamic> json) =>
      _$MemberDtoFromJson(json);

  Map<String, dynamic> toJson() => _$MemberDtoToJson(this);
}

And here is the auto-generated JsonSerializable code:

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'group_dto.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

GroupDto _$GroupDtoFromJson(Map<String, dynamic> json) => GroupDto(
      title: json['title'] as String,
      description: json['description'] as String?,
      members: (json['members'] as List<dynamic>?)
          ?.map((e) => MemberDto.fromJson(e as Map<String, dynamic>))
          .toList(),
    );

Map<String, dynamic> _$GroupDtoToJson(GroupDto instance) {
  final val = <String, dynamic>{
    'title': instance.title,
  };

  void writeNotNull(String key, dynamic value) {
    if (value != null) {
      val[key] = value;
    }
  }

  writeNotNull('description', instance.description);
  writeNotNull('members', instance.members);
  return val;
}

MemberDto _$MemberDtoFromJson(Map<String, dynamic> json) => MemberDto(
      userId: json['userId'] as String,
      status: $enumDecode(_$MembershipStatusEnumMap, json['status']),
    );

Map<String, dynamic> _$MemberDtoToJson(MemberDto instance) => <String, dynamic>{
      'userId': instance.userId,
      'status': _$MembershipStatusEnumMap[instance.status]!,
    };

const _$MembershipStatusEnumMap = {
  MembershipStatus.active: 'active',
  MembershipStatus.invited: 'invited',
  MembershipStatus.notYetSent: 'notYetSent',
  MembershipStatus.rejected: 'rejected',
  MembershipStatus.left: 'left',
  MembershipStatus.removed: 'removed',
  MembershipStatus.none: 'none',
};
1

There are 1 answers

3
Cabdirashiid On BEST ANSWER

To get the list of mapped values from the instance try this example: Try getting the list of mapped values by mapping each instance to json then to list.

Example

An instance of GroupDto

  GroupDto groups = GroupDto.fromDomain(
    Group(
      title: 'first group',
      members: [
        Member(userId: 'first user', status: MembershipStatus.active),
        Member(userId: 'second user', status: MembershipStatus.active),
      ],
    ),
  );
  • Then call the toJson() on groups, it should return a map
  Map<String, dynamic> groupsMap = groups.toJson();
  • Then get the list of instances of MemberDto from the map
  List<MemberDto> members = groupsMap['members'];
  • Then iterate through the list returning each with the toJson() method, then return them as list with toList()
  List<Map<String, dynamic>> membersMapList = members
                .map((e) => e.toJson())
                .toList();

That should give you a map of MembersDto


Edit

The problem is with the nested List<MemberDto>? object.

If you want the the package to do it for you, Change the explicitToJson option of the JsonSerializable annotation in the parent class to true.

{bool? explicitToJson}

If true, generated toJson methods will explicitly call toJson on nested objects.

@JsonSerializable(includeIfNull: false, explicitToJson: true)
class GroupDto {
...

Thanks to this answer for pointing it out.