Skip to content
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
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: FieldRename.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.

2 changes: 1 addition & 1 deletion 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 Down
5 changes: 4 additions & 1 deletion json_annotation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
## 4.9.1-wip
## 4.10.0-wip

- Support `JsonKey` annotation on constructor parameters.
- Require `meta: ^1.16.0`
- Require `sdk: ^3.9.0`
- Add `JsonSerializable.unionRename`
- Add `JsonSerializable.unionDiscriminator`
- Require Dart 3.8

## 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
20 changes: 20 additions & 0 deletions json_annotation/lib/src/json_serializable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,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 [FieldRename.none] (the default), the name of the class is
/// used without modification.
///
/// See [FieldRename] for details on the other options.
final FieldRename? unionRename;

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

factory JsonSerializable.fromJson(Map<String, dynamic> json) =>
Expand All @@ -296,6 +312,8 @@ class JsonSerializable {
ignoreUnannotated: false,
includeIfNull: true,
genericArgumentFactories: false,
unionDiscriminator: 'type',
unionRename: FieldRename.none,
);

/// Returns a new [JsonSerializable] instance with fields equal to the
Expand All @@ -318,6 +336,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
14 changes: 14 additions & 0 deletions json_annotation/lib/src/json_serializable.g.dart

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

2 changes: 1 addition & 1 deletion json_annotation/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: json_annotation
version: 4.9.1-wip
version: 4.10.0-wip
description: >-
Classes and helper functions that support JSON code generation via the
`json_serializable` package.
Expand Down
7 changes: 5 additions & 2 deletions json_serializable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 6.12.0-wip

- Add support for deserializing union json to sealed class
- Add support for serializing sealed class to union json

## 6.11.3

- Require `analyzer: ^9.0.0`
Expand Down Expand Up @@ -32,8 +37,6 @@

## 6.10.0

- Required `analyzer: ^7.4.0`.
- Switch to analyzer element2 model and `build: ^3.0.0-dev`.
- Move `package:collection` to a dev dependency.
- Use new `null-aware element` feature in generated code.
- Require Dart 3.8
Expand Down
49 changes: 49 additions & 0 deletions json_serializable/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,53 @@ customize the encoding/decoding of any type, you have a few options.
}
```

# Sealed classes

As of `json_serializable` version 6.10.0 and `json_annotation`
version 4.10.0, sealed classes can be serialized to json unions and json unions
can be deserialized to sealed classes.

To achieve this, both the sealed class and its subclasses should be annotated
with [`JsonSerializable`]. Only the sealed class should have `fromJson` factory
or `toJson` function. To customize the sealed class behavior, use the fields
`unionRename` and `unionDiscriminator` in [`JsonSerializable`] or adjust the
default behavior by changing the corresponding fields in `build.yaml`. For
more complex examples, please see [example]:

```dart
import 'package:json_annotation/json_annotation.dart';

part 'sealed_example.g.dart';

@JsonSerializable()
sealed class SealedBase {
const SealedBase();

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

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

@JsonSerializable()
class SealedSub1 extends SealedBase {
final String exampleField1;

SealedSub1({
required this.exampleField1,
});
}

@JsonSerializable()
class SealedSub2 extends SealedBase {
final String exampleField2;

SealedSub2({
required this.exampleField2,
});
}
```

# Build configuration

Aside from setting arguments on the associated annotation classes, you can also
Expand Down Expand Up @@ -282,6 +329,8 @@ targets:
generic_argument_factories: false
ignore_unannotated: false
include_if_null: true
union_discriminator: type
union_rename: none
```

To exclude generated files from coverage, you can further configure `build.yaml`.
Expand Down
Loading