Skip to content

Commit 00e16ce

Browse files
committed
feat: implement sealed class serialization
1 parent 7d8d510 commit 00e16ce

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2879
-161
lines changed

_test_yaml/test/src/build_config.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class Builder {
8585
Map<String, dynamic> toJson() => _$BuilderToJson(this);
8686
}
8787

88-
@JsonEnum(fieldRename: FieldRename.snake)
88+
@JsonEnum(fieldRename: RenameType.snake)
8989
enum AutoApply { none, dependents, allPackages, rootPackage }
9090

9191
enum BuildTo { cache, source }
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import 'package:json_annotation/json_annotation.dart';
2+
3+
part 'complex_sealed_class_examples.g.dart';
4+
5+
@JsonSerializable(unionDiscriminator: 'organization')
6+
sealed class Organization {
7+
final String name;
8+
9+
Organization({required this.name});
10+
11+
factory Organization.fromJson(Map<String, dynamic> json) =>
12+
_$OrganizationFromJson(json);
13+
14+
Map<String, dynamic> toJson() => _$OrganizationToJson(this);
15+
}
16+
17+
@JsonSerializable(unionDiscriminator: 'department')
18+
sealed class Department extends Organization {
19+
final String departmentHead;
20+
21+
Department({required this.departmentHead, required super.name});
22+
23+
factory Department.fromJson(Map<String, dynamic> json) =>
24+
_$DepartmentFromJson(json);
25+
}
26+
27+
@JsonSerializable()
28+
class Team extends Department {
29+
final String teamLead;
30+
31+
Team({
32+
required this.teamLead,
33+
required super.departmentHead,
34+
required super.name,
35+
});
36+
}

example/lib/complex_sealed_class_examples.g.dart

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import 'package:json_annotation/json_annotation.dart';
2+
3+
part 'sealed_class_example.g.dart';
4+
5+
@JsonSerializable(
6+
unionDiscriminator: 'vehicle_type',
7+
unionRename: RenameType.snake,
8+
)
9+
sealed class Vehicle {
10+
final String vehicleID;
11+
12+
Vehicle({required this.vehicleID});
13+
14+
factory Vehicle.fromJson(Map<String, dynamic> json) =>
15+
_$VehicleFromJson(json);
16+
17+
Map<String, dynamic> toJson() => _$VehicleToJson(this);
18+
}
19+
20+
@JsonSerializable()
21+
class Car extends Vehicle {
22+
final int numberOfDoors;
23+
24+
Car({required this.numberOfDoors, required super.vehicleID});
25+
}
26+
27+
@JsonSerializable()
28+
class Bicycle extends Vehicle {
29+
final bool hasBell;
30+
31+
Bicycle({required this.hasBell, required super.vehicleID});
32+
}

example/lib/sealed_class_example.g.dart

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ environment:
77
resolution: workspace
88

99
dependencies:
10-
json_annotation: ^4.9.0
10+
json_annotation: ^4.10.0-wip
1111

1212
dev_dependencies:
1313
# Used by tests. Not required to use `json_serializable`.
@@ -21,7 +21,7 @@ dev_dependencies:
2121
build_verify: ^3.0.0
2222

2323
# REQUIRED!
24-
json_serializable: ^6.8.0
24+
json_serializable: ^6.10.0
2525

2626
# Not required to use `json_serializable`.
2727
path: ^1.8.0

json_annotation/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
## 4.9.1-wip
1+
## 4.10.0-wip
22

3+
- Add `JsonSerializable.unionRename`
4+
- Add `JsonSerializable.unionDiscriminator`
35
- Require Dart 3.8
6+
- Deprecated `FieldRename` in favor of `RenameType`
47

58
## 4.9.0
69

json_annotation/lib/src/allowed_keys_helpers.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ class UnrecognizedKeysException extends BadKeyException {
7979
: super._(map);
8080
}
8181

