diff --git a/carp_mobile_sensing/lib/carp_mobile_sensing.json.dart b/carp_mobile_sensing/lib/carp_mobile_sensing.json.dart index 67877c26..018c6633 100644 --- a/carp_mobile_sensing/lib/carp_mobile_sensing.json.dart +++ b/carp_mobile_sensing/lib/carp_mobile_sensing.json.dart @@ -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 diff --git a/carp_mobile_sensing/lib/domain.g.dart b/carp_mobile_sensing/lib/domain.g.dart index 400d83dc..4f68fba4 100644 --- a/carp_mobile_sensing/lib/domain.g.dart +++ b/carp_mobile_sensing/lib/domain.g.dart @@ -117,8 +117,8 @@ Map _$SmartphoneStudyProtocolToJson( StudyDescription _$StudyDescriptionFromJson(Map json) => StudyDescription( title: json['title'] as String, - description: json['description'] as String, - purpose: json['purpose'] as String, + description: json['description'] as String?, + purpose: json['purpose'] as String?, studyDescriptionUrl: json['studyDescriptionUrl'] as String?, privacyPolicyUrl: json['privacyPolicyUrl'] as String?, responsible: json['responsible'] == null @@ -138,8 +138,8 @@ Map _$StudyDescriptionToJson(StudyDescription instance) { writeNotNull('__type', instance.$type); val['title'] = instance.title; - val['description'] = instance.description; - val['purpose'] = instance.purpose; + writeNotNull('description', instance.description); + writeNotNull('purpose', instance.purpose); writeNotNull('studyDescriptionUrl', instance.studyDescriptionUrl); writeNotNull('privacyPolicyUrl', instance.privacyPolicyUrl); writeNotNull('responsible', instance.responsible); @@ -150,10 +150,10 @@ StudyResponsible _$StudyResponsibleFromJson(Map json) => StudyResponsible( id: json['id'] as String, name: json['name'] as String, - title: json['title'] as String, - email: json['email'] as String, - affiliation: json['affiliation'] as String, - address: json['address'] as String, + title: json['title'] as String?, + email: json['email'] as String?, + affiliation: json['affiliation'] as String?, + address: json['address'] as String?, )..$type = json['__type'] as String?; Map _$StudyResponsibleToJson(StudyResponsible instance) { @@ -168,10 +168,10 @@ Map _$StudyResponsibleToJson(StudyResponsible instance) { writeNotNull('__type', instance.$type); val['id'] = instance.id; val['name'] = instance.name; - val['title'] = instance.title; - val['email'] = instance.email; - val['address'] = instance.address; - val['affiliation'] = instance.affiliation; + writeNotNull('title', instance.title); + writeNotNull('email', instance.email); + writeNotNull('address', instance.address); + writeNotNull('affiliation', instance.affiliation); return val; } diff --git a/carp_mobile_sensing/lib/domain/study_description.dart b/carp_mobile_sensing/lib/domain/study_description.dart index f3fc4103..4020620f 100644 --- a/carp_mobile_sensing/lib/domain/study_description.dart +++ b/carp_mobile_sensing/lib/domain/study_description.dart @@ -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; @@ -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, @@ -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 diff --git a/carp_serializable/CHANGELOG.md b/carp_serializable/CHANGELOG.md index 91860e54..1f45ee69 100644 --- a/carp_serializable/CHANGELOG.md +++ b/carp_serializable/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.0.0 + +* type safe annotation in class factor method, like this; `FromJsonFactory().fromJson(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(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 diff --git a/carp_serializable/LICENSE b/carp_serializable/LICENSE index 85453020..50e4ee3c 100644 --- a/carp_serializable/LICENSE +++ b/carp_serializable/LICENSE @@ -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. diff --git a/carp_serializable/README.md b/carp_serializable/README.md index 7ef6c00c..41206e4c 100644 --- a/carp_serializable/README.md +++ b/carp_serializable/README.md @@ -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 ``` @@ -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 json) => FromJsonFactory().fromJson(json) as A; + + factory A.fromJson(Map json) => FromJsonFactory().fromJson(json); + + @override Map 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 json) => FromJsonFactory().fromJson(json) as B; + + factory B.fromJson(Map json) => FromJsonFactory().fromJson(json); + + @override Map toJson() => _$BToJson(this); } ``` @@ -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 { @@ -100,21 +102,61 @@ For example, if the class `B` above should use a different `__type` annotation, <> - 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 json) => FromJsonFactory().fromJson(json); + @override + Map 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 @@ -122,6 +164,35 @@ 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 { + + <> + + factory B.fromJson(Map json) => + FromJsonFactory().fromJson(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]. @@ -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). diff --git a/carp_serializable/example/example.dart b/carp_serializable/example/example.dart index 34053911..21948602 100644 --- a/carp_serializable/example/example.dart +++ b/carp_serializable/example/example.dart @@ -1,60 +1,96 @@ import 'package:carp_serializable/carp_serializable.dart'; import 'package:json_annotation/json_annotation.dart'; +// Auto generate json code (.g files) with: +// dart run build_runner build --delete-conflicting-outputs part 'example.g.dart'; -/// An example class. +/// A base class with default behavior. @JsonSerializable() class A extends Serializable { - int? index; + int index; - A() : super(); + A([this.index = 0]) : super(); @override Function get fromJsonFunction => _$AFromJson; factory A.fromJson(Map json) => - FromJsonFactory().fromJson(json) as A; + FromJsonFactory().fromJson(json); @override Map toJson() => _$AToJson(this); } -/// Another example class that inherits from A. -@JsonSerializable() +/// A class extending [A]. +/// +/// This class highlight three non-default behaviors: +/// * it holds a nullable string [str] which is not included in the JSON since the +/// [includeIfNull] is set to false +/// * has its own non-default [jsonType] type name +/// * provides a [notAvailable] parameter to the [fromJson] factory method which +/// specifies a default value for the class, if a fromJson method is not registered +/// in the [FromJsonFactory]. +@JsonSerializable(includeIfNull: false) class B extends A { String? str; - B() : super(); + B([super.index, this.str]) : super(); - // provide another type annotation for this class @override - String get jsonType => 'dk.cachet.$runtimeType'; + String get jsonType => 'dk.carp.$runtimeType'; @override Function get fromJsonFunction => _$BFromJson; factory B.fromJson(Map json) => - FromJsonFactory().fromJson(json) as B; + FromJsonFactory().fromJson(json, notAvailable: B(-1)); @override Map toJson() => _$BToJson(this); } +/// A class nesting another serializable class ([B]). +/// +/// Note that [explicitToJson] must be set to true in order to make a 'deep' +/// serialization to JSON via the [toJson] method. +@JsonSerializable(explicitToJson: true) +class C extends A { + B b; + + C(super.index, this.b) : super(); + + @override + Function get fromJsonFunction => _$CFromJson; + factory C.fromJson(Map json) => + FromJsonFactory().fromJson(json); + @override + Map toJson() => _$CToJson(this); +} + void main() { // remember to register the de-serialization functions in the JSON Factory FromJsonFactory().register(A()); FromJsonFactory().register(B()); + FromJsonFactory().register(C(0, B())); - A a = A(); - B b = B(); - a.index = 1; - b.str = 'abc'; + // can also register B using another jsonType + FromJsonFactory().register(B(1), type: 'B'); + + A a = A(1); + B b = B(2, 'abc'); + C c = C(3, b); print(toJsonString(a)); print(toJsonString(b)); + print(toJsonString(c)); A newA = A.fromJson(a.toJson()); B newB = B.fromJson(b.toJson()); + // Note that the following ONLY works if the C class is annotated with + // "explicitToJson" set to true. This ensures "deep" conversion to JSON. + C newC = C.fromJson(c.toJson()); + print(toJsonString(newA.toJson())); print(toJsonString(newB.toJson())); + print(toJsonString(newC.toJson())); } diff --git a/carp_serializable/example/example.g.dart b/carp_serializable/example/example.g.dart index 5c6b0666..a935966b 100644 --- a/carp_serializable/example/example.g.dart +++ b/carp_serializable/example/example.g.dart @@ -6,22 +6,42 @@ part of 'example.dart'; // JsonSerializableGenerator // ************************************************************************** -A _$AFromJson(Map json) => A() - ..$type = json['__type'] as String? - ..index = json['index'] as int?; +A _$AFromJson(Map json) => A( + (json['index'] as num?)?.toInt() ?? 0, + )..$type = json['__type'] as String?; Map _$AToJson(A instance) => { '__type': instance.$type, 'index': instance.index, }; -B _$BFromJson(Map json) => B() - ..$type = json['__type'] as String? - ..index = json['index'] as int? - ..str = json['str'] as String?; +B _$BFromJson(Map json) => B( + (json['index'] as num?)?.toInt() ?? 0, + json['str'] as String?, + )..$type = json['__type'] as String?; -Map _$BToJson(B instance) => { +Map _$BToJson(B instance) { + final val = {}; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('__type', instance.$type); + val['index'] = instance.index; + writeNotNull('str', instance.str); + return val; +} + +C _$CFromJson(Map json) => C( + (json['index'] as num).toInt(), + B.fromJson(json['b'] as Map), + )..$type = json['__type'] as String?; + +Map _$CToJson(C instance) => { '__type': instance.$type, 'index': instance.index, - 'str': instance.str, + 'b': instance.b.toJson(), }; diff --git a/carp_serializable/lib/carp_serializable.dart b/carp_serializable/lib/carp_serializable.dart index 6c85a631..3b152fa1 100644 --- a/carp_serializable/lib/carp_serializable.dart +++ b/carp_serializable/lib/carp_serializable.dart @@ -2,6 +2,7 @@ library carp_serializable; import 'dart:math' as math; import 'dart:convert'; +import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; /* @@ -35,7 +36,7 @@ import 'package:json_annotation/json_annotation.dart'; /// ```dart /// @JsonSerializable() /// class A extends Serializable { -/// int index; +/// int? index; /// /// A() : super(); /// @@ -47,7 +48,7 @@ import 'package:json_annotation/json_annotation.dart'; /// /// @JsonSerializable() /// class B extends A { -/// String str; +/// String? str; /// /// B() : super(); /// @@ -69,7 +70,7 @@ import 'package:json_annotation/json_annotation.dart'; /// FromJsonFactory().register(A()); /// ```` /// -/// For this purpose it is helpful to have an empty constructor, but any constructur +/// For this purpose it is helpful to have an empty constructor, but any constructor /// will work, since only the `fromJsonFunction` function is used. /// /// Polymorphic serialization is handled by setting the `__type` property in the @@ -152,14 +153,14 @@ class FromJsonFactory { FromJsonFactory._(); - /// Register a [Serializable] class which can be deserialized from JSON. + /// Register [serializable] as a class which can be deserialized from JSON. /// /// If [type] is specified, then this is used as the type identifier as /// specified in [CLASS_IDENTIFIER]. /// Otherwise the [Serializable] class [jsonType] is used. /// /// A type needs to be registered **before** a class can be deserialized from - /// JSON to a Flutter class. + /// JSON to a Dart class. void register(Serializable serializable, {String? type}) => _registry[type ?? serializable.jsonType] = serializable.fromJsonFunction; @@ -172,23 +173,45 @@ class FromJsonFactory { } } - /// Deserialize [json] based on its type. - Serializable? fromJson(Map json) { + /// Deserialize [json] based on its type [T]. + /// + /// Returns the deserialized object if the type [T] has been registered in + /// this factory. + /// If the type is not available (registered), [notAvailable] is returned if specified. + /// Otherwise, a [SerializationException] is thrown. + T fromJson( + Map json, { + T? notAvailable, + }) { + var message = ''; final type = json[Serializable.CLASS_IDENTIFIER]; if (!_registry.containsKey(type)) { - throw SerializationException( + message = "A 'fromJson' function was not found in the FromJsonFactory for the type '$type'. " "Register a Serializable class using the 'FromJsonFactory().register()' method." "\nIf you are using CARP Mobile Sensing, you can ensure json initialization by calling" - "'CarpMobileSensing.ensureInitialized()' as part of your main method."); + "'CarpMobileSensing.ensureInitialized()' as part of your main method."; + + if (notAvailable != null) { + debugPrint('$runtimeType - $message'); + return notAvailable; + } else { + throw SerializationException(message); + } } + var fromJson = Function.apply(_registry[type!]!, [json]); - if (fromJson is Serializable) { - return fromJson; + if (fromJson is T) return fromJson; + + message = + "The 'fromJson' function registered for the type '$type' does not return an object of the correct type ('$T'). " + "Make sure that the fromJson function returns a class of type '$T'."; + + if (notAvailable != null) { + debugPrint('$runtimeType - $message'); + return notAvailable; } else { - throw SerializationException( - "The 'fromJson' function registered for the type '$type' does not return a Serializable class. " - "Make sure that the fromJson function returns a class that inherits from the Serializable class."); + throw SerializationException(message); } } } @@ -202,22 +225,26 @@ class SerializationException implements Exception { String toString() => '$runtimeType - $message'; } -/// A connivent function to convert a Dart object into a formatted JSON string. +/// Converts [object] to a formatted JSON [String]. String toJsonString(Object? object) => const JsonEncoder.withIndent(' ').convert(object); /// A Universal Unique ID (UUID) generator. /// -/// Defaults generator function is `UUID.v1`. +/// Defaults generator function is `Uuid().v1`. /// /// Example: /// /// ```dart /// // Generate a v1 (random) id -/// var uuid = UUID.v1; +/// var uuid = Uuid().v1; /// ``` -class UUID { - static String get v1 { +class Uuid { + const Uuid(); + + /// Generates a time-based version 1 UUID. + /// By default it will generate a string based off current time. + String get v1 { math.Random random = math.Random(DateTime.now().microsecond); const hexDigits = "0123456789abcdef"; diff --git a/carp_serializable/pubspec.yaml b/carp_serializable/pubspec.yaml index e6707ae0..39ef3edc 100644 --- a/carp_serializable/pubspec.yaml +++ b/carp_serializable/pubspec.yaml @@ -1,6 +1,6 @@ name: carp_serializable description: Polymorphic JSON serialization based on json_serializable annotations. -version: 1.2.0 +version: 2.0.0 homepage: https://github.com/cph-cachet/carp.sensing-flutter environment: diff --git a/carp_serializable/test/carp_serializable_test.dart b/carp_serializable/test/carp_serializable_test.dart index 8f16fbad..1c883462 100644 --- a/carp_serializable/test/carp_serializable_test.dart +++ b/carp_serializable/test/carp_serializable_test.dart @@ -1,45 +1,36 @@ +import 'dart:convert'; import 'package:carp_serializable/carp_serializable.dart'; -import 'package:json_annotation/json_annotation.dart'; import 'package:test/test.dart'; -part 'carp_serializable_test.g.dart'; +// Using the examples for this test +import '../example/example.dart'; void main() { setUp(() { - // remember to register the de-serialization functions in the JSON Factory - FromJsonFactory().register(A()); - FromJsonFactory().register(B()); + // register the de-serialization functions in the JSON Factory + FromJsonFactory().register(A(0)); + FromJsonFactory().register(B(0)); + FromJsonFactory().register(C(0, B(0))); }); - test('A & B -> JSON w. null value', () async { - A a = A(); - B b = B(); - a.index = 1; - b.str = 'abc'; + test('A & B & C -> JSON', () async { + A a = A(1); + B b = B(2, 'abc'); + C c = C(3, b); print(toJsonString(a)); print(toJsonString(b)); + print(toJsonString(c)); }); - test('A & B -> JSON', () async { - A a = A(); - B b = B(); - a.index = 1; - - b.index = 2; - b.str = 'abc'; - - print(toJsonString(a)); + test('B -> JSON w. null value', () async { + B b = B(2); print(toJsonString(b)); }); test('JSON -> A & B', () async { - A a = A(); - B b = B(); - a.index = 1; - - b.index = 2; - b.str = 'abc'; + A a = A(1); + B b = B(2, 'abc'); A newA = A.fromJson(a.toJson()); B newB = B.fromJson(b.toJson()); @@ -53,50 +44,81 @@ void main() { print(toJsonString(newB.toJson())); }); - test('UUID - version 4.0', () async { - List list = []; - for (var i = 0; i < 5; i++) { - list.add((UUID.v1)); - list.add((UUID.v1)); - } - list.forEach(print); + test('JSON -> A & B & C', () async { + A a = A(1); + B b = B(2, 'abc'); + C c = C(2, b); - // check that all UUIDs are unique - var unique = list.toSet().toList(); - expect(unique.length, list.length); + A newA = A.fromJson(a.toJson()); + B newB = B.fromJson(b.toJson()); + + // Note that the following ONLY works if the C class is annotated with + // "explicitToJson" set to true. This ensures "deep" conversion to JSON. + C newC = C.fromJson(c.toJson()); + + expect(newA, isNot(a)); + expect(newB, isNot(b)); + expect(newC, isNot(c)); + expect(newA.index, a.index); + expect(newB.str, b.str); + expect(newC.b, isNot(b)); + expect(newC.b.index, b.index); + + print(toJsonString(newA.toJson())); + print(toJsonString(newB.toJson())); + print(toJsonString(newC.toJson())); }); -} -/// An example class. -@JsonSerializable() -class A extends Serializable { - int? index; + test('JSON string -> A & B & C', () async { + const a = '{"__type": "A", "index": 1 }'; + const b = '{"__type": "dk.carp.B", "index": 2, "str": "abc" }'; + const c = + '{"__type": "C", "index": 3, "b": {"__type": "dk.carp.B", "index": 2, "str": "abc"} }'; - A() : super(); + var newA = A.fromJson(json.decode(a) as Map); + var newB = B.fromJson(json.decode(b) as Map); + var newC = C.fromJson(json.decode(c) as Map); - @override - Function get fromJsonFunction => _$AFromJson; + expect(newA.index, 1); + expect(newB.str, 'abc'); + expect(newC.b.index, 2); + + print(toJsonString(newA.toJson())); + print(toJsonString(newB.toJson())); + print(toJsonString(newC.toJson())); + }); - factory A.fromJson(Map json) => - FromJsonFactory().fromJson(json) as A; + test('JSON string -> missing type', () async { + // in this case the B __type is wrong (should be "dk.carp.B" and not just "B") + const c = + '{"__type": "C", "index": 3, "b": {"__type": "B", "index": 2, "str": "abc"} }'; - @override - Map toJson() => _$AToJson(this); -} + var newC = C.fromJson(json.decode(c) as Map); -@JsonSerializable() -class B extends A { - String? str; + // expect -1 since this is the "notAvailable" value (see below) + expect(newC.b.index, -1); + print(toJsonString(newC.toJson())); + }); - B() : super(); + test('JSON string -> missing type throws exception', () async { + // in this case the A __type is wrong (should be "A" and not "AB") + const a = '{"__type": "AB", "index": 1 }'; - @override - String get jsonType => 'dk.cachet.$runtimeType'; + // the fromJson method should throw an exception since A does not define a "notAvailable" value + expect(() => A.fromJson(json.decode(a) as Map), + throwsA(const TypeMatcher())); + }); - @override - Function get fromJsonFunction => _$BFromJson; - factory B.fromJson(Map json) => - FromJsonFactory().fromJson(json) as B; - @override - Map toJson() => _$BToJson(this); + test('UUID - version 1.0', () async { + List list = []; + for (var i = 0; i < 5; i++) { + list.add((const Uuid().v1)); + list.add((const Uuid().v1)); + } + list.forEach(print); + + // check that all UUIDs are unique + var unique = list.toSet().toList(); + expect(unique.length, list.length); + }); } diff --git a/carp_serializable/test/carp_serializable_test.g.dart b/carp_serializable/test/carp_serializable_test.g.dart deleted file mode 100644 index 36d0c40e..00000000 --- a/carp_serializable/test/carp_serializable_test.g.dart +++ /dev/null @@ -1,27 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'carp_serializable_test.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -A _$AFromJson(Map json) => A() - ..$type = json['__type'] as String? - ..index = json['index'] as int?; - -Map _$AToJson(A instance) => { - '__type': instance.$type, - 'index': instance.index, - }; - -B _$BFromJson(Map json) => B() - ..$type = json['__type'] as String? - ..index = json['index'] as int? - ..str = json['str'] as String?; - -Map _$BToJson(B instance) => { - '__type': instance.$type, - 'index': instance.index, - 'str': instance.str, - }; diff --git a/packages/carp_health_package/CHANGELOG.md b/packages/carp_health_package/CHANGELOG.md index 73819fbd..6e72ad56 100755 --- a/packages/carp_health_package/CHANGELOG.md +++ b/packages/carp_health_package/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.11.0 + +* upgrading to health v. 11.x + ## 2.10.0 * upgrading to carp_mobile_sensing v. 1.9.0 (better permission handling) diff --git a/packages/carp_health_package/lib/health_package.g.dart b/packages/carp_health_package/lib/health_package.g.dart index 1bb09b52..c5558275 100644 --- a/packages/carp_health_package/lib/health_package.g.dart +++ b/packages/carp_health_package/lib/health_package.g.dart @@ -43,6 +43,7 @@ Map _$HealthSamplingConfigurationToJson( const _$HealthDataTypeEnumMap = { HealthDataType.ACTIVE_ENERGY_BURNED: 'ACTIVE_ENERGY_BURNED', + HealthDataType.ATRIAL_FIBRILLATION_BURDEN: 'ATRIAL_FIBRILLATION_BURDEN', HealthDataType.AUDIOGRAM: 'AUDIOGRAM', HealthDataType.BASAL_ENERGY_BURNED: 'BASAL_ENERGY_BURNED', HealthDataType.BLOOD_GLUCOSE: 'BLOOD_GLUCOSE', @@ -52,14 +53,51 @@ const _$HealthDataTypeEnumMap = { HealthDataType.BODY_FAT_PERCENTAGE: 'BODY_FAT_PERCENTAGE', HealthDataType.BODY_MASS_INDEX: 'BODY_MASS_INDEX', HealthDataType.BODY_TEMPERATURE: 'BODY_TEMPERATURE', + HealthDataType.BODY_WATER_MASS: 'BODY_WATER_MASS', HealthDataType.DIETARY_CARBS_CONSUMED: 'DIETARY_CARBS_CONSUMED', + HealthDataType.DIETARY_CAFFEINE: 'DIETARY_CAFFEINE', HealthDataType.DIETARY_ENERGY_CONSUMED: 'DIETARY_ENERGY_CONSUMED', HealthDataType.DIETARY_FATS_CONSUMED: 'DIETARY_FATS_CONSUMED', HealthDataType.DIETARY_PROTEIN_CONSUMED: 'DIETARY_PROTEIN_CONSUMED', + HealthDataType.DIETARY_FIBER: 'DIETARY_FIBER', + HealthDataType.DIETARY_SUGAR: 'DIETARY_SUGAR', + HealthDataType.DIETARY_FAT_MONOUNSATURATED: 'DIETARY_FAT_MONOUNSATURATED', + HealthDataType.DIETARY_FAT_POLYUNSATURATED: 'DIETARY_FAT_POLYUNSATURATED', + HealthDataType.DIETARY_FAT_SATURATED: 'DIETARY_FAT_SATURATED', + HealthDataType.DIETARY_CHOLESTEROL: 'DIETARY_CHOLESTEROL', + HealthDataType.DIETARY_VITAMIN_A: 'DIETARY_VITAMIN_A', + HealthDataType.DIETARY_THIAMIN: 'DIETARY_THIAMIN', + HealthDataType.DIETARY_RIBOFLAVIN: 'DIETARY_RIBOFLAVIN', + HealthDataType.DIETARY_NIACIN: 'DIETARY_NIACIN', + HealthDataType.DIETARY_PANTOTHENIC_ACID: 'DIETARY_PANTOTHENIC_ACID', + HealthDataType.DIETARY_VITAMIN_B6: 'DIETARY_VITAMIN_B6', + HealthDataType.DIETARY_BIOTIN: 'DIETARY_BIOTIN', + HealthDataType.DIETARY_VITAMIN_B12: 'DIETARY_VITAMIN_B12', + HealthDataType.DIETARY_VITAMIN_C: 'DIETARY_VITAMIN_C', + HealthDataType.DIETARY_VITAMIN_D: 'DIETARY_VITAMIN_D', + HealthDataType.DIETARY_VITAMIN_E: 'DIETARY_VITAMIN_E', + HealthDataType.DIETARY_VITAMIN_K: 'DIETARY_VITAMIN_K', + HealthDataType.DIETARY_FOLATE: 'DIETARY_FOLATE', + HealthDataType.DIETARY_CALCIUM: 'DIETARY_CALCIUM', + HealthDataType.DIETARY_CHLORIDE: 'DIETARY_CHLORIDE', + HealthDataType.DIETARY_IRON: 'DIETARY_IRON', + HealthDataType.DIETARY_MAGNESIUM: 'DIETARY_MAGNESIUM', + HealthDataType.DIETARY_PHOSPHORUS: 'DIETARY_PHOSPHORUS', + HealthDataType.DIETARY_POTASSIUM: 'DIETARY_POTASSIUM', + HealthDataType.DIETARY_SODIUM: 'DIETARY_SODIUM', + HealthDataType.DIETARY_ZINC: 'DIETARY_ZINC', + HealthDataType.DIETARY_CHROMIUM: 'DIETARY_CHROMIUM', + HealthDataType.DIETARY_COPPER: 'DIETARY_COPPER', + HealthDataType.DIETARY_IODINE: 'DIETARY_IODINE', + HealthDataType.DIETARY_MANGANESE: 'DIETARY_MANGANESE', + HealthDataType.DIETARY_MOLYBDENUM: 'DIETARY_MOLYBDENUM', + HealthDataType.DIETARY_SELENIUM: 'DIETARY_SELENIUM', HealthDataType.FORCED_EXPIRATORY_VOLUME: 'FORCED_EXPIRATORY_VOLUME', HealthDataType.HEART_RATE: 'HEART_RATE', HealthDataType.HEART_RATE_VARIABILITY_SDNN: 'HEART_RATE_VARIABILITY_SDNN', + HealthDataType.HEART_RATE_VARIABILITY_RMSSD: 'HEART_RATE_VARIABILITY_RMSSD', HealthDataType.HEIGHT: 'HEIGHT', + HealthDataType.INSULIN_DELIVERY: 'INSULIN_DELIVERY', HealthDataType.RESTING_HEART_RATE: 'RESTING_HEART_RATE', HealthDataType.RESPIRATORY_RATE: 'RESPIRATORY_RATE', HealthDataType.PERIPHERAL_PERFUSION_INDEX: 'PERIPHERAL_PERFUSION_INDEX', @@ -68,19 +106,22 @@ const _$HealthDataTypeEnumMap = { HealthDataType.WALKING_HEART_RATE: 'WALKING_HEART_RATE', HealthDataType.WEIGHT: 'WEIGHT', HealthDataType.DISTANCE_WALKING_RUNNING: 'DISTANCE_WALKING_RUNNING', + HealthDataType.DISTANCE_SWIMMING: 'DISTANCE_SWIMMING', + HealthDataType.DISTANCE_CYCLING: 'DISTANCE_CYCLING', HealthDataType.FLIGHTS_CLIMBED: 'FLIGHTS_CLIMBED', - HealthDataType.MOVE_MINUTES: 'MOVE_MINUTES', HealthDataType.DISTANCE_DELTA: 'DISTANCE_DELTA', HealthDataType.MINDFULNESS: 'MINDFULNESS', HealthDataType.WATER: 'WATER', - HealthDataType.SLEEP_IN_BED: 'SLEEP_IN_BED', HealthDataType.SLEEP_ASLEEP: 'SLEEP_ASLEEP', + HealthDataType.SLEEP_AWAKE_IN_BED: 'SLEEP_AWAKE_IN_BED', HealthDataType.SLEEP_AWAKE: 'SLEEP_AWAKE', - HealthDataType.SLEEP_LIGHT: 'SLEEP_LIGHT', HealthDataType.SLEEP_DEEP: 'SLEEP_DEEP', - HealthDataType.SLEEP_REM: 'SLEEP_REM', + HealthDataType.SLEEP_IN_BED: 'SLEEP_IN_BED', + HealthDataType.SLEEP_LIGHT: 'SLEEP_LIGHT', HealthDataType.SLEEP_OUT_OF_BED: 'SLEEP_OUT_OF_BED', + HealthDataType.SLEEP_REM: 'SLEEP_REM', HealthDataType.SLEEP_SESSION: 'SLEEP_SESSION', + HealthDataType.SLEEP_UNKNOWN: 'SLEEP_UNKNOWN', HealthDataType.EXERCISE_TIME: 'EXERCISE_TIME', HealthDataType.WORKOUT: 'WORKOUT', HealthDataType.HEADACHE_NOT_PRESENT: 'HEADACHE_NOT_PRESENT', @@ -89,11 +130,16 @@ const _$HealthDataTypeEnumMap = { HealthDataType.HEADACHE_SEVERE: 'HEADACHE_SEVERE', HealthDataType.HEADACHE_UNSPECIFIED: 'HEADACHE_UNSPECIFIED', HealthDataType.NUTRITION: 'NUTRITION', + HealthDataType.GENDER: 'GENDER', + HealthDataType.BIRTH_DATE: 'BIRTH_DATE', + HealthDataType.BLOOD_TYPE: 'BLOOD_TYPE', + HealthDataType.MENSTRUATION_FLOW: 'MENSTRUATION_FLOW', HealthDataType.HIGH_HEART_RATE_EVENT: 'HIGH_HEART_RATE_EVENT', HealthDataType.LOW_HEART_RATE_EVENT: 'LOW_HEART_RATE_EVENT', HealthDataType.IRREGULAR_HEART_RATE_EVENT: 'IRREGULAR_HEART_RATE_EVENT', HealthDataType.ELECTRODERMAL_ACTIVITY: 'ELECTRODERMAL_ACTIVITY', HealthDataType.ELECTROCARDIOGRAM: 'ELECTROCARDIOGRAM', + HealthDataType.TOTAL_CALORIES_BURNED: 'TOTAL_CALORIES_BURNED', }; HealthData _$HealthDataFromJson(Map json) => HealthData( diff --git a/packages/carp_health_package/pubspec.yaml b/packages/carp_health_package/pubspec.yaml index c52c9059..2a4ee2fc 100755 --- a/packages/carp_health_package/pubspec.yaml +++ b/packages/carp_health_package/pubspec.yaml @@ -1,6 +1,6 @@ name: carp_health_package description: CARP health sampling package. Samples health data from Apple Health or Google Fit. -version: 2.10.0 +version: 2.11.0 homepage: https://github.com/cph-cachet/carp.sensing-flutter/tree/master/packages/carp_health_package environment: @@ -15,7 +15,7 @@ dependencies: carp_core: ^1.4.0 carp_mobile_sensing: ^1.9.0 - health: ^10.0.0 + health: ^11.0.0 json_annotation: ^4.8.0 uuid: '>=3.0.1 <5.0.0'