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

Map with Enum keys deserialization #64

Closed ibosev closed 4 years ago

ibosev commented 4 years ago

I have a problem deserializing json to a class which contains a Map with enums as keys.

Flutter doctor:

[√] Flutter (Channel stable, v1.17.1, on Microsoft Windows [Version 10.0.18363.836], locale bg-BG)
[√] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[√] Android Studio (version 3.5)
[√] VS Code (version 1.45.1)
[√] Connected device (1 available)

• No issues found!

My class with the enum:

@jsonSerializable
@Json(enumValues: Category.values)
enum Category { First, Second, Third }

@jsonSerializable
class Split {
  @JsonProperty(enumValues: Category.values)
  Map<Category, int> values;

  Split(this.values);
}

Serializing a class instance and then trying to deserialize the same output:

void main() {
  initializeReflectable();

  final Map<Category, int> map = {
    Category.First: 1,
    Category.Second: 2,
    Category.Third: 3,
  };

  final split = Split(map);
  print(JsonMapper.serialize(split));

  print(JsonMapper.deserialize<Split>('''
{
 "values": {
  "Category.First": 1,
  "Category.Second": 2,
  "Category.Third": 3
 }
}
'''));

  runApp(MyApp());
}

The error I get: E/flutter (29166): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Map<Category, int>'

I read the docs here: https://github.com/k-paxian/dart-json-mapper#enum-types I am not sure if this some kind of a bug or I am doing something wrong. Please advice.

k-paxian commented 4 years ago

Thank you for spotting that! This will make the library more flexible.

You are not doing anything wrong 😃 It throws an error because this use case "with the constructor" was not supported till that moment. As of version 1.5.12 you have two options to chose from:

@jsonSerializable class Split { @JsonProperty(enumValues: Category.values) Map<Category, int> values = {}; }


* Or retain the constructor in place, no need to pre-initialize the map field **and** Provide an appropriate value decorator via static function on your model class or register it globally via custom adapter if you are planning to have more classes like this in your model
```dart
@jsonSerializable
@Json(enumValues: Category.values)
enum Category { First, Second, Third }

@jsonSerializable
@Json(valueDecorators: Split.valueDecorators)
class Split {
  static Map<Type, ValueDecoratorFunction> valueDecorators() =>
      {typeOf<Map<Category, int>>(): (value) => value.cast<Category, int>()};

  @JsonProperty(enumValues: Category.values)
  Map<Category, int> values;

  Split(this.values);
}

      // given
      final json =
          '{"values":{"Category.First":1,"Category.Second":2,"Category.Third":3}}';
      final map = {
        Category.First: 1,
        Category.Second: 2,
        Category.Third: 3,
      };
      final split = Split(map);

      // when
      final targetJson = JsonMapper.serialize(split, compactOptions);
      final instance = JsonMapper.deserialize<Split>(targetJson);

      // then
      expect(json, targetJson);
      expect(instance, TypeMatcher<Split>());
      expect(instance.values[Category.First], 1);
      expect(instance.values[Category.Second], 2);
      expect(instance.values[Category.Third], 3);

Please let me know if it'll work for you.

ibosev commented 4 years ago

Wow! I swear to god this is the best response I have ever had. Cheers man and thanks for the time to help me out with this one.

The first solution, without the constructor, works flawlessly. The second solution though throws me an unhandled exception:

E/flutter ( 5819): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: type 'String' is not a subtype of type 'Category' in type cast
E/flutter ( 5819): #0      CastIterator.current  (dart:_internal/cast.dart:64:36)
E/flutter ( 5819): #1      MapMixin.map  (dart:collection/maps.dart:164:32)
E/flutter ( 5819): #2      MapConverter.fromJSON 
package:dart_json_mapper/…/model/converters.dart:311
E/flutter ( 5819): #3      JsonMapper.deserializeObject.<anonymous closure> 
package:dart_json_mapper/src/mapper.dart:727
E/flutter ( 5819): #4      JsonMapper.enumeratePublicProperties 
package:dart_json_mapper/src/mapper.dart:353
E/flutter ( 5819): #5      JsonMapper.deserializeObject 
package:dart_json_mapper/src/mapper.dart:697
E/flutter ( 5819): #6      JsonMapper.deserialize 
package:dart_json_mapper/src/mapper.dart:45
E/flutter ( 5819): #7      main 
k-paxian commented 4 years ago

Looks like you've not updated the library up to 1.5.12 version.

Anyway, you can check it yourself in the unit test here

Tests are 🟢

AlaaEldeenYsr commented 4 years ago

i'm facing the same issue with the package last update 1.5.12 type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Map<String, NavbarTab>'

k-paxian commented 4 years ago

@AlaaEldeenYsr could you please share your code sample? Since tests are green it's not really obvious to me what's going wrong.

AlaaEldeenYsr commented 4 years ago

@k-paxian I replied in that issue https://github.com/k-paxian/dart-json-mapper/issues/9#issuecomment-637196441