Using dart Freezed with sealed classes and fromJson

64 Views Asked by At

The documentation of freezed is telling that we should just use the sealed classes made available in dart 3. However: it does not explain how to use it, especially in combination with fromJson. So I'm a little stumped on how to do it with sealed classes instead of union types.

Given the following union;

import 'package:freezed_annotation/freezed_annotation.dart';

part 'example.freezed.dart';
part 'example.g.dart';

@freezed
sealed class Example with _$Example {
  const factory Example.text({
    required String text,
  }) = _ExampleText;

  const factory Example.nameLocation({
    required String name,
    required String location,
  }) = _ExampleNameLocation;

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

void main() {
  print(Example.fromJson({'runtimeType': 'text', 'text': 'Hello'}));
  print(Example.fromJson({
    'runtimeType': 'nameLocation',
    'name': 'John',
    'location': 'Amsterdam'
  }));
}

I tried something like this - but that doesn't work. (The non-abstract class '_$ExampleTextImpl' is missing implementations for these members: _$Example.toJson)

import 'package:freezed_annotation/freezed_annotation.dart';

part 'example.freezed.dart';
part 'example.g.dart';

@freezed
sealed class Example with _$Example {
  const factory Example() = _Example;
  factory Example.fromJson(
    Map<String, dynamic> json,
  ) =>
      _$ExampleFromJson(json);
}

@freezed
class ExampleText extends Example with _$ExampleText {
  const factory ExampleText({required String text}) = _ExampleText;
}

@freezed
class ExampleNameLocation extends Example with _$ExampleNameLocation {
  const factory ExampleNameLocation({
    required String name,
    required String location,
  }) = _ExampleNameLocation;
}

void main() {
  print(Example.fromJson({'runtimeType': 'ExampleText', 'text': 'Hello'}));
  print(Example.fromJson({
    'runtimeType': 'ExampleNameLocation',
    'name': 'John',
    'location': 'Amsterdam'
  }));
}
2

There are 2 best solutions below

1
Volodymyr Kadomtsev On

To use sealed classes with Freezed in Dart, you don't need to create separate classes for each variant. Instead, you can define all variants within the same sealed class and handle them using pattern matching. Here's how you can modify your code:

import 'package:freezed_annotation/freezed_annotation.dart';

part 'example.freezed.dart';
part 'example.g.dart';

@freezed
abstract class Example with _$Example {
  const factory Example.text({
    required String text,
  }) = _Text;

  const factory Example.nameLocation({
    required String name,
    required String location,
  }) = _NameLocation;

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

void main() {
  final example1 = Example.fromJson({'runtimeType': 'text', 'text': 'Hello'});
  final example2 = Example.fromJson({
    'runtimeType': 'nameLocation',
    'name': 'John',
    'location': 'Amsterdam'
  });

  example1.when(
    text: (String text) => print('Text: $text'),
    nameLocation: (String name, String location) =>
        print('Name: $name, Location: $location'),
  );

  example2.when(
    text: (String text) => print('Text: $text'),
    nameLocation: (String name, String location) =>
        print('Name: $name, Location: $location'),
  );
}
0
Roboroads On

Looks like it's the same as before, except we would make the classes not private and freezed will generate the classes after the =.

import 'package:freezed_annotation/freezed_annotation.dart';

part 'example.freezed.dart';
part 'example.g.dart';

@freezed
sealed class Example with _$Example {
  const factory Example.text({
    required String text,
  }) = ExampleText;

  const factory Example.nameLocation({
    required String name,
    required String location,
  }) = ExampleNameLocation;

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

void main() {
  final example =
      Example.fromJson({'runtimeType': 'text', 'text': 'Hello World!'});
  final type = switch (example) {
    ExampleText() => 'ExampleText',
    ExampleNameLocation() => 'ExampleNameLocation',
  };
  print(type);  // ExampleText
}

I do think this is a little unclear in the documentation, but I guess it's not that much different as before. Just don't use when.