google / json_serializable.dart

Generates utilities to aid in serializing to/from JSON.
https://pub.dev/packages/json_serializable
BSD 3-Clause "New" or "Revised" License
1.54k stars 392 forks source link

When generating a List, the list type is invalid #1403

Closed matheuscsant closed 4 months ago

matheuscsant commented 4 months ago

Versions:

Dart SDK version: 3.3.0 (stable) (Tue Feb 13 10:25:19 2024 +0000) on "windows_x64"

Flutter 3.19.1 • channel stable • https://github.com/flutter/flutter.git Framework • revision abb292a07e (6 days ago) • 2024-02-20 14:35:05 -0800 Engine • revision 04817c99c9 Tools • Dart 3.3.0 • DevTools 2.31.1

I have a problem when I generate file using annotations @JsonSerializable and @RealmModel, the generator no set te correct type of list.

I have these classes:

@RealmModel()
@JsonSerializable()
class $EmpresaFilial {
  late $Empresa? empresa;
  late List<$Filial> filiais;

  EmpresaFilial toRealmObject() {
    return EmpresaFilial(
      empresa: empresa!.toRealmObject(),
      filiais: filiais.map((f) => f.toRealmObject()).toList(),
    );
  }

  static fromJson(Map<String, dynamic> json) => _$$EmpresaFilialFromJson(json);

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

extension EmpresaFilialJsonHandler on EmpresaFilial {
  static EmpresaFilial fromJson(Map<String, dynamic> json) =>
      _$$EmpresaFilialFromJson(json).toRealmObject();

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

@RealmModel()
@JsonSerializable()
class $Filial {
  @PrimaryKey()
  late int? id;
  late String? nome;

  Filial toRealmObject() {
    return Filial(
      id,
      nome: nome,
    );
  }

  static fromJson(Map<String, dynamic> json) => _$$FilialFromJson(json);

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

extension FilialJsonHandler on Filial {
  static Filial fromJson(Map<String, dynamic> json) =>
      _$$FilialFromJson(json).toRealmObject();

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

And this is a json_serializable generated code:

$EmpresaFilial _$$EmpresaFilialFromJson(Map<String, dynamic> json) =>
    $EmpresaFilial()
      ..empresa = json['empresa'] == null
          ? null
          : $Empresa.fromJson(json['empresa'] as Map<String, dynamic>)
      ..filiais = (json['filiais'] as List<dynamic>)
          .map((e) => $Filial.fromJson(e as Map<String, dynamic>))
          .toList(); <== HERE IS THE PROBLEM

Map<String, dynamic> _$$EmpresaFilialToJson($EmpresaFilial instance) =>
    <String, dynamic>{
      'empresa': instance.empresa,
      'filiais': instance.filiais,
    };

When returning from the fromJson method of the generated code, I receive this error

A value of type 'List<dynamic>' can't be assigned to a variable of type 'List<$Filial>'.
Try changing the type of the variable, or casting the right-hand type to 'List<$Filial>'.

I managed to adjust by placing a cast for List<$Filial> in the file generated at the end of the method, but this is unfeasible, since when the file is generated again, this manual cast will be deleted

god666 commented 4 months ago

Yeah, i had the same problem. When it try to convert List to List it has the same problem.

MangaModel._categoryFromJson(json['genres'] as List)

Error message: type 'List' is not a subtype of type 'List' in type cast

I manually must change to: MangaModel._categoryFromJson(List.from(json['genres']))

kevmoo commented 4 months ago

Please provide a minimal reproduction without all of the extra extension types, static helpers, etc.

I tried to create something minimal and it looks fine


@JsonSerializable()
class EmpresaFilial {
  final List<Filial> filiais;

  EmpresaFilial({required this.filiais});

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

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

@JsonSerializable()
class Filial {
  final int? id;
  final String? nome;

  Filial(this.id, {this.nome});

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

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

EmpresaFilial _$EmpresaFilialFromJson(Map<String, dynamic> json) =>
    EmpresaFilial(
      filiais: (json['filiais'] as List<dynamic>)
          .map((e) => Filial.fromJson(e as Map<String, dynamic>))
          .toList(),
    );

Map<String, dynamic> _$EmpresaFilialToJson(EmpresaFilial instance) =>
    <String, dynamic>{
      'filiais': instance.filiais,
    };

Filial _$FilialFromJson(Map<String, dynamic> json) => Filial(
      json['id'] as int?,
      nome: json['nome'] as String?,
    );

Map<String, dynamic> _$FilialToJson(Filial instance) => <String, dynamic>{
      'id': instance.id,
      'nome': instance.nome,
    };
god666 commented 4 months ago
@JsonSerializable()
class MangaModel {
  @JsonKey(name: 'id')
  final String id;

  @JsonKey(name: 'name')
  final String title;

  @JsonKey(name: 'genres', toJson: _categoryToJson, fromJson: _categoryFromJson)
  final List<CategoryModel> categories;

  const MangaModel({
    required this.id,
    required this.title,
    required this.categories,
  });

  factory MangaModel.fromJson(JsonMap json) {
    return _$MangaModelFromJson(json);
  }

  JsonMap toJson() {
    return _$MangaModelToJson(this);
  }

  static List<CategoryModel> _categoryFromJson(List<String> list) {
    final genres =
        list.map((category) => CategoryModel.findByApiName(category)).toList();
    return genres;
  }

  static List<String> _categoryToJson(List<CategoryModel> categories) {
    final genres = categories.map((e) => e.apiName).toList();
    return genres;
  }
}

enum CategoryModel {
  none('', ''),
  isekai('Isekai', 'Isekai'),
  fantasy('Fantasy', 'Fantasy'),
  action('Action', 'Action'),
  harem('Harem', 'Harem'),
  shounen('Shounen', 'Shounen'),
  scify('Sci-fy', 'Sci-y'),
  horror('Horror', 'Horror');

  final String appName;
  final String apiName;
  const CategoryModel(this.appName, this.apiName);

  static CategoryModel findByApiName(String name) {
    return CategoryModel.values.firstWhere(
      (element) => element.apiName == name,
      orElse: () => CategoryModel.none,
    );
  }
}

MangaModel _$MangaModelFromJson(Map<String, dynamic> json) => MangaModel(
      id: json['id'] as String,
      title: json['name'] as String,

      //This code returns: type 'List' is not a subtype of type 'List' in type cast
      categories: MangaModel._categoryFromJson(json['genres'] as List<String>),
      //I need to change to:
      //categories: MangaModel._categoryFromJson(List.from(json['genres'])),
    );

Map<String, dynamic> _$MangaModelToJson(MangaModel instance) =>
    <String, dynamic>{
      'id': instance.id,
      'name': instance.title,
      'genres': MangaModel._categoryToJson(instance.categories),
    };
kevmoo commented 4 months ago

Ah! You need to update _categoryFromJson to take List<dynamic> and do the conversion there! the fromJson: param in JsonKey is exactly that! You need to make the type JSON-compatible!