k-paxian / dart-json-mapper

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

Problem converting enum with following uppercase character #203

Closed Lars-Sommer closed 1 year ago

Lars-Sommer commented 1 year ago

I have the following classes:

@JsonSerializable()
@Json(caseStyle: CaseStyle.pascal)
class TestModel {
  String? id;
  EDeviceRadioType radio = EDeviceRadioType.LoRa;
}

@JsonSerializable()
class TestParent {
  TestModel? newDevice;
}

And this enum:

@JsonSerializable()
enum EDeviceRadioType { LoRa, NBIoT, MBus, MobileInternet, WMBus }

This deserialization works:

TestModel model = TestModel();
model.id = '123';
model.radio = EDeviceRadioType.WMBus;
var serialized = JsonMapper.serialize(parent);
var deserialized = JsonMapper.deserialize<TestModel>(serialized);

But this does not work:

TestParent parent = TestParent();
TestModel model = TestModel();
model.id = '123';
model.radio = EDeviceRadioType.WMBus;
parent.newDevice = model;

var serialized = JsonMapper.serialize(parent);
var deserialized = JsonMapper.deserialize<TestParent>(serialized);

The deserialize function fails with:_TypeError (type 'Null' is not a subtype of type 'EDeviceRadioType' of 'value')

But if I change the enum to a type which does not have a following upper case character, the deserialization works:

TestParent parent = TestParent();
TestModel model = TestModel();
model.id = '123';
model.radio = EDeviceRadioType.LoRa;
parent.newDevice = model;

var serialized = JsonMapper.serialize(parent);
var deserialized = JsonMapper.deserialize<TestParent>(serialized);

As far as I can read, WMBus and alike should be valid PascalCase. But nonetheless, it is weird that it works if the model is not wrapped in a parent. Am I missing anything here? 🤔

k-paxian commented 1 year ago

Interesting 🤓 thanks for detailed pre-investigation. I'll take a look deeper soon, since I have no idea yet off the top of my head.

k-paxian commented 1 year ago

Here we go preferred workaround would be to - rename enum values all camelCase since it will be aligned with the linter rule below

image as well aligned with the Dart sdk core codebase which is using this lint rule.

@JsonSerializable()
enum EDeviceRadioType { loRa, nBIoT, mBus, mobileInternet, wMBus }

According to this doc assumption is that your Dart code IS following camelCase style initially. Main misunderstanding comes from braking this assumption by giving the initial code in PascalCase

So you should be good to go just by renaming the enum values, and those values will be transformed accordingly to case style option.

And what if you need to have those enum values to still be transmitted using completely custom logic? you can do that by providing enum values mappings, like this

    JsonMapper().useAdapter(
       JsonMapperAdapter(enumValues: {
        EDeviceRadioType: EnumDescriptor(
            values: EDeviceRadioType.values,
            mapping: <EDeviceRadioType, String>{
              EDeviceRadioType.loRa: 'LORa',
              EDeviceRadioType.nBIoT: 'NB-IoT',
              EDeviceRadioType.mBus: '_MBUs_',
              EDeviceRadioType.wMBus: 'WMBUS',
              // ...
            })
      })
   );

Hope this helps 😄

Lars-Sommer commented 1 year ago

Thanks alot, I will look into it in the near future and return :)

Lars-Sommer commented 1 year ago

I´ve tried your adapter solution and renamed the enums with camelCase. This works fine. I have to do this because it is our backend who has defined and named these enums.

So I guess I´ve got to rename all my enum definitions in the app, and then create the corresponding adapter mappings. It would be nice if this was not nescacary, but I´m glad there is some kind of solution :)

k-paxian commented 1 year ago

I mean this should work even w/o adapter, just by lower casing first letter of each enum entry. These adapter mappings are for non standard cases primarily, when there is no suited predefined casing.

Lars-Sommer commented 1 year ago

I cannot get it to work unless I create the adapter. This is my new enum definition:

enum EDeviceRadioType { loRa, nbIot, mBus, mobileInternet, wmBus }

This parsing works:

const json = '{ "Id": "6267a8bf0a66709b2c7021bd", "Radio": "WMBus" }';
TestModel model =  JsonMapper.deserialize<TestModel>(json);

But similiar to the problem I mentioned in the original question, the deserializer cannot parse the enum when the object is nested, in this case in an array:

const json = '[{ "Id": "6267a8bf0a66709b2c7021bd", "Radio": "WMBus" }]';
List<TestModel> models =  JsonMapper.deserialize<List<TestModel>>(json);

Error: "type 'Null' is not a subtype of type 'EDeviceRadioType' of 'value'"

If I create the adapter, it works.

I´ve also just tried to revert the enum renaming to the original values (WMBus etc.), and the situation is the same; I can parse a single object, but not when it is in an array or has a parent.

🤓

k-paxian commented 1 year ago

Also, you have a caseInsensitive flag in the EnumDescriptor class to declare enum as such. This way you can omit listing all the values in mappings.

I'm still trying to solve this issue in a more elegant way, so it's officially a bug.

k-paxian commented 1 year ago

hopefully I've got a fix for this, please, if you have some time to validate it on your side it would be super helpful 👍

you can take a look at the unit tests here and here

Lars-Sommer commented 1 year ago

I´ve tried upgrading to dart_json_mapper: ^2.2.7 and made a quick test, and it seems I can now parse my WMBus enum in both a model and a model in an array. Without the extra adapter setting. So I guess it works! Thanks alot - you´re awesome :)