json_serializable.dart icon indicating copy to clipboard operation
json_serializable.dart copied to clipboard

When generating a List, the list type is invalid

Open matheuscsant opened this issue 4 months ago • 3 comments

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

matheuscsant avatar Feb 26 '24 13:02 matheuscsant

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

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

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

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

god666 avatar Feb 28 '24 06:02 god666

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,
    };

kevmoo avatar Feb 28 '24 23:02 kevmoo

@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),
    };

god666 avatar Mar 03 '24 04:03 god666

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!

kevmoo avatar Mar 05 '24 16:03 kevmoo