k-paxian / dart-json-mapper

Serialize / Deserialize Dart Objects to / from JSON
https://pub.dev/packages/dart_json_mapper
Other
402 stars 33 forks source link

Enum and Equatable incompatibility #51

Closed bounty1342 closed 4 years ago

bounty1342 commented 4 years ago

Hi,

Run into this issue an issue with equatable today.

The following code :

import 'package:dart_json_mapper/dart_json_mapper.dart';
import 'package:equatable/equatable.dart';

enum MyColor { black, red }

@jsonSerializable
@Json(ignoreNullMembers: true)
class MyCarModel extends Equatable {
  final String model;

  @JsonProperty(enumValues: MyColor.values)
  final MyColor color;

  const MyCarModel({this.model, this.color});

  @override
  List<Object> get props => [model, color];

  Map<String, dynamic> toJson() {
    return JsonMapper.toMap(this);
  }
}

Will trigger this exception :

--¶ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ¶----------------------------------------------------
The following _MissingEnumValuesErrorImpl was thrown running a test:
It seems your Enum class field is missing annotation:
@JsonProperty(enumValues: MyColor.values)

When the exception was thrown, this was the stack:
#0      JsonMapper.serializeObject (package:dart_json_mapper/src/mapper.dart:481:9)
#1      JsonMapper.serializeIterable.<anonymous closure> (package:dart_json_mapper/src/mapper.dart:452:32)
#2      MappedListIterable.elementAt (dart:_internal/iterable.dart:417:29)
#3      ListIterable.toList (dart:_internal/iterable.dart:221:19)
#4      JsonMapper.serializeIterable (package:dart_json_mapper/src/mapper.dart:452:64)
#5      JsonMapper.serializeObject.<anonymous closure> (package:dart_json_mapper/src/mapper.dart:529:32)
#6      JsonMapper.enumeratePublicFields (package:dart_json_mapper/src/mapper.dart:329:14)
#7      JsonMapper.serializeObject (package:dart_json_mapper/src/mapper.dart:509:5)
#8      JsonMapper.serialize (package:dart_json_mapper/src/mapper.dart:40:18)
#9      JsonMapper.toMap (package:dart_json_mapper/src/mapper.dart:60:9)
#10     MyCarModel.toJson (package:easivio/domain/models/user_model.dart:168:23)
#11     main.<anonymous closure>.<anonymous closure> (file:///C:/Users/Stephane/easivio/easivio-mobile/test/onboarding_test.dart:47:29)
<asynchronous suspension>
#12     testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:124:25)
#13     TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:696:19)
<asynchronous suspension>
#16     TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:679:14)
#17     AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:1050:24)
#23     AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:1047:15)
#24     testWidgets.<anonymous closure> (package:flutter_test/src/widget_tester.dart:121:22)
#25     Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:171:27)
<asynchronous suspension>
#26     Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:242:15)
#31     Invoker.waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:239:5)
#32     Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:169:33)
#37     Declarer.test.<anonymous closure> (package:test_api/src/backend/declarer.dart:168:13)
#38     Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart:392:25)
#52     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:384:19)
#53     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:418:5)
#54     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174:12)
(elided 28 frames from class _FakeAsync, package dart:async, package dart:async-patch, and package stack_trace)

Regards

bounty1342 commented 4 years ago

@k-paxian : Actually it's not related to Equatable, but rather to another corner case of a getter of a list of Object containing a Enum.

enum MyColor { black, red }

@jsonSerializable
class MyCarModel  {
  final String model;

  @JsonProperty(enumValues: MyColor.values)
  final MyColor color;

 const MyCarModel({this.model, this.color});

  List<Object> get myVal => [
        model,
        color,
      ];

  Map<String, dynamic> toJson() {
    return JsonMapper.toMap(this);
  }
}

Certainly because getter are consider like field, it's expecting an annotation for myVal. The following works witch seems to confirm the expectation :

enum MyColor { black, red }

@jsonSerializable
class MyCarModel {
  final String model;

  @JsonProperty(enumValues: MyColor.values)
  final MyColor color;

  const MyCarModel({this.model, this.color});

  @JsonProperty(enumValues: MyColor.values)
  Object get props => color;

  Map<String, dynamic> toJson() {
    return JsonMapper.toMap(this);
  }
}

Since this bug is resolve, one easy way to make it compatible with Equatable is to add the 'ignore' @ on the getter containing Enum.

@jsonSerializable
class MyCarModel extends Equatable {
  final String model;

  @JsonProperty(enumValues: MyColor.values)
  final MyColor color;

  const MyCarModel({this.model, this.color});

  @JsonProperty(ignore: true)
  List<Object> get props => [
        model,
        color,
      ];

  Map<String, dynamic> toJson() {
    return JsonMapper.toMap(this);
  }
}

Hope this findings will help you and other users. 😃

k-paxian commented 4 years ago

So nice to see clean and concise model code 😄