freezed icon indicating copy to clipboard operation
freezed copied to clipboard

Accessing the UnionType String generated by freezed

Open lhengl opened this issue 3 years ago • 2 comments

Is your feature request related to a problem? Please describe. Union type strings are not easily accessible at runtime. It seems to only be used for the purpose of toJson and fromJson generated code. For example I can't find an elegant way to store freezed objects in a map by type - Map<String, UnionObject> where String is the union type.

Describe the solution you'd like Would it be possible for a get method to be generated that mirrors the unionKey? For example:

@Freezed(unionKey: 'type')
class MyType with _$MyType {
  const MyType._();

  const factory MyType.type1({
    required int field1,
    required int field2,
  }) = Type1;

  const factory MyType.type2({
    required int field1,
    required int field2,
  }) = Type2;

  factory MyType.fromJson(Map<String, dynamic> json) => _$MyTypeFromJson(json);
}

With the above freezed object I'd like to get the type like this:

var object = Type1();
print(object.type); // type1 is printed

Describe alternatives you've considered To get around this problem, the solution I've used so far is to add my own "get type" method like the below:

@Freezed(unionKey: 'type')
class MyType with _$MyType {
  const MyType._();

  const factory MyType.type1({
    required int field1,
    required int field2,
  }) = Type1;

  const factory MyType.type2({
    required int field1,
    required int field2,
  }) = Type2;

  String get type {
    return map(
      type1: (_) => 'type1',
      type2: (_) => 'type2',
    );
  }

  factory MyType.fromJson(Map<String, dynamic> json) => _$MyTypeFromJson(json);
}

With the above code I can do something like this:

Map<String, MyType> myObjects = {myOtherObject.type : myOtherObject};

I know this doesn't seem very useful with just 2 union types, but I've had to work with sometimes 20 union types that I need to store a subset of these in a map for later retrieval. So I've had to repeat the "String get type {}" method on all freezed class that requires a subset to be stored for later use. It can be very verbose and may introduce inconsistency because it's not generated by the package and relies on the coder to remember to mirror the code correctly in both places, defeating the benefit of union types.

Additional context The other option is to use "runtimeType" but will again introduce inconsistency with the outputted string of to/fromJson. So why not just bake the solution into the freezed package itself? Surely there are scenarios where getting the type string is useful in some ways.

lhengl avatar Nov 16 '21 02:11 lhengl

Ran into this issue myself and would be interested in also having unionValueCase and fallbackUnion taken into account as well.

For example:

@Freezed(unionKey: 'some_snake_case_key', unionValueCase: FreezedUnionCase.snake, fallbackUnion: 'unknown')
class MyType with _$MyType {
  const factory MyType.typeOne({
    required int field1,
    required int field2,
  }) = _TypeOne;

  const factory MyType.typeTwo({
    required int field3,
    required int field4,
  }) = _TypeTwo;

  const factory MyType.unknown({
    required String field5,
  }) = _Unknown;

  factory MyType.fromJson(Map<String, dynamic> json) => _$MyTypeFromJson(json);
}

void main() {
  final first = MyType.fromJson({
    'some_snake_case_key': 'type_one',
    'field1': 1,
    'field2': 2,
  });
  final second = MyType.fromJson({
    'some_snake_case_key': 'type_two',
    'field3': 3,
    'field4': 4,
  });
  final third = MyType.fromJson({
    'some_snake_case_key': 'type_three',
    'field5': 'field5',
  });

  assert(first.someSnakeCaseKey == 'type_one');
  assert(second.someSnakeCaseKey == 'type_two');
  assert(third.someSnakeCaseKey == 'type_three'); // fallback key still gets captured
}

This would likely require the exposed 'unionKey' value to be nullable since the fallback also occurs if the key isn't present in the json.

CelticMajora avatar Jan 19 '22 20:01 CelticMajora

Right now my workaround (if you have toJson enabled) is to convert it to a json map and put the value of the unionKey in . It would be really handy to have it generated by freezed though.

Reprevise avatar Aug 07 '22 19:08 Reprevise