Skip to content

Commit

Permalink
Implement referenceable notation for cyclical dependencies.
Browse files Browse the repository at this point in the history
Fix #33
  • Loading branch information
sestegra committed Jul 16, 2016
1 parent b4ade8c commit 83f957c
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 23 deletions.
2 changes: 1 addition & 1 deletion example/serializer_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class ModelC extends JsonObject {
}

class Id extends SerializedName {
const Id() : super("_id");
const Id(): super("_id");
}

main() {
Expand Down
14 changes: 14 additions & 0 deletions lib/src/annotations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ class Ignore {

const ignore = const Ignore();

class Referenceable {
const Referenceable();
}

const referenceable = const Referenceable();

class Reference {
const Reference();
}

const reference = const Reference();

class SerializedName {
final String name;
const SerializedName(this.name);
Expand All @@ -47,4 +59,6 @@ class _MetadataManager<T> {
}

const _MetadataManager<SerializedName> serializedNameMetadataManager = const _MetadataManager<SerializedName>();
const _MetadataManager<Referenceable> referenceableMetadataManager = const _MetadataManager<Referenceable>();
const _MetadataManager<Reference> referenceMetadataManager = const _MetadataManager<Reference>();
const _MetadataManager<Ignore> ignoreMetadataManager = const _MetadataManager<Ignore>();
29 changes: 15 additions & 14 deletions lib/src/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ abstract class TypedJsonObject extends Serialize {

/// Utility class to access to the serializer api
class Serializer {
static final Map<String, ClassMirror> _classes = singletonClasses;
static final Map<String, ClassSerialiazerInfo> _classes = singletonClasses;

///////////////////
// Public
Expand Down Expand Up @@ -113,10 +113,10 @@ class Serializer {
_classes.containsKey(type.toString());

/// Convert the object to a Map
Map toMap(Object input) => _toMap(input);
Map toMap(Object input) => _toMap(input, true);

/// Encode the object to serialized string
String encode(Object input) => _encode(input);
String encode(Object input) => _encode(input, true);

/// Decode the object from a seriablized string
Object decode(String encoded, [Type type]) => _decode(encoded, type);
Expand Down Expand Up @@ -176,7 +176,7 @@ class Serializer {
} else if (_typeCodecs.containsKey(name)) {
return _typeCodecs[name].type;
} else {
ClassMirror classMirror = _classes[name];
ClassMirror classMirror = _classes[name]?.classMirror;
return classMirror?.dynamicReflectedType;
}
}
Expand Down Expand Up @@ -272,21 +272,21 @@ class Serializer {
return null;
}

Object _encodeValue(value) {
Object _encodeValue(value, [bool withReferenceable = false]) {
if (hasTypeCodec(value.runtimeType)) {
return typeCodec(value.runtimeType).encode(value);
} else if (value is Map || isSerializable(value.runtimeType)) {
return _toMap(value);
return _toMap(value, withReferenceable);
} else if (value is List) {
return _encodeList(value);
return _encodeList(value, withReferenceable);
} else if (isPrimaryType(value.runtimeType)) {
return value;
}
return null;
}

List _encodeList(List list) {
return list.map((elem) => _encodeValue(elem))
List _encodeList(List list, [bool withReferenceable = false]) {
return list.map((elem) => _encodeValue(elem, withReferenceable))
.toList(growable: false);
}

Expand All @@ -297,7 +297,7 @@ class Serializer {
}
}

Map _toMap(Object obj) {
Map _toMap(Object obj, [bool withReferenceable = false]) {
if (obj == null || obj is List) {
return null;
}
Expand All @@ -323,7 +323,8 @@ class Serializer {
if ( !data.containsKey(name)
&& !ignoreMetadataManager.hasMetadata(dec)
&& ( (dec is VariableMirror && isSerializableVariable(dec))
|| (dec is MethodMirror && dec.isGetter))) {
|| (dec is MethodMirror && dec.isGetter))
&& isEncodeableField(_classes, cm, dec, withReferenceable)) {
_encodeMap(data, name, mir.invokeGetter(originalName));
}
});
Expand All @@ -332,13 +333,13 @@ class Serializer {
return data;
}

String _encode(Object obj) {
String _encode(Object obj, [bool withReferenceable = false]) {
if (obj == null) {
return null;
}
if (obj is List) {
return _codec.encode(_encodeList(obj));
return _codec.encode(_encodeList(obj, withReferenceable));
}
return _codec.encode(_toMap(obj));
return _codec.encode(_toMap(obj, withReferenceable));
}
}
37 changes: 31 additions & 6 deletions lib/src/convert.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'annotations.dart';
final String MapTypeString = {}.runtimeType.toString();
final String ListTypeString = [].runtimeType.toString();

bool isSerializableClassMirror(Map<String, ClassMirror> serializables, ClassMirror cm) {
bool isSerializableClassMirror(Map<String, ClassSerialiazerInfo> serializables, ClassMirror cm) {
return serializables.containsKey(cm.mixin.simpleName);
}

Expand All @@ -35,15 +35,23 @@ String serializedName(DeclarationMirror dec) {
}
}

bool isEncodeableField(Map<String, ClassSerialiazerInfo> serializables, ClassMirror cm, DeclarationMirror dec, bool withReferenceable) {
return withReferenceable || !serializables[cm.mixin.simpleName].isReferenceable || referenceMetadataManager.hasMetadata(dec);
}

_printToString(String data) => "$data\n";

String printAndDumpSerializables() {
String output = "";
initSingletonClasses();
singletonClasses.values.forEach((classMirror) {
var cm = classMirror;
singletonClasses.values.forEach((classInfo) {
var cm = classInfo.classMirror;
output += _printToString(cm.mixin.simpleName);
print(cm.mixin.simpleName);
if (classInfo.isReferenceable) {
print(cm.mixin.simpleName + " (with Reference)");
} else {
print(cm.mixin.simpleName);
}
while (cm != null
&& cm.superclass != null
&& isSerializableClassMirror(singletonClasses, cm)) {
Expand All @@ -56,6 +64,7 @@ String printAndDumpSerializables() {
bool isSetter = false;
bool isGetter = false;
bool isIgnored = ignoreMetadataManager.hasMetadata(decl);
bool isRef = referenceMetadataManager.hasMetadata(decl);
String renamed = serializedName(decl);

if (decl is VariableMirror) {
Expand All @@ -81,6 +90,7 @@ String printAndDumpSerializables() {

if (type != null) {
var line = " ";
line += isRef ? "R" : "-";
line += isSetter ? "G" : "-";
line += isGetter ? "S" : "-";
line += isIgnored ? "I" : "-";
Expand All @@ -98,14 +108,29 @@ String printAndDumpSerializables() {
}

// Singleton that maps every class annotated with @serializable
final singletonClasses = <String, ClassMirror>{};
class ClassSerialiazerInfo {
ClassMirror classMirror;
bool isReferenceable;
ClassSerialiazerInfo(this.classMirror, this.isReferenceable);
}

final singletonClasses = <String, ClassSerialiazerInfo>{};
initSingletonClasses() {
if (singletonClasses.isEmpty) {
for (ClassMirror classMirror in serializable.annotatedClasses) {
if (classMirror != null
&& classMirror.simpleName != null
&& classMirror.metadata.contains(serializable)) {
singletonClasses[classMirror.simpleName] = classMirror;

// Searching for referenceable annotation
var isReferenceable = false;
var cm = classMirror;
while (cm != null && cm.superclass != null) {
isReferenceable = isReferenceable || referenceableMetadataManager.hasMetadata(cm);
cm = cm?.superclass;
}

singletonClasses[classMirror.simpleName] = new ClassSerialiazerInfo(classMirror, isReferenceable);
}
}
}
Expand Down
49 changes: 49 additions & 0 deletions test/json_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,25 @@ class M2 {
String m2;
}

@referenceable
@serializable
class Employee {
@reference
int id;
String name;
Address address;
Employee manager;
}

@referenceable
@serializable
class Address {
@reference
int id;
String location;
Employee owner;
}

Serializer serializer;

main() {
Expand Down Expand Up @@ -529,4 +548,34 @@ main() {
expect(mixin.m2, "M2");
});
});

group("Referenceable", () {
test("Serialize", () {
Address addressManager = new Address()
..id = 1337
..location = "Somewhere";

Address addressEmployee = new Address()
..id = 1338
..location = "Somewhere else";

Employee manager = new Employee()
..id = 43
..name = "Alice Doo"
..address = addressManager;
addressManager.owner = manager;

Employee employee = new Employee()
..id = 42
..name = "Bob Smith"
..address = addressEmployee
..manager = manager;
addressEmployee.owner = employee;

expect(serializer.encode(addressManager), '{"id":1337,"location":"Somewhere","owner":{"id":43}}');
expect(serializer.encode(addressEmployee), '{"id":1338,"location":"Somewhere else","owner":{"id":42}}');
expect(serializer.encode(manager), '{"id":43,"name":"Alice Doo","address":{"id":1337}}');
expect(serializer.encode(employee), '{"id":42,"name":"Bob Smith","address":{"id":1338},"manager":{"id":43}}');
});
});
}
53 changes: 51 additions & 2 deletions test/typed_json_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,25 @@ class M2 {
String m2;
}

@referenceable
@serializable
class Employee {
@reference
int id;
String name;
Address address;
Employee manager;
}

@referenceable
@serializable
class Address {
@reference
int id;
String location;
Employee owner;
}

main() {
var serializer = new Serializer.TypedJson();

Expand Down Expand Up @@ -382,8 +401,8 @@ main() {
try {
TypedDontWantToBeSerialize _ = serializer.decode('{"@type":"TypedDontWantToBeSerialize","foo":"bar"}');
} catch (e) {
expect(true, e is String);
// expect("Cannot instantiate abstract class DontWantToBeSerialize: _url 'null' line null", e);
expect(true, e is String);
// expect("Cannot instantiate abstract class DontWantToBeSerialize: _url 'null' line null", e);
}
});
});
Expand Down Expand Up @@ -463,4 +482,34 @@ main() {
expect(mixin.m2, "M2");
});
});

group("Referenceable", () {
test("Serialize", () {
Address addressManager = new Address()
..id = 1337
..location = "Somewhere";

Address addressEmployee = new Address()
..id = 1338
..location = "Somewhere else";

Employee manager = new Employee()
..id = 43
..name = "Alice Doo"
..address = addressManager;
addressManager.owner = manager;

Employee employee = new Employee()
..id = 42
..name = "Bob Smith"
..address = addressEmployee
..manager = manager;
addressEmployee.owner = employee;

expect(serializer.encode(addressManager), '{"@type":"Address","id":1337,"location":"Somewhere","owner":{"@type":"Employee","id":43}}');
expect(serializer.encode(addressEmployee), '{"@type":"Address","id":1338,"location":"Somewhere else","owner":{"@type":"Employee","id":42}}');
expect(serializer.encode(manager), '{"@type":"Employee","id":43,"name":"Alice Doo","address":{"@type":"Address","id":1337}}');
expect(serializer.encode(employee), '{"@type":"Employee","id":42,"name":"Bob Smith","address":{"@type":"Address","id":1338},"manager":{"@type":"Employee","id":43}}');
});
});
}

0 comments on commit 83f957c

Please sign in to comment.