diff --git a/packages/body_parser/README.md b/packages/body_parser/README.md index b35781b..a7cb453 100644 --- a/packages/body_parser/README.md +++ b/packages/body_parser/README.md @@ -1,6 +1,6 @@ # Belatuk Body Parser -[![version](https://img.shields.io/badge/pub-v3.0.1-brightgreen)](https://pub.dev/packages/belatuk_body_parser) +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/belatuk_body_parser?include_prereleases) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/body_parser/LICENSE) diff --git a/packages/code_buffer/README.md b/packages/code_buffer/README.md index dc23b12..f8f9896 100644 --- a/packages/code_buffer/README.md +++ b/packages/code_buffer/README.md @@ -1,6 +1,6 @@ # Belatuk Code Buffer -[![version](https://img.shields.io/badge/pub-v3.0.2-brightgreen)](https://pub.dev/packages/belatuk_code_buffer) +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/belatuk_code_buffer?include_prereleases) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/code_buffer/LICENSE) diff --git a/packages/combinator/README.md b/packages/combinator/README.md index 2f2051e..8ed0b42 100644 --- a/packages/combinator/README.md +++ b/packages/combinator/README.md @@ -1,6 +1,6 @@ # Belatuk Combinator -[![version](https://img.shields.io/badge/pub-v3.0.1-brightgreen)](https://pub.dev/packages/belatuk_combinator) +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/belatuk_combinator?include_prereleases) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/combinator/LICENSE) diff --git a/packages/html_builder/README.md b/packages/html_builder/README.md index 8e733eb..5850767 100644 --- a/packages/html_builder/README.md +++ b/packages/html_builder/README.md @@ -1,6 +1,6 @@ # Betaluk Html Builder -[![version](https://img.shields.io/badge/pub-v3.0.2-brightgreen)](https://pub.dev/packages/belatuk_html_builder) +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/belatuk_html_builder?include_prereleases) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/html_builder/LICENSE) diff --git a/packages/json_serializer/.travis.yml b/packages/json_serializer/.travis.yml new file mode 100644 index 0000000..a9e2c10 --- /dev/null +++ b/packages/json_serializer/.travis.yml @@ -0,0 +1,4 @@ +language: dart +dart: + - dev + - stable \ No newline at end of file diff --git a/packages/json_serializer/AUTHORS.md b/packages/json_serializer/AUTHORS.md new file mode 100644 index 0000000..ac95ab5 --- /dev/null +++ b/packages/json_serializer/AUTHORS.md @@ -0,0 +1,12 @@ +Primary Authors +=============== + +* __[Thomas Hii](dukefirehawk.apps@gmail.com)__ + + Thomas is the current maintainer of the code base. He has refactored and migrated the + code base to support NNBD. + +* __[Tobe O](thosakwe@gmail.com)__ + + Tobe has written much of the original code prior to NNBD migration. He has moved on and + is no longer involved with the project. diff --git a/packages/json_serializer/CHANGELOG.md b/packages/json_serializer/CHANGELOG.md new file mode 100644 index 0000000..b7ca85d --- /dev/null +++ b/packages/json_serializer/CHANGELOG.md @@ -0,0 +1,40 @@ +# Change Log + +## 5.0.0 + +* Added `lints` linter +* Removed deprecated parameters +* Published as `belatuk_json_serializer` package + +## 4.0.3 + +* Fixed static analysis errors + +## 4.0.2 + +* Updated pubspec description +* Fixed static analysis errors + +## 4.0.1 + +* Added example +* Updated README + +## 4.0.0 + +* Migrated to support Dart SDK 2.12.x NNBD + +## 3.0.0 + +* Migrated to work with Dart SDK 2.12.x Non NNBD + +## 2.0.0-beta+3 + +* Long-needed updates, ensured Dart 2 compatibility, fixed DDC breakages. +* Patches for reflection bugs with typing. + +## 2.0.0-beta+2 + +* This version breaks in certain Dart versions (likely anything *after* `2.0.0-dev.59.0`) +until is resolved. +* Removes the reference to `Schema` class. diff --git a/packages/json_serializer/LICENSE b/packages/json_serializer/LICENSE new file mode 100644 index 0000000..e37a346 --- /dev/null +++ b/packages/json_serializer/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2021, dukefirehawk.com +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/packages/json_serializer/README.md b/packages/json_serializer/README.md new file mode 100644 index 0000000..df4e595 --- /dev/null +++ b/packages/json_serializer/README.md @@ -0,0 +1,107 @@ +# Belatuk JSON Serializer + +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/json_serializer?include_prereleases) +[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) +[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) +[![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/json_serializer/LICENSE) + +**Replacement of `package:json_god` with breaking changes to support NNBD.** + +The ***new and improved*** definitive solution for JSON in Dart. It supports synchronously transform an object into a JSON string and also deserialize a JSON string back into an instance of any type. + +## Installation + + dependencies: + belatuk_json_serializer: ^5.0.0 + +## Usage + +It is recommended to import the library under an alias, i.e., `jsonSerializer`. + +```dart +import 'package:belatuk_json_serialization/belatuk_json_serialization.dart' as jsonSerializer; +``` + +## Serializing JSON + +Simply call `jsonSerializer.serialize(x)` to synchronously transform an object into a JSON +string. + +```dart +Map map = {"foo": "bar", "numbers": [1, 2, {"three": 4}]}; + +// Output: {"foo":"bar","numbers":[1,2,{"three":4]"} +String json = jsonSerializer.serialize(map); +print(json); +``` + +You can easily serialize classes, too. Belatuk JSON Serializer also supports classes as members. + +```dart + +class A { + String foo; + A(this.foo); +} + +class B { + late String hello; + late A nested; + B(String hello, String foo) { + this.hello = hello; + this.nested = A(foo); + } +} + +main() { + print(jsonSerializer.serialize( B("world", "bar"))); +} + +// Output: {"hello":"world","nested":{"foo":"bar"}} +``` + +If a class has a `toJson` method, it will be called instead. + +## Deserializing JSON + +Deserialization is equally easy, and is provided through `jsonSerializer.deserialize`. + +```dart +Map map = jsonSerializer.deserialize('{"hello":"world"}'); +int three = jsonSerializer.deserialize("3"); +``` + +### Deserializing to Classes + +Belatuk JSON Serializer lets you deserialize JSON into an instance of any type. Simply pass the type as the second argument to `jsonSerializer.deserialize`. + +If the class has a `fromJson` constructor, it will be called instead. + +```dart +class Child { + String foo; +} + +class Parent { + String hello; + Child child = Child(); +} + +main() { + Parent parent = jsonSerializer.deserialize('{"hello":"world","child":{"foo":"bar"}}', Parent); + print(parent); +} +``` + +**Any JSON-deserializable classes must initializable without parameters. If `Foo()` would throw an error, then you can't use Foo with JSON.** + +This allows for validation of a sort, as only fields you have declared will be accepted. + +```dart +class HasAnInt { int theInt; } + +HasAnInt invalid = jsonSerializer.deserialize('["some invalid input"]', HasAnInt); +// Throws an error +``` + +An exception will be thrown if validation fails. diff --git a/packages/json_serializer/analysis_options.yaml b/packages/json_serializer/analysis_options.yaml new file mode 100644 index 0000000..ea2c9e9 --- /dev/null +++ b/packages/json_serializer/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml \ No newline at end of file diff --git a/packages/json_serializer/example/main.dart b/packages/json_serializer/example/main.dart new file mode 100644 index 0000000..d26bb1b --- /dev/null +++ b/packages/json_serializer/example/main.dart @@ -0,0 +1,18 @@ +import 'package:belatuk_json_serializer/belatuk_json_serializer.dart' as god; + +class A { + String foo; + A(this.foo); +} + +class B { + String hello; + late A nested; + B(this.hello, String foo) { + nested = A(foo); + } +} + +void main() { + print(god.serialize(B("world", "bar"))); +} diff --git a/packages/json_serializer/lib/belatuk_json_serializer.dart b/packages/json_serializer/lib/belatuk_json_serializer.dart new file mode 100644 index 0000000..194f285 --- /dev/null +++ b/packages/json_serializer/lib/belatuk_json_serializer.dart @@ -0,0 +1,18 @@ +/// A robust library for JSON serialization and deserialization. +library belatuk_json_serializer; + +//import 'package:dart2_constant/convert.dart'; +import 'dart:convert'; +import 'package:logging/logging.dart'; +import 'src/reflection.dart' as reflection; + +part 'src/serialize.dart'; +part 'src/deserialize.dart'; +part 'src/validation.dart'; +part 'src/util.dart'; + +/// Instead, listen to [logger]. +//@deprecated +//bool debug = false; + +final Logger logger = Logger('belatuk_json_serializer'); diff --git a/packages/json_serializer/lib/src/deserialize.dart b/packages/json_serializer/lib/src/deserialize.dart new file mode 100644 index 0000000..35cc5ef --- /dev/null +++ b/packages/json_serializer/lib/src/deserialize.dart @@ -0,0 +1,44 @@ +part of belatuk_json_serializer; + +/// Deserializes a JSON string into a Dart datum. +/// +/// You can also provide an output Type to attempt to serialize the JSON into. +deserialize(String json, {Type? outputType}) { + var deserialized = deserializeJson(json, outputType: outputType); + logger.info("Deserialization result: $deserialized"); + return deserialized; +} + +/// Deserializes JSON into data, without validating it. +deserializeJson(String s, {Type? outputType}) { + logger.info("Deserializing the following JSON: $s"); + + if (outputType == null) { + logger + .info("No output type was specified, so we are just using json.decode"); + return json.decode(s); + } else { + logger.info("Now deserializing to type: $outputType"); + return deserializeDatum(json.decode(s), outputType: outputType); + } +} + +/// Deserializes some JSON-serializable value into a usable Dart value. +deserializeDatum(value, {Type? outputType}) { + if (outputType != null) { + return reflection.deserialize(value, outputType, deserializeDatum); + } else if (value is List) { + logger.info("Deserializing this List: $value"); + return value.map(deserializeDatum).toList(); + } else if (value is Map) { + logger.info("Deserializing this Map: $value"); + Map result = {}; + value.forEach((k, v) { + result[k] = deserializeDatum(v); + }); + return result; + } else if (_isPrimitive(value)) { + logger.info("Value $value is a primitive"); + return value; + } +} diff --git a/packages/json_serializer/lib/src/reflection.dart b/packages/json_serializer/lib/src/reflection.dart new file mode 100644 index 0000000..72bcbbc --- /dev/null +++ b/packages/json_serializer/lib/src/reflection.dart @@ -0,0 +1,188 @@ +library belatuk_json_serializer.reflection; + +import 'dart:mirrors'; +import '../belatuk_json_serializer.dart'; + +const Symbol hashCodeSymbol = #hashCode; +const Symbol runtimeTypeSymbol = #runtimeType; + +typedef Serializer = dynamic Function(dynamic value); +typedef Deserializer = dynamic Function(dynamic value, {Type? outputType}); + +List _findGetters(ClassMirror classMirror) { + List result = []; + + classMirror.instanceMembers + .forEach((Symbol symbol, MethodMirror methodMirror) { + if (methodMirror.isGetter && + symbol != hashCodeSymbol && + symbol != runtimeTypeSymbol) { + logger.info("Found getter on instance: $symbol"); + result.add(symbol); + } + }); + + return result; +} + +serialize(value, Serializer serializer) { + logger.info("Serializing this value via reflection: $value"); + Map result = {}; + InstanceMirror instanceMirror = reflect(value); + ClassMirror classMirror = instanceMirror.type; + + // Check for toJson + for (Symbol symbol in classMirror.instanceMembers.keys) { + if (symbol == #toJson) { + logger.info("Running toJson..."); + var result = instanceMirror.invoke(symbol, []).reflectee; + logger.info("Result of serialization via reflection: $result"); + return result; + } + } + + for (Symbol symbol in _findGetters(classMirror)) { + String name = MirrorSystem.getName(symbol); + var valueForSymbol = instanceMirror.getField(symbol).reflectee; + + try { + result[name] = serializer(valueForSymbol); + logger.info("Set $name to $valueForSymbol"); + } catch (e, st) { + logger.severe("Could not set $name to $valueForSymbol", e, st); + } + } + + logger.info("Result of serialization via reflection: $result"); + + return result; +} + +deserialize(value, Type outputType, Deserializer deserializer) { + logger.info("About to deserialize $value to a $outputType"); + + try { + if (value is List) { + List typeArguments = reflectType(outputType).typeArguments; + + Iterable it; + + if (typeArguments.isEmpty) { + it = value.map(deserializer); + } else { + it = value.map((item) => + deserializer(item, outputType: typeArguments[0].reflectedType)); + } + + if (typeArguments.isEmpty) return it.toList(); + logger.info( + 'Casting list elements to ${typeArguments[0].reflectedType} via List.from'); + + var mirror = reflectType(List, [typeArguments[0].reflectedType]); + + if (mirror is ClassMirror) { + var output = mirror.newInstance(#from, [it]).reflectee; + logger.info('Casted list type: ${output.runtimeType}'); + return output; + } else { + throw ArgumentError( + '${typeArguments[0].reflectedType} is not a class.'); + } + } else if (value is Map) { + return _deserializeFromJsonByReflection(value, deserializer, outputType); + } else { + return deserializer(value); + } + } catch (e, st) { + logger.severe('Deserialization failed.', e, st); + rethrow; + } +} + +/// Uses mirrors to deserialize an object. +_deserializeFromJsonByReflection( + data, Deserializer deserializer, Type outputType) { + // Check for fromJson + var typeMirror = reflectType(outputType); + + if (typeMirror is! ClassMirror) { + throw ArgumentError('$outputType is not a class.'); + } + + var type = typeMirror; + var fromJson = Symbol('${MirrorSystem.getName(type.simpleName)}.fromJson'); + + for (Symbol symbol in type.declarations.keys) { + if (symbol == fromJson) { + var decl = type.declarations[symbol]; + + if (decl is MethodMirror && decl.isConstructor) { + logger.info("Running fromJson..."); + var result = type.newInstance(#fromJson, [data]).reflectee; + + logger.info("Result of deserialization via reflection: $result"); + return result; + } + } + } + + ClassMirror classMirror = type; + InstanceMirror instanceMirror = classMirror.newInstance(Symbol(""), []); + + if (classMirror.isSubclassOf(reflectClass(Map))) { + var typeArguments = classMirror.typeArguments; + + if (typeArguments.isEmpty || + classMirror.typeArguments + .every((t) => t == currentMirrorSystem().dynamicType)) { + return data; + } else { + var mapType = + reflectType(Map, typeArguments.map((t) => t.reflectedType).toList()) + as ClassMirror; + logger.info('Casting this map $data to Map of [$typeArguments]'); + var output = mapType.newInstance(Symbol(''), []).reflectee; + + for (var key in data.keys) { + output[key] = data[key]; + } + + logger.info('Output: $output of type ${output.runtimeType}'); + return output; + } + } else { + data.keys.forEach((key) { + try { + logger.info("Now deserializing value for $key"); + logger.info("data[\"$key\"] = ${data[key]}"); + var deserializedValue = deserializer(data[key]); + + logger.info( + "I want to set $key to the following ${deserializedValue.runtimeType}: $deserializedValue"); + // Get target type of getter + Symbol searchSymbol = Symbol(key.toString()); + Symbol symbolForGetter = classMirror.instanceMembers.keys + .firstWhere((x) => x == searchSymbol); + Type requiredType = classMirror + .instanceMembers[symbolForGetter]!.returnType.reflectedType; + if (data[key].runtimeType != requiredType) { + logger.info("Currently, $key is a ${data[key].runtimeType}."); + logger.info("However, $key must be a $requiredType."); + + deserializedValue = + deserializer(deserializedValue, outputType: requiredType); + } + + logger.info( + "Final deserialized value for $key: $deserializedValue <${deserializedValue.runtimeType}>"); + instanceMirror.setField(Symbol(key.toString()), deserializedValue); + + logger.info("Success! $key has been set to $deserializedValue"); + } catch (e, st) { + logger.severe('Could not set value for field $key.', e, st); + } + }); + } + + return instanceMirror.reflectee; +} diff --git a/packages/json_serializer/lib/src/serialize.dart b/packages/json_serializer/lib/src/serialize.dart new file mode 100644 index 0000000..04251a0 --- /dev/null +++ b/packages/json_serializer/lib/src/serialize.dart @@ -0,0 +1,36 @@ +part of belatuk_json_serializer; + +/// Serializes any arbitrary Dart datum to JSON. Supports schema validation. +String serialize(value) { + var serialized = serializeObject(value); + logger.info('Serialization result: $serialized'); + return json.encode(serialized); +} + +/// Transforms any Dart datum into a value acceptable to json.encode. +serializeObject(value) { + if (_isPrimitive(value)) { + logger.info("Serializing primitive value: $value"); + return value; + } else if (value is DateTime) { + logger.info("Serializing this DateTime: $value"); + return value.toIso8601String(); + } else if (value is Iterable) { + logger.info("Serializing this Iterable: $value"); + return value.map(serializeObject).toList(); + } else if (value is Map) { + logger.info("Serializing this Map: $value"); + return serializeMap(value); + } else { + return serializeObject(reflection.serialize(value, serializeObject)); + } +} + +/// Recursively transforms a Map and its children into JSON-serializable data. +Map serializeMap(Map value) { + Map outputMap = {}; + value.forEach((key, value) { + outputMap[key] = serializeObject(value); + }); + return outputMap; +} diff --git a/packages/json_serializer/lib/src/util.dart b/packages/json_serializer/lib/src/util.dart new file mode 100644 index 0000000..18a7f87 --- /dev/null +++ b/packages/json_serializer/lib/src/util.dart @@ -0,0 +1,5 @@ +part of belatuk_json_serializer; + +bool _isPrimitive(value) { + return value is num || value is bool || value is String || value == null; +} diff --git a/packages/json_serializer/lib/src/validation.dart b/packages/json_serializer/lib/src/validation.dart new file mode 100644 index 0000000..8fc230e --- /dev/null +++ b/packages/json_serializer/lib/src/validation.dart @@ -0,0 +1,25 @@ +part of belatuk_json_serializer; + +/// Thrown when schema validation fails. +class JsonValidationError implements Exception { + //final Schema schema; + final dynamic invalidData; + final String cause; + + const JsonValidationError( + this.cause, this.invalidData); //, Schema this.schema); +} + +/// Specifies a schema to validate a class with. +class WithSchema { + final Map schema; + + const WithSchema(this.schema); +} + +/// Specifies a schema to validate a class with. +class WithSchemaUrl { + final String schemaUrl; + + const WithSchemaUrl(this.schemaUrl); +} diff --git a/packages/json_serializer/pubspec.yaml b/packages/json_serializer/pubspec.yaml new file mode 100644 index 0000000..89b009f --- /dev/null +++ b/packages/json_serializer/pubspec.yaml @@ -0,0 +1,13 @@ +name: belatuk_json_serializer +version: 5.0.0 +description: Easy JSON to Object serialization and deserialization in Dart. +homepage: https://github.com/dart-backend/belatuk-common-utilities/tree/main/packages/json_serializer +environment: + sdk: '>=2.12.0 <3.0.0' +dependencies: + #dart2_constant: ^1.0.0 + logging: ^1.0.1 +dev_dependencies: + stack_trace: ^1.10.0 + test: ^1.17.4 + lints: ^1.0.0 \ No newline at end of file diff --git a/packages/json_serializer/test/deserialization_test.dart b/packages/json_serializer/test/deserialization_test.dart new file mode 100644 index 0000000..d9d3535 --- /dev/null +++ b/packages/json_serializer/test/deserialization_test.dart @@ -0,0 +1,115 @@ +import 'package:belatuk_json_serializer/belatuk_json_serializer.dart' as god; +import 'package:test/test.dart'; +import 'shared.dart'; + +main() { + god.logger.onRecord.listen(printRecord); + + group('deserialization', () { + test('deserialize primitives', testDeserializationOfPrimitives); + + test('deserialize maps', testDeserializationOfMaps); + + test('deserialize maps + reflection', + testDeserializationOfMapsWithReflection); + + test('deserialize lists + reflection', + testDeserializationOfListsAsWellAsViaReflection); + + test('deserialize with schema validation', + testDeserializationWithSchemaValidation); + }); +} + +testDeserializationOfPrimitives() { + expect(god.deserialize('1'), equals(1)); + expect(god.deserialize('1.4'), equals(1.4)); + expect(god.deserialize('"Hi!"'), equals("Hi!")); + expect(god.deserialize("true"), equals(true)); + expect(god.deserialize("null"), equals(null)); +} + +testDeserializationOfMaps() { + String simpleJson = + '{"hello":"world", "one": 1, "class": {"hello": "world"}}'; + String nestedJson = + '{"foo": {"bar": "baz", "funny": {"how": "life", "seems": 2, "hate": "us sometimes"}}}'; + var simple = god.deserialize(simpleJson) as Map; + var nested = god.deserialize(nestedJson) as Map; + + expect(simple['hello'], equals('world')); + expect(simple['one'], equals(1)); + expect(simple['class']['hello'], equals('world')); + + expect(nested['foo']['bar'], equals('baz')); + expect(nested['foo']['funny']['how'], equals('life')); + expect(nested['foo']['funny']['seems'], equals(2)); + expect(nested['foo']['funny']['hate'], equals('us sometimes')); +} + +class Pokedex { + Map? pokemon; +} + +testDeserializationOfMapsWithReflection() { + var s = '{"pokemon": {"Bulbasaur": 1, "Deoxys": 382}}'; + var pokedex = god.deserialize(s, outputType: Pokedex) as Pokedex; + expect(pokedex.pokemon, hasLength(2)); + expect(pokedex.pokemon!['Bulbasaur'], 1); + expect(pokedex.pokemon!['Deoxys'], 382); +} + +testDeserializationOfListsAsWellAsViaReflection() { + String json = '''[ + { + "hello": "world", + "nested": [] + }, + { + "hello": "dolly", + "nested": [ + { + "bar": "baz" + }, + { + "bar": "fight" + } + ] + } + ] + '''; + + var list = god.deserialize(json, outputType: ([]).runtimeType) + as List; + SampleClass first = list[0]; + SampleClass second = list[1]; + + expect(list.length, equals(2)); + expect(first.hello, equals("world")); + expect(first.nested.length, equals(0)); + expect(second.hello, equals("dolly")); + expect(second.nested.length, equals(2)); + + SampleNestedClass firstNested = second.nested[0]; + SampleNestedClass secondNested = second.nested[1]; + + expect(firstNested.bar, equals("baz")); + expect(secondNested.bar, equals("fight")); +} + +testDeserializationWithSchemaValidation() async { + String babelRcJson = + '{"presets":["es2015","stage-0"],"plugins":["add-module-exports"]}'; + + var deserialized = + god.deserialize(babelRcJson, outputType: BabelRc) as BabelRc; + + print(deserialized.presets.runtimeType); + expect(deserialized.presets is List, equals(true)); + expect(deserialized.presets.length, equals(2)); + expect(deserialized.presets[0], equals('es2015')); + expect(deserialized.presets[1], equals('stage-0')); + expect(deserialized.plugins is List, equals(true)); + expect(deserialized.plugins.length, equals(1)); + expect(deserialized.plugins[0], equals('add-module-exports')); +} diff --git a/packages/json_serializer/test/serialization_test.dart b/packages/json_serializer/test/serialization_test.dart new file mode 100644 index 0000000..c93ae8e --- /dev/null +++ b/packages/json_serializer/test/serialization_test.dart @@ -0,0 +1,133 @@ +//import 'package:dart2_constant/convert.dart'; +import 'dart:convert'; + +import 'package:belatuk_json_serializer/belatuk_json_serializer.dart' as god; +import 'package:test/test.dart'; +import 'shared.dart'; + +main() { + god.logger.onRecord.listen(printRecord); + + group('serialization', () { + test('serialize primitives', testSerializationOfPrimitives); + + test('serialize dates', testSerializationOfDates); + + test('serialize maps', testSerializationOfMaps); + + test('serialize lists', testSerializationOfLists); + + test('serialize via reflection', testSerializationViaReflection); + + test('serialize with schema validation', + testSerializationWithSchemaValidation); + }); +} + +testSerializationOfPrimitives() { + expect(god.serialize(1), equals("1")); + expect(god.serialize(1.4), equals("1.4")); + expect(god.serialize("Hi!"), equals('"Hi!"')); + expect(god.serialize(true), equals("true")); + expect(god.serialize(null), equals("null")); +} + +testSerializationOfDates() { + DateTime date = DateTime.now(); + String s = god.serialize({'date': date}); + + print(s); + + var deserialized = json.decode(s); + expect(deserialized['date'], equals(date.toIso8601String())); +} + +testSerializationOfMaps() { + var simple = json.decode(god + .serialize({'hello': 'world', 'one': 1, 'class': SampleClass('world')})); + var nested = json.decode(god.serialize({ + 'foo': { + 'bar': 'baz', + 'funny': {'how': 'life', 'seems': 2, 'hate': 'us sometimes'} + } + })); + + expect(simple['hello'], equals('world')); + expect(simple['one'], equals(1)); + expect(simple['class']['hello'], equals('world')); + + expect(nested['foo']['bar'], equals('baz')); + expect(nested['foo']['funny']['how'], equals('life')); + expect(nested['foo']['funny']['seems'], equals(2)); + expect(nested['foo']['funny']['hate'], equals('us sometimes')); +} + +testSerializationOfLists() { + List pandorasBox = [ + 1, + "2", + {"num": 3, "four": SampleClass('five')}, + SampleClass('six')..nested.add(SampleNestedClass('seven')) + ]; + String s = god.serialize(pandorasBox); + print(s); + + var deserialized = json.decode(s); + + expect(deserialized is List, equals(true)); + expect(deserialized.length, equals(4)); + expect(deserialized[0], equals(1)); + expect(deserialized[1], equals("2")); + expect(deserialized[2] is Map, equals(true)); + expect(deserialized[2]['num'], equals(3)); + expect(deserialized[2]['four'] is Map, equals(true)); + expect(deserialized[2]['four']['hello'], equals('five')); + expect(deserialized[3] is Map, equals(true)); + expect(deserialized[3]['hello'], equals('six')); + expect(deserialized[3]['nested'] is List, equals(true)); + expect(deserialized[3]['nested'].length, equals(1)); + expect(deserialized[3]['nested'][0] is Map, equals(true)); + expect(deserialized[3]['nested'][0]['bar'], equals('seven')); +} + +testSerializationViaReflection() { + SampleClass sample = SampleClass('world'); + + for (int i = 0; i < 3; i++) { + sample.nested.add(SampleNestedClass('baz')); + } + + String s = god.serialize(sample); + print(s); + + var deserialized = json.decode(s); + expect(deserialized['hello'], equals('world')); + expect(deserialized['nested'] is List, equals(true)); + expect(deserialized['nested'].length == 3, equals(true)); + expect(deserialized['nested'][0]['bar'], equals('baz')); + expect(deserialized['nested'][1]['bar'], equals('baz')); + expect(deserialized['nested'][2]['bar'], equals('baz')); +} + +testSerializationWithSchemaValidation() async { + BabelRc babelRc = + BabelRc(presets: ['es2015', 'stage-0'], plugins: ['add-module-exports']); + + String s = god.serialize(babelRc); + print(s); + + var deserialized = json.decode(s); + + expect(deserialized['presets'] is List, equals(true)); + expect(deserialized['presets'].length, equals(2)); + expect(deserialized['presets'][0], equals('es2015')); + expect(deserialized['presets'][1], equals('stage-0')); + expect(deserialized['plugins'] is List, equals(true)); + expect(deserialized['plugins'].length, equals(1)); + expect(deserialized['plugins'][0], equals('add-module-exports')); + + //Map babelRc2 = {'presets': 'Hello, world!'}; + + String json2 = god.serialize(babelRc); + print(json2); +} diff --git a/packages/json_serializer/test/shared.dart b/packages/json_serializer/test/shared.dart new file mode 100644 index 0000000..a35ce4f --- /dev/null +++ b/packages/json_serializer/test/shared.dart @@ -0,0 +1,51 @@ +import 'package:logging/logging.dart'; +import 'package:belatuk_json_serializer/belatuk_json_serializer.dart'; +import 'package:stack_trace/stack_trace.dart'; + +void printRecord(LogRecord rec) { + print(rec); + if (rec.error != null) print(rec.error); + if (rec.stackTrace != null) print(Chain.forTrace(rec.stackTrace!).terse); +} + +class SampleNestedClass { + String? bar; + + SampleNestedClass([String? this.bar]); +} + +class SampleClass { + String? hello; + List nested = []; + + SampleClass([String? this.hello]); +} + +@WithSchemaUrl( + "http://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/babelrc.json") +class BabelRc { + List presets; + List plugins; + + BabelRc( + {List this.presets: const [], + List this.plugins: const []}); +} + +@WithSchema(const { + r"$schema": "http://json-schema.org/draft-04/schema#", + "title": "Validated Sample Class", + "description": "Sample schema for validation via JSON God", + "type": "object", + "hello": const {"description": "A friendly greeting.", "type": "string"}, + "nested": const { + "description": "A list of NestedSampleClass items within this instance.", + "type": "array", + "items": const { + "type": "object", + "bar": const {"description": "Filler text", "type": "string"} + } + }, + "required": const ["hello", "nested"] +}) +class ValidatedSampleClass {} diff --git a/packages/json_serializer/test/to_json_test.dart b/packages/json_serializer/test/to_json_test.dart new file mode 100644 index 0000000..8da16ef --- /dev/null +++ b/packages/json_serializer/test/to_json_test.dart @@ -0,0 +1,32 @@ +import 'package:belatuk_json_serializer/belatuk_json_serializer.dart' as god; +import 'package:test/test.dart'; +import 'shared.dart'; + +main() { + god.logger.onRecord.listen(printRecord); + + test('fromJson', () { + var foo = god.deserialize('{"bar":"baz"}', outputType: Foo) as Foo; + + expect(foo is Foo, true); + expect(foo.text, equals('baz')); + }); + + test('toJson', () { + var foo = Foo(text: 'baz'); + var data = god.serializeObject(foo); + expect(data, equals({'bar': 'baz', 'foo': 'poobaz'})); + }); +} + +class Foo { + String? text; + + String get foo => 'poo$text'; + + Foo({this.text}); + + factory Foo.fromJson(Map json) => Foo(text: json['bar'].toString()); + + Map toJson() => {'bar': text, 'foo': foo}; +} diff --git a/packages/merge_map/README.md b/packages/merge_map/README.md index ccc328b..2f56a18 100644 --- a/packages/merge_map/README.md +++ b/packages/merge_map/README.md @@ -1,6 +1,6 @@ # Belatuk Merge Map -[![version](https://img.shields.io/badge/pub-v3.0.2-brightgreen)](https://pub.dev/packages/belatuk_merge_map) +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/belatuk_merge_map?include_prereleases) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/merge_map/LICENSE) diff --git a/packages/pretty_logging/AUTHORS.md b/packages/pretty_logging/AUTHORS.md new file mode 100644 index 0000000..ac95ab5 --- /dev/null +++ b/packages/pretty_logging/AUTHORS.md @@ -0,0 +1,12 @@ +Primary Authors +=============== + +* __[Thomas Hii](dukefirehawk.apps@gmail.com)__ + + Thomas is the current maintainer of the code base. He has refactored and migrated the + code base to support NNBD. + +* __[Tobe O](thosakwe@gmail.com)__ + + Tobe has written much of the original code prior to NNBD migration. He has moved on and + is no longer involved with the project. diff --git a/packages/pretty_logging/CHANGELOG.md b/packages/pretty_logging/CHANGELOG.md new file mode 100644 index 0000000..987e8f8 --- /dev/null +++ b/packages/pretty_logging/CHANGELOG.md @@ -0,0 +1,31 @@ +# Change Log + +## 4.0.0 + +* Added `lints` linter +* Removed deprecated parameters +* Published as `belatuk_pretty_logging` package + +## 3.0.3 + +* Updated README + +## 3.0.2 + +* Updated README + +## 3.0.1 + +* Fixed invalid homepage url in pubspec.yaml + +## 3.0.0 + +* Migrated to support Dart SDK 2.12.x NNBD + +## 2.0.0 + +* Migrated to work with Dart SDK 2.12.x Non NNBD + +## 1.0.0 + +* Initial release. diff --git a/packages/pretty_logging/LICENSE b/packages/pretty_logging/LICENSE new file mode 100644 index 0000000..e37a346 --- /dev/null +++ b/packages/pretty_logging/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2021, dukefirehawk.com +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/packages/pretty_logging/README.md b/packages/pretty_logging/README.md new file mode 100644 index 0000000..2f80314 --- /dev/null +++ b/packages/pretty_logging/README.md @@ -0,0 +1,41 @@ +# Belatuk Petty Logging + +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/belatuk_pretty_logging?include_prereleases) +[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) +[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) +[![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/pretty_logging/LICENSE) + +**Replacement of `package:pretty_logging` with breaking changes to support NNBD.** + +Standalone helper for colorful logging output, using pkg:io AnsiCode. + +## Installation + +In your `pubspec.yaml`: + +```yaml +dependencies: + belatuk_pretty_logging: ^4.0.0 +``` + +## Usage + +Basic usage is very simple: + +```dart +myLogger.onRecord.listen(prettyLog); +``` + +However, you can conditionally pass logic to omit printing an error, provide colors, or to provide a custom print function: + +```dart +var pretty = prettyLog( + logColorChooser: (_) => red, + printFunction: stderr.writeln, + omitError: (r) { + var err = r.error; + return err is AngelHttpException && err.statusCode != 500; + }, +); +myLogger.onRecord.listen(pretty); +``` diff --git a/packages/pretty_logging/analysis_options.yaml b/packages/pretty_logging/analysis_options.yaml new file mode 100644 index 0000000..ea2c9e9 --- /dev/null +++ b/packages/pretty_logging/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml \ No newline at end of file diff --git a/packages/pretty_logging/example/main.dart b/packages/pretty_logging/example/main.dart new file mode 100644 index 0000000..e867338 --- /dev/null +++ b/packages/pretty_logging/example/main.dart @@ -0,0 +1,11 @@ +import 'package:logging/logging.dart'; +import 'package:belatuk_pretty_logging/belatuk_pretty_logging.dart'; + +void main() { + Logger.root + ..level = Level.ALL + ..onRecord.listen(prettyLog) + ..info('Hey!') + ..finest('Bye!') + ..severe('Oops!', StateError('Wrong!'), StackTrace.current); +} diff --git a/packages/pretty_logging/lib/belatuk_pretty_logging.dart b/packages/pretty_logging/lib/belatuk_pretty_logging.dart new file mode 100644 index 0000000..bc45c81 --- /dev/null +++ b/packages/pretty_logging/lib/belatuk_pretty_logging.dart @@ -0,0 +1,50 @@ +import 'package:logging/logging.dart'; +import 'package:io/ansi.dart'; + +/// Prints the contents of a [LogRecord] with pretty colors. +/// +/// By passing [omitError], you can omit printing the error of a given +/// [LogRecord]. +/// +/// You can also pass a custom [printFunction] or [logColorChooser]. +void prettyLog(LogRecord record, + {bool Function(LogRecord)? omitError, + void Function(String)? printFunction, + AnsiCode Function(Level)? logColorChooser}) { + logColorChooser ??= chooseLogColor; + omitError ??= (_) => false; + printFunction ??= print; + + var code = logColorChooser(record.level); + if (record.error == null) printFunction(code.wrap(record.toString())!); + + if (record.error != null) { + var err = record.error; + if (omitError(record)) return; + printFunction(code.wrap(record.toString() + '\n')!); + printFunction(code.wrap(err.toString())!); + + if (record.stackTrace != null) { + printFunction(code.wrap(record.stackTrace.toString())!); + } + } +} + +/// Chooses a color based on the logger [level]. +AnsiCode chooseLogColor(Level level) { + if (level == Level.SHOUT) { + return backgroundRed; + } else if (level == Level.SEVERE) { + return red; + } else if (level == Level.WARNING) { + return yellow; + } else if (level == Level.INFO) { + return cyan; + } else if (level == Level.CONFIG || + level == Level.FINE || + level == Level.FINER || + level == Level.FINEST) { + return lightGray; + } + return resetAll; +} diff --git a/packages/pretty_logging/pubspec.yaml b/packages/pretty_logging/pubspec.yaml new file mode 100644 index 0000000..ba381b4 --- /dev/null +++ b/packages/pretty_logging/pubspec.yaml @@ -0,0 +1,11 @@ +name: belatuk_pretty_logging +version: 4.0.0 +description: Standalone helper for colorful logging output, using pkg:io AnsiCode. +homepage: https://github.com/dart-backend/belatuk-common-utilities/tree/main/packages/pretty_logging +environment: + sdk: '>=2.12.0 <3.0.0' +dependencies: + io: ^1.0.0 + logging: ^1.0.1 +dev_dependencies: + lints: ^1.0.0 diff --git a/packages/pub_sub/README.md b/packages/pub_sub/README.md index 691622b..26e0a5d 100644 --- a/packages/pub_sub/README.md +++ b/packages/pub_sub/README.md @@ -1,6 +1,6 @@ # Belatuk Pub Sub -[![version](https://img.shields.io/badge/pub-v4.0.3-brightgreen)](https://pub.dev/packages/belatuk_pub_sub) +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/belatuk_pub_sub?include_prereleases) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/pub_sub/LICENSE) diff --git a/packages/range_header/README.md b/packages/range_header/README.md index bae55a9..4f860eb 100644 --- a/packages/range_header/README.md +++ b/packages/range_header/README.md @@ -1,6 +1,6 @@ # Belatuk Range Header -[![version](https://img.shields.io/badge/pub-v4.0.1-brightgreen)](https://pub.dev/packages/belatuk_range_header) +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/belatuk_range_header?include_prereleases) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/range_header/LICENSE) diff --git a/packages/symbol_table/README.md b/packages/symbol_table/README.md index 1604672..cfb8688 100644 --- a/packages/symbol_table/README.md +++ b/packages/symbol_table/README.md @@ -1,6 +1,6 @@ # Belatuk Symbol Table -[![version](https://img.shields.io/badge/pub-v3.0.1-brightgreen)](https://pub.dev/packages/belatuk_symbol_table) +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/belatuk_symbol_table?include_prereleases) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/symbol_table/LICENSE) diff --git a/packages/user_agent/README.md b/packages/user_agent/README.md index 8eaddb2..a9e4d7c 100644 --- a/packages/user_agent/README.md +++ b/packages/user_agent/README.md @@ -1,6 +1,6 @@ # User Agent Analyzer -[![version](https://img.shields.io/badge/pub-v3.1.0-brightgreen)](https://pub.dev/packages/user_agent_analyzer) +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/user_agent_analyzer?include_prereleases) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/user_agent/LICENSE)