diff --git a/packages/json_god/CHANGELOG.md b/packages/json_god/CHANGELOG.md new file mode 100644 index 00000000..091e7408 --- /dev/null +++ b/packages/json_god/CHANGELOG.md @@ -0,0 +1,8 @@ +# 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 https://github.com/dart-lang/sdk/issues/33594 is resolved. +* Removes the reference to `Schema` class. \ No newline at end of file diff --git a/packages/json_god/LICENSE b/packages/json_god/LICENSE new file mode 100644 index 00000000..66b339ee --- /dev/null +++ b/packages/json_god/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Tobe O + +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 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/packages/json_god/README.md b/packages/json_god/README.md new file mode 100644 index 00000000..c1f3b8c8 --- /dev/null +++ b/packages/json_god/README.md @@ -0,0 +1,113 @@ +# JSON God v2 + +[![Pub](https://img.shields.io/pub/v/json_god.svg)](https://pub.dartlang.org/packages/json_god) +[![build status](https://travis-ci.org/thosakwe/json_god.svg)](https://travis-ci.org/thosakwe/json_god) + +The ***new and improved*** definitive solution for JSON in Dart. + + +# Installation + dependencies: + json_god: ^2.0.0-beta + +# Usage + +It is recommended to import the library under an alias, i.e., `god`. + +```dart +import 'package:json_god/json_god.dart' as god; +``` + +## Serializing JSON + +Simply call `god.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 = god.serialize(map); +print(json); +``` + +You can easily serialize classes, too. JSON God also supports classes as members. +```dart +class A { + String foo; + A(this.foo); +} + +class B { + String hello; + A nested; + B(String hello, String foo) { + this.hello = hello; + this.nested = new A(foo); + } +} + +main() { + God god = new God(); + print(god.serialize(new 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 `god.deserialize`. +```dart +Map map = god.deserialize('{"hello":"world"}'); +int three = god.deserialize("3"); +``` + +### Deserializing to Classes + +JSON God lets you deserialize JSON into an instance of any type. Simply pass the +type as the second argument to `god.deserialize`. + +If the class has a `fromJson` constructor, it will be called instead. + +```dart +class Child { + String foo; +} + +class Parent { + String hello; + Child child = new Child(); +} + +main() { + God god = new God(); + Parent parent = god.deserialize('{"hello":"world","child":{"foo":"bar"}}', Parent); + print(parent); +} +``` + +**Any JSON-deserializable classes must initializable without parameters. +If `new 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 = god.deserialize('["some invalid input"]', HasAnInt); +// Throws an error +``` + +An exception will be thrown if validation fails. + +# Thank you for using JSON God + +Thank you for using this library. I hope you like it. + +Feel free to follow me on Twitter: +[@thosakwe](http://twitter.com/thosakwe) + +Or, check out [my blog](https://thosakwe.com) \ No newline at end of file diff --git a/packages/json_god/analysis_options.yaml b/packages/json_god/analysis_options.yaml new file mode 100644 index 00000000..eae1e42a --- /dev/null +++ b/packages/json_god/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + strong-mode: + implicit-casts: false \ No newline at end of file diff --git a/packages/json_god/lib/json_god.dart b/packages/json_god/lib/json_god.dart new file mode 100644 index 00000000..5a3c7d68 --- /dev/null +++ b/packages/json_god/lib/json_god.dart @@ -0,0 +1,17 @@ +/// A robust library for JSON serialization and deserialization. +library json_god; + +import 'package:dart2_constant/convert.dart'; +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 = new Logger('json_god'); \ No newline at end of file diff --git a/packages/json_god/lib/src/deserialize.dart b/packages/json_god/lib/src/deserialize.dart new file mode 100644 index 00000000..94a5e682 --- /dev/null +++ b/packages/json_god/lib/src/deserialize.dart @@ -0,0 +1,43 @@ +part of json_god; + +/// 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_god/lib/src/reflection.dart b/packages/json_god/lib/src/reflection.dart new file mode 100644 index 00000000..4adcccd5 --- /dev/null +++ b/packages/json_god/lib/src/reflection.dart @@ -0,0 +1,191 @@ +library json_god.reflection; + +import 'dart:mirrors'; +import 'package:json_god/json_god.dart'; + +const Symbol hashCodeSymbol = #hashCode; +const Symbol runtimeTypeSymbol = #runtimeType; + +typedef Serializer(value); +typedef Deserializer(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, [@deprecated bool debug = false]) { + 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, + [@deprecated bool debug = false]) { + 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 new 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, + [@deprecated bool debug = false]) { + // Check for fromJson + var typeMirror = reflectType(outputType); + + if (typeMirror is! ClassMirror) { + throw new ArgumentError('$outputType is not a class.'); + } + + var type = typeMirror as ClassMirror; + var fromJson = + new 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(new 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(new 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 = new 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(new 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_god/lib/src/serialize.dart b/packages/json_god/lib/src/serialize.dart new file mode 100644 index 00000000..609d1b3a --- /dev/null +++ b/packages/json_god/lib/src/serialize.dart @@ -0,0 +1,35 @@ +part of json_god; + +/// 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_god/lib/src/util.dart b/packages/json_god/lib/src/util.dart new file mode 100644 index 00000000..a562c4c8 --- /dev/null +++ b/packages/json_god/lib/src/util.dart @@ -0,0 +1,5 @@ +part of json_god; + +bool _isPrimitive(value) { + return value is num || value is bool || value is String || value == null; +} \ No newline at end of file diff --git a/packages/json_god/lib/src/validation.dart b/packages/json_god/lib/src/validation.dart new file mode 100644 index 00000000..eeb7f564 --- /dev/null +++ b/packages/json_god/lib/src/validation.dart @@ -0,0 +1,25 @@ +part of json_god; + +/// Thrown when schema validation fails. +class JsonValidationError implements Exception { + //final Schema schema; + final invalidData; + final String cause; + + const JsonValidationError( + String this.cause, this.invalidData);//, Schema this.schema); +} + +/// Specifies a schema to validate a class with. +class WithSchema { + final Map schema; + + const WithSchema(Map this.schema); +} + +/// Specifies a schema to validate a class with. +class WithSchemaUrl { + final String schemaUrl; + + const WithSchemaUrl(String this.schemaUrl); +} diff --git a/packages/json_god/pubspec.yaml b/packages/json_god/pubspec.yaml new file mode 100644 index 00000000..b1f8be2a --- /dev/null +++ b/packages/json_god/pubspec.yaml @@ -0,0 +1,14 @@ +name: json_god +version: 3.0.0 +authors: + - Tobe O +description: Easy JSON serialization and deserialization in Dart. +homepage: https://github.com/thosakwe/json_god +environment: + sdk: ">=2.10.0 <3.0.0" +dependencies: + dart2_constant: ^1.0.0 + logging: ^1.0.0 +dev_dependencies: + stack_trace: ^1.0.0 + test: any \ No newline at end of file diff --git a/packages/json_god/test/deserialization_test.dart b/packages/json_god/test/deserialization_test.dart new file mode 100644 index 00000000..0d8f27aa --- /dev/null +++ b/packages/json_god/test/deserialization_test.dart @@ -0,0 +1,112 @@ +import 'package:json_god/json_god.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_god/test/serialization_test.dart b/packages/json_god/test/serialization_test.dart new file mode 100644 index 00000000..8b596948 --- /dev/null +++ b/packages/json_god/test/serialization_test.dart @@ -0,0 +1,131 @@ +import 'package:dart2_constant/convert.dart'; +import 'package:json_god/json_god.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 = new 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': new 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": new SampleClass('five')}, + new SampleClass('six')..nested.add(new 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 = new SampleClass('world'); + + for (int i = 0; i < 3; i++) { + sample.nested.add(new 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 = new 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_god/test/shared.dart b/packages/json_god/test/shared.dart new file mode 100644 index 00000000..6380baba --- /dev/null +++ b/packages/json_god/test/shared.dart @@ -0,0 +1,51 @@ +import 'package:logging/logging.dart'; +import 'package:json_god/json_god.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(new 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_god/test/to_json_test.dart b/packages/json_god/test/to_json_test.dart new file mode 100644 index 00000000..f4bea217 --- /dev/null +++ b/packages/json_god/test/to_json_test.dart @@ -0,0 +1,32 @@ +import 'package:json_god/json_god.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 = new 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) => new Foo(text: json['bar'].toString()); + + Map toJson() => {'bar': text, 'foo': foo}; +}