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.55k stars 397 forks source link

Enums and custom data structures are not converting back if declared in superclass #644

Closed gysipos closed 3 years ago

gysipos commented 4 years ago

I'm serializing a class that has an enum field. The generated FooFromJson method in the foo.g.dart tries to create the field simply by accessing the json['key'], which is a String, not an enum. Sure enough, when i try to use it, breaks with the output: type 'String' is not a subtype of type 'EstateTypes'

here is the enum field i'm having trouble with: enum EstateTypes { flat, house, both }

here is what the generated code looks like:

in the FooToJson: 'estateTypes': _$EstateTypesEnumMap[instance.estateTypes], and sure enough, there is a map in the file:

const _$EstateTypesEnumMap = {
  EstateTypes.flat: 'flat',
  EstateTypes.house: 'house',
  EstateTypes.both: 'both',
};

but the FooFromJson does not takes advantage of it: estateTypes: json['estateTypes'],

My pubspec.yaml looks like this (no concrete version number on any of the two dependencies): dependencies: json_annotation: dev_dependencies: json_serializable:

flutter --version output: Flutter 1.19.0-2.0.pre.133 • channel master • https://github.com/flutter/flutter.git Framework • revision e92afc16b6 (17 hours ago) • 2020-05-21 05:29:01 +0900 Engine • revision 2d4e83921d Tools • Dart 2.9.0 (build 2.9.0-9.0.dev 40f7a11d89)

Let me know if I'm not doing something properly, or if you need any further information!

gysipos commented 4 years ago

Also, another issue i found, that i have a custom class, for which i created a converter class like this:

class RangedInfoConverter implements JsonConverter<RangedInfo, Map<String, dynamic>> {
  const RangedInfoConverter();

the two overrides, implemented

}

and the FromJson does not uses this one as well. The ToJson works as expected: 'price': const RangedInfoConverter().toJson(instance.price),

but again, the FromJson is just a simple: price: json['price'],

It soes not uses the provided converters' FromJson method.

gysipos commented 4 years ago

Update:

This only happens if the field are coming from a parent class of the serialized class and are not declared for the child, only passed to the super constructor

kevmoo commented 4 years ago

please include a minimal repo. I'm not sure what's going on here.

gysipos commented 4 years ago

Man, i tried to recreate the issue in a stripped down repo, but here it works perfectly. I don't get it, but the issue will be in my code somewhere. Sorry for bothering and thank you for the awesome package!

flodaniel commented 3 years ago

i can confirm this issue. It occures if the enum is declared in an abstract parent class from which the class that is deserialised extends. This is a working version:


enum GroupsLoadingStatus {
  @JsonValue('loading')
  loading,
  @JsonValue('success')
  success,
  @JsonValue('failure')
  failure
}

abstract class GroupsState extends Equatable {
  Map<String, dynamic> toJson();

  @override
  List<Object> get props => [];
}

@JsonSerializable()
class GroupsLoadInProgress extends GroupsState {
  GroupsLoadInProgress({this.status = GroupsLoadingStatus.loading});

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

  @override
  Map<String, dynamic> toJson() => _$GroupsLoadInProgressToJson(this);

  GroupsLoadInProgress copyWith({GroupsLoadingStatus? status}) {
    return GroupsLoadInProgress(status: status ?? this.status);
  }

  final GroupsLoadingStatus status;

  @override
  List<Object> get props => [status];
}

The from json for the enum looks like this:

GroupsLoadInProgress _$GroupsLoadInProgressFromJson(Map<String, dynamic> json) {
  return GroupsLoadInProgress(
    status: _$enumDecode(_$GroupsLoadingStatusEnumMap, json['status']),
  );
}

while this is a version where it is broken:

abstract class GroupsState extends Equatable {
  const GroupsState({required this.status});

  Map<String, dynamic> toJson();

  final GroupsLoadingStatus status;

  @override
  List<Object> get props => [status];
}

@JsonSerializable()
class GroupsLoadInProgress extends GroupsState {
  GroupsLoadInProgress({status = GroupsLoadingStatus.loading})
      : super(status: status);

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

  @override
  Map<String, dynamic> toJson() => _$GroupsLoadInProgressToJson(this);

  GroupsLoadInProgress copyWith({GroupsLoadingStatus? status}) {
    return GroupsLoadInProgress(status: status ?? this.status);
  }

  @override
  List<Object> get props => [status];
}

here the fromJson looks like this:

GroupsLoadInProgress _$GroupsLoadInProgressFromJson(Map<String, dynamic> json) {
  return GroupsLoadInProgress(
    status: json['status'],
  );
}
kevmoo commented 3 years ago

@flodaniel – please confirm you're using v5.0.1 of json_serializable

kevmoo commented 3 years ago

You're missing the type of the status arg in the subclass!

@JsonSerializable()
class GroupsLoadInProgress extends GroupsState {
  GroupsLoadInProgress({/* THIS NEEDS A TYPE!! */status = GroupsLoadingStatus.loading})
      : super(status: status);
flodaniel commented 3 years ago

Oh wow - this is surprising, as the type is automatically detected in dart. I did not know that I have to add the type to make it work with json_serializable. Thanks a lot @kevmoo!