Added json_god package
This commit is contained in:
parent
6661055410
commit
0752498a16
15 changed files with 801 additions and 0 deletions
8
packages/json_god/CHANGELOG.md
Normal file
8
packages/json_god/CHANGELOG.md
Normal file
|
@ -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.
|
21
packages/json_god/LICENSE
Normal file
21
packages/json_god/LICENSE
Normal file
|
@ -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.
|
113
packages/json_god/README.md
Normal file
113
packages/json_god/README.md
Normal file
|
@ -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)
|
3
packages/json_god/analysis_options.yaml
Normal file
3
packages/json_god/analysis_options.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
17
packages/json_god/lib/json_god.dart
Normal file
17
packages/json_god/lib/json_god.dart
Normal file
|
@ -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');
|
43
packages/json_god/lib/src/deserialize.dart
Normal file
43
packages/json_god/lib/src/deserialize.dart
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
191
packages/json_god/lib/src/reflection.dart
Normal file
191
packages/json_god/lib/src/reflection.dart
Normal file
|
@ -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<Symbol> _findGetters(ClassMirror classMirror) {
|
||||
List<Symbol> 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<TypeMirror> 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;
|
||||
}
|
35
packages/json_god/lib/src/serialize.dart
Normal file
35
packages/json_god/lib/src/serialize.dart
Normal file
|
@ -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;
|
||||
}
|
5
packages/json_god/lib/src/util.dart
Normal file
5
packages/json_god/lib/src/util.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
part of json_god;
|
||||
|
||||
bool _isPrimitive(value) {
|
||||
return value is num || value is bool || value is String || value == null;
|
||||
}
|
25
packages/json_god/lib/src/validation.dart
Normal file
25
packages/json_god/lib/src/validation.dart
Normal file
|
@ -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);
|
||||
}
|
14
packages/json_god/pubspec.yaml
Normal file
14
packages/json_god/pubspec.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: json_god
|
||||
version: 3.0.0
|
||||
authors:
|
||||
- Tobe O <thosakwe@gmail.com>
|
||||
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
|
112
packages/json_god/test/deserialization_test.dart
Normal file
112
packages/json_god/test/deserialization_test.dart
Normal file
|
@ -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<String, int> 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: (<SampleClass>[]).runtimeType) as List<SampleClass>;
|
||||
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'));
|
||||
}
|
131
packages/json_god/test/serialization_test.dart
Normal file
131
packages/json_god/test/serialization_test.dart
Normal file
|
@ -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);
|
||||
}
|
51
packages/json_god/test/shared.dart
Normal file
51
packages/json_god/test/shared.dart
Normal file
|
@ -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<SampleNestedClass> nested = [];
|
||||
|
||||
SampleClass([String this.hello]);
|
||||
}
|
||||
|
||||
@WithSchemaUrl(
|
||||
"http://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/babelrc.json")
|
||||
class BabelRc {
|
||||
List<String> presets;
|
||||
List<String> plugins;
|
||||
|
||||
BabelRc(
|
||||
{List<String> this.presets: const [],
|
||||
List<String> 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 {}
|
32
packages/json_god/test/to_json_test.dart
Normal file
32
packages/json_god/test/to_json_test.dart
Normal file
|
@ -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};
|
||||
}
|
Loading…
Reference in a new issue