Skip to content

feat: implement first version of sealed class serialization #1483

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion _test_yaml/test/src/build_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class Builder {
Map<String, dynamic> toJson() => _$BuilderToJson(this);
}

@JsonEnum(fieldRename: FieldRename.snake)
@JsonEnum(fieldRename: RenameType.snake)
enum AutoApply { none, dependents, allPackages, rootPackage }

enum BuildTo { cache, source }
36 changes: 36 additions & 0 deletions example/lib/complex_sealed_class_examples.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:json_annotation/json_annotation.dart';

part 'complex_sealed_class_examples.g.dart';

@JsonSerializable(unionDiscriminator: 'organization')
sealed class Organization {
final String name;

Organization({required this.name});

factory Organization.fromJson(Map<String, dynamic> json) =>
_$OrganizationFromJson(json);

Map<String, dynamic> toJson() => _$OrganizationToJson(this);
}

@JsonSerializable(unionDiscriminator: 'department')
sealed class Department extends Organization {
final String departmentHead;

Department({required this.departmentHead, required super.name});

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

@JsonSerializable()
class Team extends Department {
final String teamLead;

Team({
required this.teamLead,
required super.departmentHead,
required super.name,
});
}
52 changes: 52 additions & 0 deletions example/lib/complex_sealed_class_examples.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions example/lib/sealed_class_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:json_annotation/json_annotation.dart';

part 'sealed_class_example.g.dart';

@JsonSerializable(
unionDiscriminator: 'vehicle_type',
unionRename: RenameType.snake,
)
sealed class Vehicle {
final String vehicleID;

Vehicle({required this.vehicleID});

factory Vehicle.fromJson(Map<String, dynamic> json) =>
_$VehicleFromJson(json);

Map<String, dynamic> toJson() => _$VehicleToJson(this);
}

@JsonSerializable()
class Car extends Vehicle {
final int numberOfDoors;

Car({required this.numberOfDoors, required super.vehicleID});
}

@JsonSerializable()
class Bicycle extends Vehicle {
final bool hasBell;

Bicycle({required this.hasBell, required super.vehicleID});
}
46 changes: 46 additions & 0 deletions example/lib/sealed_class_example.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ environment:
resolution: workspace

dependencies:
json_annotation: ^4.9.0
json_annotation: ^4.10.0-wip

dev_dependencies:
# Used by tests. Not required to use `json_serializable`.
Expand All @@ -21,7 +21,7 @@ dev_dependencies:
build_verify: ^3.0.0

# REQUIRED!
json_serializable: ^6.8.0
json_serializable: ^6.10.0

# Not required to use `json_serializable`.
path: ^1.8.0
Expand Down
5 changes: 4 additions & 1 deletion json_annotation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
## 4.9.1-wip
## 4.10.0-wip

- Add `JsonSerializable.unionRename`
- Add `JsonSerializable.unionDiscriminator`
- Require Dart 3.8
- Deprecated `FieldRename` in favor of `RenameType`

## 4.9.0

Expand Down
18 changes: 18 additions & 0 deletions json_annotation/lib/src/allowed_keys_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ class UnrecognizedKeysException extends BadKeyException {
: super._(map);
}

/// Exception thrown if there is an unrecognized union type in a JSON map
/// that was provided during deserialization.
class UnrecognizedUnionTypeException extends BadKeyException {
/// The discriminator that was not recognized.
final String unrecognizedType;

/// The type of the union that was being deserialized.
final Type unionType;

@override
String get message =>
'Unrecognized type: $unrecognizedType '
'for union: $unionType.';

UnrecognizedUnionTypeException(this.unrecognizedType, this.unionType, Map map)
: super._(map);
}

/// Exception thrown if there are missing required keys in a JSON map that was
/// provided during deserialization.
class MissingRequiredKeysException extends BadKeyException {
Expand Down
8 changes: 4 additions & 4 deletions json_annotation/lib/src/json_enum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import 'json_value.dart';
class JsonEnum {
const JsonEnum({
this.alwaysCreate = false,
this.fieldRename = FieldRename.none,
this.fieldRename = RenameType.none,
this.valueField,
});

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

/// Specifies the field within an "enhanced enum" to use as the value
/// to use for serialization.
Expand Down
36 changes: 30 additions & 6 deletions json_annotation/lib/src/json_serializable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ import 'json_key.dart';

part 'json_serializable.g.dart';

// TODO: Remove typedef
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have to be documented in the changelog, etc!

@Deprecated('Use RenameType instead')
typedef FieldRename = RenameType;

/// Values for the automatic field renaming behavior for [JsonSerializable].
enum FieldRename {
enum RenameType {
/// Use the field name without changes.
none,

Expand All @@ -35,7 +39,7 @@ enum FieldRename {
@JsonSerializable(
checked: true,
disallowUnrecognizedKeys: true,
fieldRename: FieldRename.snake,
fieldRename: RenameType.snake,
)
@Target({TargetKind.classType})
class JsonSerializable {
Expand Down Expand Up @@ -153,14 +157,14 @@ class JsonSerializable {
/// Defines the automatic naming strategy when converting class field names
/// into JSON map keys.
///
/// With a value [FieldRename.none] (the default), the name of the field is
/// With a value [RenameType.none] (the default), the name of the field is
/// used without modification.
///
/// See [FieldRename] for details on the other options.
/// See [RenameType] for details on the other options.
///
/// Note: the value for [JsonKey.name] takes precedence over this option for
/// fields annotated with [JsonKey].
final FieldRename? fieldRename;
final RenameType? fieldRename;

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

/// The discriminator key used to identify the union type.
///
/// Defaults to `type`.
final String? unionDiscriminator;

/// Defines the automatic naming strategy when converting class names
/// to union type names.
///
/// With a value [RenameType.none] (the default), the name of the class is
/// used without modification.
///
/// See [RenameType] for details on the other options.
final RenameType? unionRename;

/// A list of [JsonConverter] to apply to this class.
///
/// Writing:
Expand Down Expand Up @@ -276,6 +294,8 @@ class JsonSerializable {
this.converters,
this.genericArgumentFactories,
this.createPerFieldToJson,
this.unionDiscriminator,
this.unionRename,
});

factory JsonSerializable.fromJson(Map<String, dynamic> json) =>
Expand All @@ -292,10 +312,12 @@ class JsonSerializable {
createToJson: true,
disallowUnrecognizedKeys: false,
explicitToJson: false,
fieldRename: FieldRename.none,
fieldRename: RenameType.none,
ignoreUnannotated: false,
includeIfNull: true,
genericArgumentFactories: false,
unionDiscriminator: 'type',
unionRename: RenameType.none,
);

/// Returns a new [JsonSerializable] instance with fields equal to the
Expand All @@ -318,6 +340,8 @@ class JsonSerializable {
includeIfNull: includeIfNull ?? defaults.includeIfNull,
genericArgumentFactories:
genericArgumentFactories ?? defaults.genericArgumentFactories,
unionDiscriminator: unionDiscriminator ?? defaults.unionDiscriminator,
unionRename: unionRename ?? defaults.unionRename,
);

Map<String, dynamic> toJson() => _$JsonSerializableToJson(this);
Expand Down
Loading