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