k-paxian / dart-json-mapper

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

Problem converting enum with following uppercase character - second edition :) #212

Closed Lars-Sommer closed 1 year ago

Lars-Sommer commented 1 year ago

Hi K-paxian.

A couple of months ago, I had this issue: https://github.com/k-paxian/dart-json-mapper/issues/203

Using version ^2.2.7 I now have a similar, if not the same issue:

I have this enum definition:

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

And this class definition:

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

If I serialize and deserialize this class instance, it works:

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

var serialized = JsonMapper.serialize(model);
var deserialized = JsonMapper.deserialize<TestModel>(serialized);

But if I wrap this instance in an array, and try to deserialize it to a list, the _TypeError (type 'Null' is not a subtype of type 'EDeviceRadioType' of 'value') exception is thrown.

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

var serialized = JsonMapper.serialize([model]);
var deserialized = JsonMapper.deserialize<List<TestModel>>(serialized);

If I use the LoRa enum value instead of WMBus it works.

Now as far as I understand this SHOULD work, right? If yes, I am quite curious to hear if you are able to reproduce it in this 2.2.7, or if I am missing something. Because last time you fixed this specific case as a bug. I tried upgrading to 2.2.9, but it seems like the newest flutter version is required to perform that upgrade. And I am currently are not able to upgrade flutter right now.

Any clue? Thanks!

k-paxian commented 1 year ago

Hi @fasterlars ,

I'm seeing you are still referring to an enum declared values in Pascal case instead of being declared using lowerCamelCase case.

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

it should work for this enum instead:

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

Please take a look at the unit test here and here

Once again the assumption is that Dart code enum values are following lowerCamelCase style

Lars-Sommer commented 1 year ago

Thanks alot for the attention and your unit testing. From the unit test, I can confirm that it is possible to convert "WMBus" string into the wMBus enum.

But this is not possible, if I create the same case in my application:

@jsonSerializable
enum EDeviceRadioType { loRa, nBIoT, mBus, mobileInternet, wMBus }

@jsonSerializable
@Json(caseStyle: CaseStyle.pascal)
class PascalCaseModel {
  String? id;
  EDeviceRadioType radio = EDeviceRadioType.loRa;
}

test() async {
  String json = '[{ "Id": "6267a8bf0a66709b2c7021bd", "Radio": "WMBus" }]';
  List<PascalCaseModel>? target = JsonMapper.deserialize<List<PascalCaseModel>>(json);
}

the deserialize function throws the _TypeError (type 'Null' is not a subtype of type 'EDeviceRadioType' of 'value') exception. I guess this might have something to do with versioning. I am using ^2.2.7. And I cannot figure out what version you are using in your unit test setup, other than '>=2.1.12 <3.0.0'. Will it then be 3.0.0? If yes, versioning might be the cause of my problem?

Anyways. I use alot of different enums that my backend defines. In my app I define them in the same way the backend does to keep consistency, and with the same casing. All of them is in pascal, and some with the uppercasing in the second character the whole problem revolves around. In the app I use these models, serialize and deserialize them, and send them over network to my backend. And when this happens, I of course need to send "WMBus", and not "wMBus" to the backend, or the call will fail. So actually I think the most simple workaround for me is to create my own frontend-app-like enums ({ loRa, nBIoT, mBus, mobileInternet, wMBus }) when this situation occurs, and when I need to send these values over network to the backend, I must use a enum.toBackendString() function that translates the names correctly.

k-paxian commented 1 year ago

I see, I'll take a look deeper then. Unit tests are running against the master branch code (i.e. latest version) The fix is in 2.2.7+1, so you should probably make sure this version is used actually image

Lars-Sommer commented 1 year ago

I currently cant upgrade version to 2.2.7+1 because of incompatible dependable versions:

Because every version of flutter from sdk depends on collection 1.16.0 and dart_json_mapper >=2.2.7+1 <2.2.7+4 depends on collection ^1.17.0, flutter from sdk is incompatible with dart_json_mapper >=2.2.7+1 <2.2.7+4.
And because dart_json_mapper >=2.2.7+2 depends on intl ^0.18.0 and meter_installation_app depends on flutter from sdk, dart_json_mapper >=2.2.7+1 requires intl ^0.18.0.
So, because meter_installation_app depends on both dart_json_mapper ^2.2.7+1 and intl ^0.17.0, version solving failed.
pub get failed (1; So, because meter_installation_app depends on both dart_json_mapper ^2.2.7+1 and intl ^0.17.0, version solving failed.)

But I will upgrade flutter along with these versions very soon.

Lars-Sommer commented 1 year ago

I´ve just updated flutter and json_mapper to latest versions. And can confirm that the parsing now works as expected.

So I´ll guess I go with a custom "toBackendString()" solution when I need to parse enum values that goes over network. Unless you have a trick up your sleeve since you would take a deeper look? :)

k-paxian commented 1 year ago

Ok, nice to hear that. Could you clarify please the case with the "toBackendString()" solution if ... parsing now works as expected. what exactly doesn't work as expected? 😄 some examples would be useful

Lars-Sommer commented 1 year ago

Sorry, I´ll try to clarify :)

This code, that did not work before with version 2.2.7:

@jsonSerializable
enum EDeviceRadioType { loRa, nBIoT, mBus, mobileInternet, wMBus }

@jsonSerializable
@Json(caseStyle: CaseStyle.pascal)
class PascalCaseModel {
  String? id;
  EDeviceRadioType radio = EDeviceRadioType.loRa;
}

test() async {
  String json = '[{ "Id": "6267a8bf0a66709b2c7021bd", "Radio": "WMBus" }]';
  List<PascalCaseModel>? target = JsonMapper.deserialize<List<PascalCaseModel>>(json);
}

... now works after updating to version 2.2.9👍 However, it still does not work if I name the enums as defined in the backend I communicate with: { LoRa, NBIoT, MBus, MobileInternet, WMBus }, and this is expected as you stated earlier.

So my current solution is to first rename my enums which has this problem, in this case the EDeviceRadioType, to camelCase: { loRa, nBIoT, mBus, mobileInternet, wMBus }. And when I need to send an enum property to my backend over network, I´ll create a helper function (toBackendString() for example) in the enum class file that translates 'wMBus' to 'WMBus'.

k-paxian commented 1 year ago

Well I'm pretty sure you don't need this custom magic with toBackendString() (that was originally the main aim to have this case style back and forth transparent) both ways serialize/deserialize.

I've added a test case to illustrate that behaviour here

So you just need to serialize the entire class before sending it to the backend and you'll have those enum values in PascalCase. I really hope I've got your issue right 😄

Lars-Sommer commented 1 year ago

Thanks alot, it works 👍 I guess the versioning confused me, because that serilization failed for me the last time 😅

k-paxian commented 1 year ago

Awesome news, thanks for reaching out! Ready to help with new troubles 😃