rrousselGit / freezed

Code generation for immutable classes that has a simple syntax/API without compromising on the features.
https://pub.dev/packages/freezed
1.94k stars 237 forks source link

fromJson cast error with nested Freezed Classes #1118

Closed WesselvanDam closed 2 months ago

WesselvanDam commented 3 months ago

Describe the bug I get the following error:

Error: type '_Map<Object?, Object?>' is not a subtype of type 'Map<String, dynamic>' in type cast

Because I have a freezed class that has another freezed class as one of its fields, and the generated code does not correctly cast the JSON field of the parent class's fromJSON.

To Reproduce

Simplified code:

The parent class, UserModel:

@freezed
class UserModel with _$UserModel {
  @JsonSerializable(explicitToJson: true)
  const factory UserModel({
    @Default(CustomClaimsModel()) CustomClaimsModel customClaims,
  }) = _UserModel;
  const UserModel._();

  factory UserModel.fromJson(Map<String, dynamic> json) =>
      _$UserModelFromJson(json);
}

child class, CustomClaimsModel:

@freezed
class CustomClaimsModel with _$CustomClaimsModel {
  @JsonSerializable(explicitToJson: true)
  const factory CustomClaimsModel({
    @Default(false) bool admin,
  }) = _CustomClaimsModel;

  factory CustomClaimsModel.fromJson(Map<String, dynamic> json) =>
      _$CustomClaimsModelFromJson(json);
}

Expected behavior I expect the parent class UserModel to be able to generate an instance of CustomClaimsModel using its fromJson method, which should use the CustomClaimsModel's fromJson method for the value of the field customClaims. If I change all references of Map<String, dynamic> in the code of CustomClaimsModel and its generated files to Map, the code works and I do not get the error. Hence, I suspect that the casting of the JSON to the Map is faulty. Let me know if I'm missing something!

WesselvanDam commented 2 months ago

I am not sure what I had originally done in my project, but I tried to make it work again and the models are constructed without issue. Perhaps I messed up the syntax. Closing this one.

WesselvanDam commented 2 months ago

A small update: I encountered the issue again: it turned out that it was only a problem that occurred on mobile, and not on web. I was testing on mobile for a while when I encountered this and filed the issue, then I re-implemented it when I was testing on web and I thought it was solved. Then I reverted back to mobile and the issue was back.

The root cause turns out to be https://github.com/firebase/flutterfire/issues/11872, where it is written that the response of the cloud function that I use to fetch the required data is different depending on the platform. You don't notice it if you do not have nested classes: you just type myJson = Map<String, dynamic>.from(response.data as Map);

However, with nested freezed classes, this conversion is not sufficient, because the nested object will throw the ERROR: type '_Map<Object?, Object?>' is not a subtype of type 'Map<String, dynamic>' in type cast. It is necessary to cast the nested field beforehand as well. I have done this with this function:

  Map<String, dynamic> prepareJson(Map json) {
    final mappedJson = Map<String, dynamic>.from(json);
    // also map the nested field
    mappedJson['customClaims'] = json['customClaims'] != null
        ? Map<String, dynamic>.from(json['customClaims'] as Map)
        : null;
    return mappedJson;
  }
jimmyff commented 2 months ago

@WesselvanDam see the readme, you need to use explicitToJson:

@freezed
class Example with _$Example {
  @JsonSerializable(explicitToJson: true)
  factory Example(@JsonKey(name: 'my_property') SomeOtherClass myProperty) = _Example;

  factory Example.fromJson(Map<String, dynamic> json) => _$ExampleFromJson(json);
}
WesselvanDam commented 1 week ago

@jimmyff a bit late, but the problem was with deserializing, not serializing, so adding 'explicitToJson' would not solve the problem