Skip to content
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

Serialization v. 2.0 (type safe + graceful error handling) #417

Merged
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
15 changes: 2 additions & 13 deletions carp_mobile_sensing/lib/carp_mobile_sensing.json.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,11 @@ void _registerFromJsonFunctions() {

// Protocol classes
FromJsonFactory().registerAll([
StudyResponsible(
id: '',
title: '',
address: '',
affiliation: '',
email: '',
name: '',
),
StudyResponsible(id: '', name: ''),
DataEndPoint(type: ''),
FileDataEndPoint(),
SQLiteDataEndPoint(),
StudyDescription(
title: '',
description: '',
purpose: '',
)
StudyDescription(title: '')
]);

// Task classes
Expand Down
24 changes: 12 additions & 12 deletions carp_mobile_sensing/lib/domain.g.dart

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

24 changes: 12 additions & 12 deletions carp_mobile_sensing/lib/domain/study_description.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ class StudyDescription extends Serializable {
String title;

/// The description of this study.
String description;
String? description;

/// The purpose of the study. To be used to inform the user about
/// this study and its purpose.
String purpose;
String? purpose;

/// The URL pointing to a web page description of this study.
String? studyDescriptionUrl;
Expand All @@ -30,8 +30,8 @@ class StudyDescription extends Serializable {

StudyDescription({
required this.title,
required this.description,
required this.purpose,
this.description,
this.purpose,
this.studyDescriptionUrl,
this.privacyPolicyUrl,
this.responsible,
Expand All @@ -55,18 +55,18 @@ class StudyDescription extends Serializable {
class StudyResponsible extends Serializable {
String id;
String name;
String title;
String email;
String address;
String affiliation;
String? title;
String? email;
String? address;
String? affiliation;

StudyResponsible({
required this.id,
required this.name,
required this.title,
required this.email,
required this.affiliation,
required this.address,
this.title,
this.email,
this.affiliation,
this.address,
});

@override
Expand Down
8 changes: 8 additions & 0 deletions carp_serializable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 2.0.0

* type safe annotation in class factor method, like this; `FromJsonFactory().fromJson<A>(json)`
* graceful handling of errors when a non-known JSON type is encountered by allowing for a "notAvailable" parameter to the fromJson factory method, like this; `FromJsonFactory().fromJson<B>(json, notAvailable: B(-1))`
* refactor of universal unique IDs (UUIDs) to using the `Uuid().v1` construct
* extending unit test coverage (incl., e.g. exceptions)
* improvement to examples in the `example.dart` file and documentation in the API doc and README

## 1.2.0

* added support for generating universal unique IDs (UUIDs) via the `UUID.v1` construct
Expand Down
16 changes: 4 additions & 12 deletions carp_serializable/LICENSE
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
MIT License.

Copyright 2022 Copenhagen Center for Health Technology (CACHET) at the Technical University of Denmark (DTU).
Copyright 2024 the Technical University of Denmark (DTU).

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the ”Software”), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ”Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED ”AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED ”AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
113 changes: 92 additions & 21 deletions carp_serializable/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,10 @@ To use this package, add [`carp_serializable`](https://pub.dev/packages/carp_ser

```yaml
dependencies:
flutter:
sdk: flutter
json_annotation: ^latest
carp_serializable: ^latest

dev_dependencies:
flutter_test:
sdk: flutter
build_runner: any # For building json serialization
json_serializable: any
```
Expand All @@ -45,21 +41,29 @@ Below is a simple example of two classes `A` and `B` where B extends A.
class A extends Serializable {
int index;

A() : super();
A([this.index = 0]) : super();

@override
Function get fromJsonFunction => _$AFromJson;
factory A.fromJson(Map<String, dynamic> json) => FromJsonFactory().fromJson(json) as A;

factory A.fromJson(Map<String, dynamic> json) => FromJsonFactory().fromJson<A>(json);

@override
Map<String, dynamic> toJson() => _$AToJson(this);
}

@JsonSerializable()
@JsonSerializable(includeIfNull: false)
class B extends A {
String str;
String? str;

B() : super();
B([super.index, this.str]) : super();

@override
Function get fromJsonFunction => _$BFromJson;
factory B.fromJson(Map<String, dynamic> json) => FromJsonFactory().fromJson(json) as B;

factory B.fromJson(Map<String, dynamic> json) => FromJsonFactory().fromJson<B>(json);

@override
Map<String, dynamic> toJson() => _$BToJson(this);
}
```
Expand All @@ -69,14 +73,12 @@ Note that the naming of the `fromJson()` and `toJson()` functions follows the [j
The `fromJsonFunction` must be registered on app startup (before use of de-serialization) in the `FromJsonFactory` singleton, like this:

```dart
FromJsonFactory().register(A());
FromJsonFactory().register(A());
```

For this purpose it is helpful to have an empty constructor, but any constructor will work, since only the `fromJsonFunction` function is used.
For this purpose it is helpful to have an empty constructor, but any constructor will work, since only getting the `fromJsonFunction` function is used during registration.

Polymorphic serialization is handled by setting the `__type` property in the `Serializable` class. Per default, an object's `runtimeType` is used as the
`__type` for an object. Hence, the json of object of type `A` and `B` would
look like this:
Polymorphic serialization is handled by setting the `__type` property in the `Serializable` class. Per default, an object's `runtimeType` is used as the `__type` for an object. Hence, the JSON of objects of type `A` and `B` would look like this:

```json
{
Expand All @@ -100,28 +102,97 @@ For example, if the class `B` above should use a different `__type` annotation,

<<as above>>

String get jsonType => 'dk.cachet.$runtimeType';
String get jsonType => 'dk.carp.$runtimeType';
}
````

In which case the json would look like:
In which case the JSON would look like:

```json
{
"__type": "dk.cachet.B",
"__type": "dk.carp.B",
"index": 2
"str": "abc"
}
```

Once the serialization code is used as above, run the
You can also create nested classes, like this class `C`:

```dart
@JsonSerializable(explicitToJson: true)
class C extends A {
B b;

C(super.index, this.b) : super();

@override
Function get fromJsonFunction => _$CFromJson;
factory C.fromJson(Map<String, dynamic> json) => FromJsonFactory().fromJson<C>(json);
@override
Map<String, dynamic> toJson() => _$CToJson(this);
}
````

The following statement;

```dart
B b = B(2, 'abc');
C c = C(3, b);
```

will generate json like this:

```json
{
"__type": "C",
"index": 3,
"b": {
"__type": "dk.carp.B",
"index": 2,
"str": "abc"
}
}
```

> Note that in order to support "deep" or nested toJson serialization, you need to annotate the class with `@JsonSerializable(explicitToJson: true)`.

Once the serialization code is written as above, run the

```shell
flutter pub run build_runner build --delete-conflicting-outputs
```

command as usual to generate the `toJson()` and `fromJson()` methods.

## Exception Handling

When trying to deserialize an object from JSON, this package looks up the `fromJson` function in the `FromJsonFactory`. In case the type is not found - either because it is unknown or has not been registered - an `SerializationException` will be thrown.

In order to avoid an exception, you can specify a default object to use in this exception case by specifying a `notAvailable` parameter to the `fromJson` method, like this:

```dart
class B extends A {

<<as above>>

factory B.fromJson(Map<String, dynamic> json) =>
FromJsonFactory().fromJson<B>(json, notAvailable: B(-1));
}
```

In case a deserialization method for B is not found, then the object `B(-1)` is returned. This will not be the "correct" object, but at least the serialization is not stopped. This is useful in deserialization of large, nested JSON.

## Universal Unique IDs

Often in serialization, there is a need to generate or use unique IDs. Hence, the package also support the generation of a simple time-based Universal Unique ID (UUID):

```dart
// Generate a v1 (time-based) id
var uuid = Uuid().v1;
```

Note, however, that this UUID is very simple. If you need more sophisticated UUIDs, use the [uuid](https://pub.dev/packages/uuid) package.

## Features and bugs

Please read about existing issues and file new feature requests and bug reports at the [issue tracker][tracker].
Expand All @@ -130,5 +201,5 @@ Please read about existing issues and file new feature requests and bug reports

## License

This software is copyright (c) [Copenhagen Center for Health Technology (CACHET)](https://www.cachet.dk/) at the [Technical University of Denmark (DTU)](https://www.dtu.dk).
This software is available 'as-is' under a [MIT license](https://github.com/cph-cachet/carp.sensing-flutter/blob/master/LICENSE).
This software is copyright (c) the [Technical University of Denmark (DTU)](https://www.dtu.dk) and is part of the [Copenhagen Research Platform](https://carp.cachet.dk/).
This software is available 'as-is' under a [MIT license](LICENSE).
Loading
Loading