skip to Main Content

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


  1. Chosen as BEST ANSWER

    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.

    @Freezed(unionKey: 'name')
    sealed class WorkingModeSealed with _$WorkingModeSealed {
      @FreezedUnionValue('property.working.mode.mix')
      factory WorkingModeSealed.mix(int type, String name) = MixMode;
      @FreezedUnionValue('property.working.mode.parallel')
      factory WorkingModeSealed.parallel(int type, String name) = ParallelMode;
      @FreezedUnionValue('property.working.mode.sequential')
      factory WorkingModeSealed.sequential(int type, String name) = SequentialMode;
      @FreezedUnionValue('property.working.mode.relay')
      factory WorkingModeSealed.relay(int type, String name) = RelayMode;
      @FreezedUnionValue('property.working.mode.free')
      factory WorkingModeSealed.free(int type, String name) = FreeMode;
      @FreezedUnionValue('property.working.mode.associative')
      factory WorkingModeSealed.associative(int type, String name) =
          AssociativeMode;
    
      factory WorkingModeSealed.fromJson(Map<String, Object?> json) =>
          _$WorkingModeSealedFromJson(json);
    }
    

    And the base class to deserialize the JSON like this:

    @freezed
    class Test with _$Test {
      const factory Test({
        required List<WorkingModeSealed> workingModesSealed,
      }) = _Test;
    
      factory Test.fromJson(Map<String, Object?> json) => _$TestFromJson(json);
    }
    

  2. 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.

    @JsonEnum(valueField: 'name')
    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,
      });
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search