json_serializable.dart
json_serializable.dart copied to clipboard
[Feature request] Support dart3 sealed class
Thank you for this package!
I failed to find any discussions about this and it feels like essential feture at this moment.
Do you have any plan to implement union-like sealed class serialization, so we can avoid using freezed?
We could use this feature something like this (inspired by freezed):
@JsonSerializable(unionKey: 'subtype') // dafualt 'runtimeType'
sealed class RootSealedClass {
const RootSealedClass();
factory RootSealedClass.fromJson(Map<String, dynamic> json) =>
_$RootSealedClassFromJson(json);
@JsonValue('first') // or literal class name as default
const factory RootSealedClass.first(String someAtribute) =
FirstSealedClassSubtype;
@JsonValue('second') // or literal class name as default
const factory RootSealedClass.second(int someOtherAtribute) =
SecondSealedClassSubtype;
Map<String, dynamic> toJson();
}
@JsonSerializable()
class FirstSealedClassSubtype extends RootSealedClass {
const FirstSealedClassSubtype(this.someAtribute);
factory FirstSealedClassSubtype.fromJson(Map<String, dynamic> json) =>
_$FirstSealedClassSubtypeFromJson(json);
final String someAtribute;
@override
Map<String, dynamic> toJson() => _$FirstSealedClassSubtypeToJson(this);
}
@JsonSerializable()
class SecondSealedClassSubtype extends RootSealedClass {
const SecondSealedClassSubtype(this.someOtherAtribute);
factory SecondSealedClassSubtype.fromJson(Map<String, dynamic> json) =>
_$SecondSealedClassSubtypeFromJson(json);
final int someOtherAtribute;
@override
Map<String, dynamic> toJson() => _$SecondSealedClassSubtypeToJson(this);
}
Maybe? How does Freezed do this?
FromJson methods for subtypes are generated as usual:
FirstSealedClassSubtype _$firstSealedClassSubtypeFromJson(Map<String, dynamic> json) =>
FirstSealedClassSubtype(
someAtribute: json['someAtribute'] as String,
);
SecondSealedClassSubtype _$secondSealedClassSubtypeFromJson(Map<String, dynamic> json) =>
SecondSealedClassSubtype(
someOtherAtribute: json['someOtherAtribute'] as int,
);
ToJson methods just need one additional key member called 'subtype'
in our case:
Map<String, dynamic> _$firstSealedClassSubtypeToJson(FirstSealedClassSubtype instance) =>
<String, dynamic>{
'someAtribute': instance.someAtribute,
'subtype': 'first', // the value is specified by user inside @JsonValue('first') (see my first comment)
};
Map<String, dynamic> _$secondSealedClassSubtypeToJson(SecondSealedClassSubtype instance) =>
<String, dynamic>{
'someOtherAtribute': instance.someOtherAtribute,
'subtype': 'second',
};
Plus we do need root fromJson implementation. That could be just redirecting map/switch:
RootSealedClass _$rootSealedClassFromJson(Map<String, dynamic> json) =>
switch (json['subtype']) {
'first' => _$firstSealedClassSubtypeFromJson(json),
'second' => _$secondSealedClassSubtypeFromJson(json),
_ => throw ArgumentError() // or some fallback?
};
The root toJson method should be abstract (so I editted my first comment)
I'd take a PR here – It's hard to judge if the complexity is worth it without seeing the PR – which makes me nervous about saying you should make the PR, though. 😄
I'd take a PR here – It's hard to judge if the complexity is worth it without seeing the PR – which makes me nervous about saying you should make the PR, though. 😄
I have precisely 0 exp with code generation)) But I'll try
This would be a nice addition.
Currently, we can implement it by manually marking the sealed
class subtypes with @JsonSerializable
, but if we want to have a fromJson
in the sealed
class we have to implement it manually, which is a little burdensome and error-prone...