82+
/// Exception thrown if there is an unrecognized union type in a JSON map
83+
/// that was provided during deserialization.
84+
class UnrecognizedUnionTypeException extends BadKeyException {
85+
/// The discriminator that was not recognized.
86+
final String unrecognizedType;
87+
88+
/// The type of the union that was being deserialized.
89+
final Type unionType;
90+
91+
@override
92+
String get message =>
93+
'Unrecognized type: $unrecognizedType '
94+
'for union: $unionType.';
95+
96+
UnrecognizedUnionTypeException(this.unrecognizedType, this.unionType, Map map)
97+
: super._(map);
98+
}
99+
82100
/// Exception thrown if there are missing required keys in a JSON map that was
83101
/// provided during deserialization.
84102
class MissingRequiredKeysException extends BadKeyException {

json_annotation/lib/src/json_enum.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import 'json_value.dart';
1212
class JsonEnum {
1313
const JsonEnum({
1414
this.alwaysCreate = false,
15-
this.fieldRename = FieldRename.none,
15+
this.fieldRename = RenameType.none,
1616
this.valueField,
1717
});
1818

@@ -27,14 +27,14 @@ class JsonEnum {
2727
/// Defines the naming strategy when converting enum entry names to JSON
2828
/// values.
2929
///
30-
/// With a value [FieldRename.none] (the default), the name of the enum entry
30+
/// With a value [RenameType.none] (the default), the name of the enum entry
3131
/// is used without modification.
3232
///
33-
/// See [FieldRename] for details on the other options.
33+
/// See [RenameType] for details on the other options.
3434
///
3535
/// Note: the value for [JsonValue.value] takes precedence over this option
3636
/// for entries annotated with [JsonValue].
37-
final FieldRename fieldRename;
37+
final RenameType fieldRename;
3838

3939
/// Specifies the field within an "enhanced enum" to use as the value
4040
/// to use for serialization.

json_annotation/lib/src/json_serializable.dart

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ import 'json_key.dart';
1212

1313
part 'json_serializable.g.dart';
1414

15+
// TODO: Remove typedef
16+
@Deprecated('Use RenameType instead')
17+
typedef FieldRename = RenameType;
18+
1519
/// Values for the automatic field renaming behavior for [JsonSerializable].
16-
enum FieldRename {
20+
enum RenameType {
1721
/// Use the field name without changes.
1822
none,
1923

@@ -35,7 +39,7 @@ enum FieldRename {
3539
@JsonSerializable(
3640
checked: true,
3741
disallowUnrecognizedKeys: true,
38-
fieldRename: FieldRename.snake,
42+
fieldRename: RenameType.snake,
3943
)
4044
@Target({TargetKind.classType})
4145
class JsonSerializable {
@@ -153,14 +157,14 @@ class JsonSerializable {
153157
/// Defines the automatic naming strategy when converting class field names
154158
/// into JSON map keys.
155159
///
156-
/// With a value [FieldRename.none] (the default), the name of the field is
160+
/// With a value [RenameType.none] (the default), the name of the field is
157161
/// used without modification.
158162
///
159-
/// See [FieldRename] for details on the other options.
163+
/// See [RenameType] for details on the other options.
160164
///
161165
/// Note: the value for [JsonKey.name] takes precedence over this option for
162166
/// fields annotated with [JsonKey].
163-
final FieldRename? fieldRename;
167+
final RenameType? fieldRename;
164168

165169
/// When `true` on classes with type parameters (generic types), extra
166170
/// "helper" parameters will be generated for `fromJson` and/or `toJson` to
@@ -224,6 +228,20 @@ class JsonSerializable {
224228
/// `includeIfNull`, that value takes precedent.
225229
final bool? includeIfNull;
226230

231+
/// The discriminator key used to identify the union type.
232+
///
233+
/// Defaults to `type`.
234+
final String? unionDiscriminator;
235+
236+
/// Defines the automatic naming strategy when converting class names
237+
/// to union type names.
238+
///
239+
/// With a value [RenameType.none] (the default), the name of the class is
240+
/// used without modification.
241+
///
242+
/// See [RenameType] for details on the other options.
243+
final RenameType? unionRename;
244+
227245
/// A list of [JsonConverter] to apply to this class.
228246
///
229247
/// Writing:
@@ -276,6 +294,8 @@ class JsonSerializable {
276294
this.converters,
277295
this.genericArgumentFactories,
278296
this.createPerFieldToJson,
297+
this.unionDiscriminator,
298+
this.unionRename,
279299
});
280300

281301
factory JsonSerializable.fromJson(Map<String, dynamic> json) =>
@@ -292,10 +312,12 @@ class JsonSerializable {
292312
createToJson: true,
293313
disallowUnrecognizedKeys: false,
294314
explicitToJson: false,
295-
fieldRename: FieldRename.none,
315+
fieldRename: RenameType.none,
296316
ignoreUnannotated: false,
297317
includeIfNull: true,
298318
genericArgumentFactories: false,
319+
unionDiscriminator: 'type',
320+
unionRename: RenameType.none,
299321
);
300322

301323
/// Returns a new [JsonSerializable] instance with fields equal to the
@@ -318,6 +340,8 @@ class JsonSerializable {
318340
includeIfNull: includeIfNull ?? defaults.includeIfNull,
319341
genericArgumentFactories:
320342
genericArgumentFactories ?? defaults.genericArgumentFactories,
343+
unionDiscriminator: unionDiscriminator ?? defaults.unionDiscriminator,
344+
unionRename: unionRename ?? defaults.unionRename,
321345
);
322346

323347
Map<String, dynamic> toJson() => _$JsonSerializableToJson(this);

0 commit comments

Comments
 (0)