I am using Flutter Freezed package which in turn uses Dart json_serialize package.
I have this Dart enhanced enum with 2 fields:
enum WorkingMode {
mix(type: 1, name: "mix"),
parallel(type: 2, name: "parallel"),
sequential(type: 3, name: "sequential"),
relay(type: 4, name: "relay"),
free(type: 5, name: "free"),
associative(type: 6, name: "associative");
final int type;
final String name;
const WorkingMode({
required this.type,
required this.name,
});
}
Which is referenced in this @freezed
class:
@freezed
class Test with _$Test {
const factory Test({
required List<WorkingMode> workingModes,
}) = _Test;
factory Test.fromJson(Map<String, Object?> json) => _$TestFromJson(json);
}
And this is the code generated for the fromJson() method:
_$_Test _$$_TestFromJson(Map<String, dynamic> json) => _$_Test(
workingModes: (json['workingModes'] as List<dynamic>)
.map((e) => $enumDecode(_$WorkingModeEnumMap, e))
.toList(),
);
const _$WorkingModeEnumMap = {
WorkingMode.mix: 'mix',
};
This code expects to decode the final value only based in one particular field, in this case the name
field. I mean it works if the JSON received is like this:
{
"workingModes":[
"parallel",
"sequential",
"mix",
"relay",
"free",
"associative"
]
}
But the problem I have is that I am receiving the JSON with all the fields in the enum from a Java REST API like this:
{
"workingModes":[
{
"type":2,
"name":"parallel"
},
{
"type":3,
"name":"sequential"
},
{
"type":1,
"name":"mix"
},
{
"type":4,
"name":"relay"
},
{
"type":5,
"name":"free"
},
{
"type":6,
"name":"associative"
}
]
}
I have tried to use the @JsonValue
and @JsonEnum(valueField: 'XXX )
annotations but it seems you can specify another different field, but not several fields at the same time.
At this time I have resolved this by coding my own fromJson/toJson methods through @JsonKey
:
@freezed
class Test with _$Test {
const factory Test({
@JsonKey(
fromJson: fromJsonWorkingModes,
toJson: toJsonWorkingModes,
)
required List<WorkingMode> workingModes,
}) = _Test;
factory Test.fromJson(Map<String, Object?> json) => _$TestFromJson(json);
}
List<WorkingMode> fromJsonWorkingModes(List<dynamic> rawWorkingModes) {
return rawWorkingModes
.map((rawWorkingMode) => WorkingMode.values
.firstWhere((element) => element.type == rawWorkingMode['type']))
.toList();
}
List<dynamic> toJsonWorkingModes(List<WorkingMode> workingModes) {
List<dynamic> finalWorkingModes = [];
for (WorkingMode workingMode in workingModes) {
var data = {'type': workingMode.type, 'name': workingMode.name};
finalWorkingModes.add(data);
}
return finalWorkingModes;
}
But I was wondering if this could be done in a more direct form. Of course another possibility would be getting rid of the enhanced enum and transform it into a plain class also annotated with @freezed
but that way I’d lose all the benefits of the enum.
Thanks.
2
Answers
As @Abion47 has pointed out it the source problem is I am not receiving a strict enum object from the REST API point.
There are 2 possible solutions (at least) to the problem.
The first one is using an enum with custom fromJson/toJson fields through
@JsonKey
as already described in the original post.The other solution is using a sealed class. I have created the sealed class like the following. Notice the use of
@Freezed(unionKey: 'name')
and@FreezedUnionValue
to fit the names received in the JSON for my particular case.And the base class to deserialize the JSON like this:
I’m not in a place where I can test this at the moment, but it seems like the intended way is to use the
JsonEnum
decorator to specify which field you want to represent the serialized enum.