diff --git a/bin/main.dart b/bin/main.dart index 227e063..9928557 100644 --- a/bin/main.dart +++ b/bin/main.dart @@ -17,92 +17,56 @@ class Simple { } } -class SimpleWithSimple { - int id; - Simple simple; -} - -class Test { - @Property(name: "_id") - int id; - - @Property(name: "_lol??") - String value; - - bool real; +@d.Entity() +class Test6 { + List simple; - double tellme; + Test6(); @override String toString() { - return "$id: $value, $real, $tellme"; + return simple.fold("Test6: \n", (a, b) => a + b.toString() + "\n"); } } -class Test1 { - SimpleWithSimple simple1; +class AnnotationSample2 { + int id; - @override - String toString() { - return "Test1: ${simple1.id} ${simple1.simple.toString()}"; - } -} + @Property(name: "test1") + String text; -class Test2 { - Test test; - List strings; + AnnotationSample2(); - @override - String toString() { - return "Test2: ${test.toString()} $strings"; - } + AnnotationSample2.withValues(this.id, this.text); } -class Test3 extends Test { - String text2; +class MapComplex { + Map persons; } -class Test4 extends Test3 { - String text3; - - @override - String toString() { - return "$id: $value, $real, $tellme, $text2, $text3"; - } +class MapSimple { + Map persons; } -class Test5 { - String text2; - Simple simple; - - @override - String toString() { - return "${simple.id}: ${simple.text}, $text2"; - } +class Person { + String name; } -@d.Entity() -class Test6 { - List simple; - - Test6(); - - @override - String toString() { - return simple.fold("Test6: \n", (a, b) => a + b.toString() + "\n"); - } +class Male { + Person person; + String test; } -void test(Test t, Type i) { - final Type d = T; - print(d); - print("${d == i}"); - if (t.runtimeType == d) { - print("is ${t.runtimeType}"); - } else { - print("is not ${t.runtimeType}"); - } -} +// void test(Test t, Type i) { +// final Type d = T; +// print(d); +// print("${d == i}"); +// if (t.runtimeType == d) { +// print("is ${t.runtimeType}"); +// } else { +// print("is not ${t.runtimeType}"); +// } +// } // {: [123] // }: [125] @@ -388,18 +352,14 @@ void main() { Logger.root.onRecord.listen((LogRecord rec) { print('${rec.level.name}: ${rec.time}: ${rec.message}'); }); - //test(new Test(), Test); - //printChars(); - - // test12(); - // test13(); - // String json = '{"id": 2, "text":"lol"}'; - // print(decodeTest(json, Simple)); + final json = + '{"person1": {"name": "name"},"person2": {"name": "name2"},"person3": {"name": "name3"}}'; + final sample = decodeTest(json, MapComplex); //print(decodeTest>(list, new List().runtimeType)); - int its = 2000; - Stopwatch w = new Stopwatch(); + // int its = 2000; + // Stopwatch w = new Stopwatch(); // w.start(); // for (int i = 0; i < its; i++) { // var b = JSON.decode(list); @@ -407,23 +367,14 @@ void main() { // w.stop(); // print("JSON.decode took: ${w.elapsedMilliseconds}"); - // w.reset(); - w.start(); - for (int i = 0; i < its; i++) { - var b = decodeTest>(list, new List().runtimeType); - //print(b.length); - } - w.stop(); - print("decodeTest took: ${w.elapsedMilliseconds}"); - // w.reset(); // w.start(); // for (int i = 0; i < its; i++) { - // var b = decodeMap(JSON.decode(list), new List().runtimeType); + // var b = decodeTest>(list, new List().runtimeType); // //print(b.length); // } // w.stop(); - // print("decodeMap took: ${w.elapsedMilliseconds}"); + // print("decode 3rd version took: ${w.elapsedMilliseconds}"); // w.reset(); // w.start(); @@ -432,7 +383,7 @@ void main() { // //print(b.length); // } // w.stop(); - // print("decode took: ${w.elapsedMilliseconds}"); + // print("decode 1st version took: ${w.elapsedMilliseconds}"); // w.reset(); // w.start(); @@ -452,57 +403,4 @@ void main() { // } // w.stop(); // print("dartson took: ${w.elapsedMilliseconds}"); - - // w.reset(); - - // String json = '{"id": 2, "text":"lol"}'; - // print(json); - // var b = decodeTest(json, Simple); - // print(b); - // print(""); - - // json = '["a", "b","c" , "d" , "a", "", " "]'; - // print(json); - // var c = decodeTest>(json, new List().runtimeType); - // print(c); - // print(""); - - // print('{"_id":1000, "_lol??": "wat", "real": true, "tellme": 2.20}'); - // var d = decodeTest( - // '{"_id":1000, "_lol??": "wat", "real": true, "tellme": 2.20}', Test); - // print(d); - // print(""); - - // print( - // '{"_id":0, "_lol??": "wat", "real": true, "tellme": 0.1001, "text2": "lol", "test3":"lol2"}'); - // var g = decodeTest( - // '{"_id":0, "_lol??": "wat", "real": true, "tellme": 0.1001, "text2": "lol", "test3":"lol2"}', - // Test4); - // print(g); - // print(""); - - // json = '{"simple1": {"id" : 2, "simple": {"id": 1, "text": "2"}}}'; - // print(json); - // var e = decodeTest(json, Test1); - // print(e); - // print(""); - - // var json = '{"text2": "test", "simple": { "id": 23, "text": "test text" }}'; - // print(json); - // var f = decodeTest( - // '{"text2": "test", "simple": { "id": 23, "text": "test text" }}', Test5); - // print(f); - // print(""); - - // json = - // '{"simple": [{ "id": 23, "text": "test text" }, { "id": 25, "text": "test text" }, { "id": 26, "text": "test text" }]}'; - // print(json); - // var h = decodeTest(json, Test6); - // print(h); - // print(""); - - // var json = '{"test": 2, "lol": "lol"}'; - // print(json); - // var b = decode(json, Map); - // print(b); } diff --git a/lib/src/annotations.dart b/lib/src/annotations.dart index 08690b4..2a0286d 100644 --- a/lib/src/annotations.dart +++ b/lib/src/annotations.dart @@ -11,5 +11,5 @@ class Property { this.name = name; bool get ignore => _ignore == null ? false : _ignore; - String toString() => "DartsonProperty: Name: ${name} , Ignore: ${ignore}"; + bool get hasName => (name == null) ? false : (name.isEmpty) ? false : true; } diff --git a/lib/src/deserialize.dart b/lib/src/deserialize.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/src/reflection.dart b/lib/src/reflection.dart index a23478e..9515c42 100644 --- a/lib/src/reflection.dart +++ b/lib/src/reflection.dart @@ -24,23 +24,6 @@ bool _isPrimitive(Type value) { value == double; } -// final _convMap = { -// bool: _bytesToBool, -// int: _bytesToInt, -// String: _bytesToString, -// double: _bytesToDouble -// }; - -// typedef int Seek(int start, {bool end}); -// final _seekMap = { -// bool: _seekNumberOrBool, -// int: _seekNumberOrBool, -// double: _seekNumberOrBool, -// String: _seekString, -// List: _seekList, -// Element: _seekClass -// }; - class Element { final Type type; final Symbol symbol; @@ -48,6 +31,7 @@ class Element { bool isList; bool isPrimList; bool isPrimMap; + bool ignore; Type listType; Element(this.symbol, this.type); @@ -89,9 +73,9 @@ TypeInfo generateElements(Type type) { if (_cache.containsKey(type)) { return _cache[type]; } - ClassMirror classMirror = reflectClass(type); - TypeMirror typeMirror = reflectType(type); - Map elements = new Map(); + final classMirror = reflectClass(type); + final typeMirror = reflectType(type); + final elements = new Map(); bool isMap = false; bool isPrimMap = false; bool isList = false; @@ -122,15 +106,14 @@ TypeInfo generateElements(Type type) { } } //check fields - final list = classMirror.declarations.values + classMirror.declarations.values .where((dm) => dm is VariableMirror) - .toList(); - - list.forEach((dm) { + .forEach((dm) { if (dm is VariableMirror) { String key = MirrorSystem.getName(dm.simpleName); - Symbol symbol = dm.simpleName; + final symbol = dm.simpleName; //check for annotation + bool ignore = false; if (dm.metadata.length > 0) { final im = dm.metadata.firstWhere((s) { if (!s.hasReflectee) return false; @@ -139,15 +122,18 @@ TypeInfo generateElements(Type type) { }, orElse: () => null); if (im != null && im.reflectee is Property) { //if we found an annotation check if its not empty and use it as key - final newkey = im.reflectee.name; - if (newkey.isNotEmpty) { - key = newkey; + if (im.reflectee.hasName && !im.reflectee.ignore) { + key = im.reflectee.name; + } + if (im.reflectee.ignore) { + ignore = im.reflectee.ignore; } } } if (dm.type.hasReflectedType) { - Type t = dm.type.reflectedType; + final t = dm.type.reflectedType; final element = new Element(symbol, t); + element.ignore = ignore; if (dm.type.isAssignableTo(_mirMap)) { element.isMap = true; } else { @@ -173,7 +159,7 @@ TypeInfo generateElements(Type type) { } } }); - var tp = new TypeInfo(type, elements, isMap, isList, isPrimMap, isPrimList, + final tp = new TypeInfo(type, elements, isMap, isList, isPrimMap, isPrimList, listType, classMirror); _cache[type] = tp; return tp; @@ -219,7 +205,6 @@ class ComplexSetter implements ISetter { final TypeInfo info; ComplexSetter(this.info) { - ClassMirror classMirror; try { instance = info.mir.newInstance(new Symbol(""), []); } catch (e) { @@ -237,7 +222,7 @@ class ComplexSetter implements ISetter { if (ele != null) { instance.setField(ele.symbol, val); } - } catch (e, st) { + } catch (e) { print(ele); print(info); throw e; @@ -270,8 +255,6 @@ class BaseSetter { setter = new MapSetter(); } - var length = vals[pos].length; - if (info.isList) { if (_isPrimitive(info.listType)) { for (int i = 0; i < length; i++) { @@ -285,19 +268,15 @@ class BaseSetter { } } } else if (info.isComplex) { - int idx = pos - 1; - final ele = info.elements[keys[idx]]; - if (ele == null) { - pos = pos - length; - } else { - if (_isPrimitive(ele.type)) { - for (int i = 0; i < length; i++) { - pos--; - setter.add(keys[pos], vals[pos]); - } + for (int i = 0; i < length; i++) { + pos--; + final ele = info.elements[keys[pos]]; + if (ele == null || ele.ignore) { + pos--; } else { - for (int i = 0; i < length; i++) { - pos--; + if (_isPrimitive(ele.type)) { + setter.add(keys[pos], vals[pos]); + } else { setter.add(keys[pos], _decodeObj(ele.type)); } } @@ -320,8 +299,8 @@ class BaseSetter { T decodeTest(String json, Type type) { final info = generateElements(type); - List keys = new List(); - List vals = new List(); + final keys = new List(); + final vals = new List(); JSON.decode(json, reviver: (dynamic key, dynamic value) { keys.add(key); vals.add(value); @@ -329,459 +308,422 @@ T decodeTest(String json, Type type) { return new BaseSetter(info, keys, vals).obj(); } -// //FIXME delet this -// String debugJson; -// T decode(String json, Type type) { -// debugJson = json; -// var res = _consume(type); -// return res.data; -// } - -// T decodeMap(Object m, Type type) { -// return _consumeMap(m, type); -// } - -// T _consumeMap(Object m, Type type) { -// final info = generateElements(type); -// ISetter setter; - -// if (info.isComplex) { -// setter = new ComplexSetter(info); -// } else if (info.isList) { -// setter = new ListSetter(); -// } else { -// setter = new MapSetter(); -// } - -// if (info.isList && m is List) { -// if (_isPrimitive(info.listType)) { -// m.forEach((d) => setter.add(0, d)); -// } else { -// m.forEach( -// (d) => setter.add(0, _consumeMap(d, info.listType))); -// } -// } else if (m is Map) { -// m.forEach((k, v) { -// if (_isPrimitive(v.runtimeType)) { -// setter.add(k, v); -// } else { -// final ele = -// info.elements.firstWhere((e) => e.key == k, orElse: () => null); -// if (ele != null) { -// setter.add(k, _consumeMap(v, ele.type)); -// } -// } -// }); -// } else { -// print(m); -// print(m.runtimeType); -// throw new StateError("object is not a map or a list"); -// } -// return setter.obj(); -// } - -// class _Result { -// T data; -// int end; - -// _Result(this.data, this.end); -// } - -// _Result _consume(Type type, {int position: 0, bool onlyOne: false}) { -// final info = generateElements(type); -// ClassMirror classMirror = reflectType(type); -// InstanceMirror instance; -// try { -// instance = classMirror.newInstance(new Symbol(""), []); -// } catch (_) {} - -// Map m; -// if (info.isMap) { -// m = new Map(); -// } -// //flag if we read the starting class opening or not -// bool afterKey = false; -// String key; - -// int idx = position; -// while (idx < debugJson.length) { -// int char = debugJson.codeUnitAt(idx); -// switch (char) { -// case _openClass: -// break; - -// case _closeClass: -// if (onlyOne) { -// return new _Result(instance.reflectee, idx); -// } -// break; - -// case _openArray: -// var endArray = _seekMap[List](idx + 1, end: true) - 1; -// int length = endArray - idx; -// Type arrayType; - -// //set type from element info (object) -// var ele = -// info.elements.firstWhere((e) => e.key == key, orElse: () => null); -// if (ele != null) { -// final List typeArguments = -// reflectType(ele.type).typeArguments; -// arrayType = typeArguments[0].reflectedType; -// } else { -// //object itself is an array so no ele info? -// final List typeArguments = -// reflectType(type).typeArguments; -// arrayType = typeArguments[0].reflectedType; -// } -// List valueList = new List(); - -// int end = 0; -// if (_isPrimitive(arrayType)) { -// while (idx < endArray) { -// int start = _seekMap[arrayType](idx); -// end = _seekMap[arrayType](start + 1, end: true); -// valueList.add(_convMap[arrayType](debugJson.substring(start, end))); -// idx = end + 1; -// } -// } else { -// //array of arrays??? -// while (idx < endArray) { -// var wat = _consume(arrayType, -// position: idx + 1, onlyOne: true); -// valueList.add(wat.data); -// idx = wat.end + 1; -// } -// } - -// if (info.isList) { -// return new _Result>(valueList, idx); -// } else if (ele != null && instance != null) { -// instance.setField(ele.symbol, valueList); -// } else { -// throw new StateError("element null and not a list? invalid json"); -// } - -// break; -// case _closeArray: //end of array -// break; -// case _quote: //read key, read type of ele -// int start = idx; -// int end = _seekMap[String](start + 1, end: true); -// key = _convMap[String](debugJson.substring(start, end)); -// idx = end; - -// Type valueType; - -// var ele = -// info.elements.firstWhere((e) => e.key == key, orElse: () => null); -// if (ele != null) { -// valueType = ele.type; -// } else if (info.isMap) { -// TypeWrap tw = _seekType(idx); -// valueType = tw.type; -// } else { -// TypeWrap tw = _seekType(idx); -// idx = tw.end + 1; -// continue; -// } -// if (_isPrimitive(valueType)) { -// int start = _seekMap[valueType](idx); -// int end = _seekMap[valueType](start + 1, end: true); -// if (info.isMap) { -// m[key] = _convMap[valueType](debugJson.substring(start, end)); -// } else { -// instance.setField(ele.symbol, -// _convMap[valueType](debugJson.substring(start, end))); -// } -// idx = end; -// } else { -// var wat = -// _consume(valueType, position: idx, onlyOne: true); -// if (info.isMap) { -// m[key] = wat.data; -// } else { -// instance.setField(ele.symbol, wat.data); -// } -// idx = wat.end; -// } -// break; -// case _colon: //ignore : -// case _white: //ignore whtiespace -// case _comma: //ignore , -// case _newLine: -// break; -// default: -// } -// idx++; -// } - -// if (info.isMap) return new _Result>(m, idx); -// return new _Result(instance.reflectee, idx); -// } - -// Uint8List stringToByteArray(String json) { -// List encoded; -// try { -// encoded = UTF8.encode(json); -// } on ArgumentError { -// return null; -// } -// return new Uint8List.fromList(encoded); -// } - -// //check if we can drop toList() -// //FIXME -// double _bytesToDouble(String str) { -// return double.parse(str); -// } - -// int _bytesToInt(String str) { -// return int.parse(str); -// } - -// String _bytesToString(String str) { -// return str.substring(1, str.length - 1); -// } - -// bool _bytesToBool(dynamic l) { -// if (l is int) if (l == _boolTrue) return true; -// if (l.first == _boolTrue) return true; -// return false; -// } - -// int _seekList(int start, {bool end: false}) { -// int count = 0; //used to skip nested arrays as well; -// bool inQuote = false; -// for (int c = start; c < debugJson.length; c++) { -// int char = debugJson.codeUnitAt(c); -// if (char == _quote) { -// if (inQuote == true) { -// inQuote = false; -// } else { -// inQuote = true; -// } -// } -// if (char == _openArray) { -// if (inQuote) continue; -// if (!end) return c; -// count++; -// continue; -// } -// if (char == _closeArray) { -// if (inQuote) continue; -// if (count == 0) { -// return c + 1; -// } -// count--; -// } -// } -// throw new StateError( -// "could not find start or end of list from position $start, end: $end"); -// } - -// int _seekString(int start, {bool end: false}) { -// for (int c = start; c < debugJson.length; c++) { -// if (debugJson.codeUnitAt(c) == _quote) { -// if (end) return c + 1; -// return c; -// } -// } -// throw new StateError( -// "could not find start or end of string from position $start, end: $end"); -// } - -// int _seekClass(int start, {bool end: false}) { -// int count = 0; -// bool inQuote = false; -// for (int c = start; c < debugJson.length; c++) { -// int char = debugJson.codeUnitAt(c); -// if (char == _quote) { -// if (inQuote == true) { -// inQuote = false; -// } else { -// inQuote = true; -// } -// } -// if (char == _openClass) { -// if (inQuote) continue; -// if (end) { -// count++; -// } else { -// return c; -// } -// } - -// if (char == _closeClass) { -// if (inQuote) continue; -// if (count == 0) { -// return c + 1; -// } -// if (end) { -// count--; -// } -// } -// } -// throw new StateError( -// "could not find start or end of class from position $start, end: $end"); -// } - -// int _seekNumberOrBool(int start, {bool end: false}) { -// bool inQuote = false; -// for (int c = start; c < debugJson.length; c++) { -// int char = debugJson.codeUnitAt(c); -// if (char == _quote) { -// if (inQuote == true) { -// inQuote = false; -// } else { -// inQuote = true; -// } -// } -// if (char == _comma || -// char == _white || -// char == _newLine || -// char == _colon || -// char == _quote || -// char == _closeArray || -// char == _closeClass || -// char == _openArray || -// char == _openClass) { -// if (end) { -// return c; -// } -// continue; -// } else { -// if (!end && !inQuote) return c; -// } -// } -// return debugJson.length; -// } - -// class TypeWrap { -// final Type type; -// final int end; - -// TypeWrap(this.type, this.end); -// } - -// TypeWrap _seekType(int start) { -// Type type; -// int end = -1; -// int sstart = -1; -// _seekMap.forEach((t, f) { -// try { -// int starttmp = 0; -// int endtmp = 0; -// starttmp = f(start); -// if (sstart == -1) { -// sstart = starttmp; -// } -// endtmp = f(starttmp + 1, end: true); -// if (endtmp - starttmp > end && starttmp <= sstart) { -// end = endtmp - starttmp; -// sstart = starttmp; -// type = t; -// } -// } catch (_) {} -// }); -// if (type == Element) { -// return new TypeWrap(Map, end); -// } -// if (type == num || type == bool || type == int) { -// if (debugJson.substring(start, end).contains(_boolTrue)) { -// return new TypeWrap(bool, end); -// } -// if (debugJson.substring(start, end).contains(_boolFalse)) { -// return new TypeWrap(bool, end); -// } -// return new TypeWrap(num, end); -// } -// return new TypeWrap(type ?? dynamic, end); -// } - -// bool _isPrimitive(Type value) { -// return value == num || -// value == bool || -// value == String || -// value == null || -// value == int || -// value == double; -// } - -// // add to test file lol -// class TestWrapSeek { -// final String json; -// final String result; -// final int start; -// final Function f; - -// TestWrapSeek(this.json, this.result, this.start, this.f); -// } - -// void test12() { -// var list = [ -// new TestWrapSeek(' { } ', '{ }', 0, _seekClass), -// new TestWrapSeek(' { { {}} } ', '{ { {}} }', 0, _seekClass), -// new TestWrapSeek(' { { {}} } ', '{ {}}', 2, _seekClass), -// new TestWrapSeek('{ [] }', '{ [] }', 0, _seekClass), -// new TestWrapSeek(' { } ', '{ }', 0, _seekClass), -// new TestWrapSeek( -// ' {"key": "value{}" } ', '{"key": "value{}" }', 0, _seekClass), -// new TestWrapSeek(' {"key": "{" } ', '{"key": "{" }', 0, _seekClass), -// new TestWrapSeek('""', '""', 0, _seekString), -// new TestWrapSeek('"test \n"', '"test \n"', 0, _seekString), -// new TestWrapSeek('"test \t"', '"test \t"', 0, _seekString), -// new TestWrapSeek('" ""', '""', 2, _seekString), -// new TestWrapSeek('[]', '[]', 0, _seekList), -// new TestWrapSeek('[ [ ] [ ] []]', '[ [ ] [ ] []]', 0, _seekList), -// new TestWrapSeek(' [ ] ', '[ ]', 0, _seekList), -// new TestWrapSeek(' ["[", "" ] ', '["[", "" ]', 0, _seekList), -// new TestWrapSeek('{"test": 0.1} ', '0.1', 0, _seekNumberOrBool), -// new TestWrapSeek('{"test": true} ', 'true', 0, _seekNumberOrBool), -// new TestWrapSeek('{"test": false} ', 'false', 0, _seekNumberOrBool), -// new TestWrapSeek(' 0.1', '0.1', 0, _seekNumberOrBool), -// new TestWrapSeek('0.1 ', '0.1', 0, _seekNumberOrBool), -// ]; - -// list.forEach((w) { -// try { -// var start = w.f(UTF8.encode(w.json), w.start); -// var end = w.f(UTF8.encode(w.json), start + 1, end: true); -// if (w.result != -// UTF8.decode(UTF8.encode(w.json).getRange(start, end).toList())) { -// print( -// "test failed\n json should be ${w.result} but is ${UTF8.decode(UTF8.encode(w.json).getRange(start, end).toList())}"); -// } -// } catch (e) { -// print( -// "crashed test\n json: ${w.json}\n result: ${w.result}\n pos: ${w.start}"); -// print(e); -// } -// }); -// } - -// class TestWrapType { -// final String json; -// final Type type; -// final int start; -// TestWrapType(this.json, this.start, this.type); -// } - -// void test13() { -// var list = [ -// new TestWrapType(' { } ', 0, Map), -// new TestWrapType(' [] ', 0, List), -// new TestWrapType(' "" ', 0, String), -// new TestWrapType(' 0.12 ', 0, num), -// new TestWrapType(' true ', 0, bool), -// new TestWrapType(' 1 ', 0, num), -// ]; - -// list.forEach((w) { -// TypeWrap t = _seekType(w.start); -// if (t.type != w.type) { -// print( -// "test failed\n type for ${w.json} should be ${w.type} but is ${t.type}"); -// } -// }); -// } +//FIXME delet this +String debugJson; +T decode(String json, Type type) { + debugJson = json; + var res = _consume(type); + return res.data; +} + +class _Result { + T data; + int end; + + _Result(this.data, this.end); +} + +_Result _consume(Type type, {int position: 0, bool onlyOne: false}) { + final info = generateElements(type); + ClassMirror classMirror = reflectType(type); + InstanceMirror instance; + try { + instance = classMirror.newInstance(new Symbol(""), []); + } catch (_) {} + + Map m; + if (info.isMap) { + m = new Map(); + } + //flag if we read the starting class opening or not + bool afterKey = false; + String key; + + int idx = position; + while (idx < debugJson.length) { + int char = debugJson.codeUnitAt(idx); + switch (char) { + case _openClass: + break; + + case _closeClass: + if (onlyOne) { + return new _Result(instance.reflectee, idx); + } + break; + + case _openArray: + var endArray = _seekMap[List](idx + 1, end: true) - 1; + int length = endArray - idx; + Type arrayType; + + //set type from element info (object) + var ele = info.elements[key]; + if (ele != null) { + final List typeArguments = + reflectType(ele.type).typeArguments; + arrayType = typeArguments[0].reflectedType; + } else { + //object itself is an array so no ele info? + final List typeArguments = + reflectType(type).typeArguments; + arrayType = typeArguments[0].reflectedType; + } + List valueList = new List(); + + int end = 0; + if (_isPrimitive(arrayType)) { + while (idx < endArray) { + int start = _seekMap[arrayType](idx); + end = _seekMap[arrayType](start + 1, end: true); + valueList.add(_convMap[arrayType](debugJson.substring(start, end))); + idx = end + 1; + } + } else { + //array of arrays??? + while (idx < endArray) { + var wat = _consume(arrayType, + position: idx + 1, onlyOne: true); + valueList.add(wat.data); + idx = wat.end + 1; + } + } + + if (info.isList) { + return new _Result>(valueList, idx); + } else if (ele != null && instance != null) { + instance.setField(ele.symbol, valueList); + } else { + throw new StateError("element null and not a list? invalid json"); + } + + break; + case _closeArray: //end of array + break; + case _quote: //read key, read type of ele + int start = idx; + int end = _seekMap[String](start + 1, end: true); + key = _convMap[String](debugJson.substring(start, end)); + idx = end; + + Type valueType; + + var ele = info.elements[key]; + if (ele != null) { + valueType = ele.type; + } else if (info.isMap) { + TypeWrap tw = _seekType(idx); + valueType = tw.type; + } else { + TypeWrap tw = _seekType(idx); + idx = tw.end + 1; + continue; + } + if (_isPrimitive(valueType)) { + int start = _seekMap[valueType](idx); + int end = _seekMap[valueType](start + 1, end: true); + if (info.isMap) { + m[key] = _convMap[valueType](debugJson.substring(start, end)); + } else { + instance.setField(ele.symbol, + _convMap[valueType](debugJson.substring(start, end))); + } + idx = end; + } else { + var wat = + _consume(valueType, position: idx, onlyOne: true); + if (info.isMap) { + m[key] = wat.data; + } else { + instance.setField(ele.symbol, wat.data); + } + idx = wat.end; + } + break; + case _colon: //ignore : + case _white: //ignore whtiespace + case _comma: //ignore , + case _newLine: + break; + default: + } + idx++; + } + + if (info.isMap) return new _Result>(m, idx); + return new _Result(instance.reflectee, idx); +} + +Uint8List stringToByteArray(String json) { + List encoded; + try { + encoded = UTF8.encode(json); + } on ArgumentError { + return null; + } + return new Uint8List.fromList(encoded); +} + +//check if we can drop toList() +//FIXME +double _bytesToDouble(String str) { + return double.parse(str); +} + +int _bytesToInt(String str) { + return int.parse(str); +} + +String _bytesToString(String str) { + return str.substring(1, str.length - 1); +} + +bool _bytesToBool(dynamic l) { + if (l is int) if (l == _boolTrue) return true; + if (l.first == _boolTrue) return true; + return false; +} + +int _seekList(int start, {bool end: false}) { + int count = 0; //used to skip nested arrays as well; + bool inQuote = false; + for (int c = start; c < debugJson.length; c++) { + int char = debugJson.codeUnitAt(c); + if (char == _quote) { + if (inQuote == true) { + inQuote = false; + } else { + inQuote = true; + } + } + if (char == _openArray) { + if (inQuote) continue; + if (!end) return c; + count++; + continue; + } + if (char == _closeArray) { + if (inQuote) continue; + if (count == 0) { + return c + 1; + } + count--; + } + } + throw new StateError( + "could not find start or end of list from position $start, end: $end"); +} + +int _seekString(int start, {bool end: false}) { + for (int c = start; c < debugJson.length; c++) { + if (debugJson.codeUnitAt(c) == _quote) { + if (end) return c + 1; + return c; + } + } + throw new StateError( + "could not find start or end of string from position $start, end: $end"); +} + +int _seekClass(int start, {bool end: false}) { + int count = 0; + bool inQuote = false; + for (int c = start; c < debugJson.length; c++) { + int char = debugJson.codeUnitAt(c); + if (char == _quote) { + if (inQuote == true) { + inQuote = false; + } else { + inQuote = true; + } + } + if (char == _openClass) { + if (inQuote) continue; + if (end) { + count++; + } else { + return c; + } + } + + if (char == _closeClass) { + if (inQuote) continue; + if (count == 0) { + return c + 1; + } + if (end) { + count--; + } + } + } + throw new StateError( + "could not find start or end of class from position $start, end: $end"); +} + +int _seekNumberOrBool(int start, {bool end: false}) { + bool inQuote = false; + for (int c = start; c < debugJson.length; c++) { + int char = debugJson.codeUnitAt(c); + if (char == _quote) { + if (inQuote == true) { + inQuote = false; + } else { + inQuote = true; + } + } + if (char == _comma || + char == _white || + char == _newLine || + char == _colon || + char == _quote || + char == _closeArray || + char == _closeClass || + char == _openArray || + char == _openClass) { + if (end) { + return c; + } + continue; + } else { + if (!end && !inQuote) return c; + } + } + return debugJson.length; +} + +class TypeWrap { + final Type type; + final int end; + + TypeWrap(this.type, this.end); +} + +TypeWrap _seekType(int start) { + Type type; + int end = -1; + int sstart = -1; + _seekMap.forEach((t, f) { + try { + int starttmp = 0; + int endtmp = 0; + starttmp = f(start); + if (sstart == -1) { + sstart = starttmp; + } + endtmp = f(starttmp + 1, end: true); + if (endtmp - starttmp > end && starttmp <= sstart) { + end = endtmp - starttmp; + sstart = starttmp; + type = t; + } + } catch (_) {} + }); + if (type == Element) { + return new TypeWrap(Map, end); + } + if (type == num || type == bool || type == int) { + if (debugJson.substring(start, end).contains(_boolTrue)) { + return new TypeWrap(bool, end); + } + if (debugJson.substring(start, end).contains(_boolFalse)) { + return new TypeWrap(bool, end); + } + return new TypeWrap(num, end); + } + return new TypeWrap(type ?? dynamic, end); +} + +// add to test file lol +class TestWrapSeek { + final String json; + final String result; + final int start; + final Function f; + + TestWrapSeek(this.json, this.result, this.start, this.f); +} + +void test12() { + var list = [ + new TestWrapSeek(' { } ', '{ }', 0, _seekClass), + new TestWrapSeek(' { { {}} } ', '{ { {}} }', 0, _seekClass), + new TestWrapSeek(' { { {}} } ', '{ {}}', 2, _seekClass), + new TestWrapSeek('{ [] }', '{ [] }', 0, _seekClass), + new TestWrapSeek(' { } ', '{ }', 0, _seekClass), + new TestWrapSeek( + ' {"key": "value{}" } ', '{"key": "value{}" }', 0, _seekClass), + new TestWrapSeek(' {"key": "{" } ', '{"key": "{" }', 0, _seekClass), + new TestWrapSeek('""', '""', 0, _seekString), + new TestWrapSeek('"test \n"', '"test \n"', 0, _seekString), + new TestWrapSeek('"test \t"', '"test \t"', 0, _seekString), + new TestWrapSeek('" ""', '""', 2, _seekString), + new TestWrapSeek('[]', '[]', 0, _seekList), + new TestWrapSeek('[ [ ] [ ] []]', '[ [ ] [ ] []]', 0, _seekList), + new TestWrapSeek(' [ ] ', '[ ]', 0, _seekList), + new TestWrapSeek(' ["[", "" ] ', '["[", "" ]', 0, _seekList), + new TestWrapSeek('{"test": 0.1} ', '0.1', 0, _seekNumberOrBool), + new TestWrapSeek('{"test": true} ', 'true', 0, _seekNumberOrBool), + new TestWrapSeek('{"test": false} ', 'false', 0, _seekNumberOrBool), + new TestWrapSeek(' 0.1', '0.1', 0, _seekNumberOrBool), + new TestWrapSeek('0.1 ', '0.1', 0, _seekNumberOrBool), + ]; + + list.forEach((w) { + try { + var start = w.f(UTF8.encode(w.json), w.start); + var end = w.f(UTF8.encode(w.json), start + 1, end: true); + if (w.result != + UTF8.decode(UTF8.encode(w.json).getRange(start, end).toList())) { + print( + "test failed\n json should be ${w.result} but is ${UTF8.decode(UTF8.encode(w.json).getRange(start, end).toList())}"); + } + } catch (e) { + print( + "crashed test\n json: ${w.json}\n result: ${w.result}\n pos: ${w.start}"); + print(e); + } + }); +} + +class TestWrapType { + final String json; + final Type type; + final int start; + TestWrapType(this.json, this.start, this.type); +} + +void test13() { + var list = [ + new TestWrapType(' { } ', 0, Map), + new TestWrapType(' [] ', 0, List), + new TestWrapType(' "" ', 0, String), + new TestWrapType(' 0.12 ', 0, num), + new TestWrapType(' true ', 0, bool), + new TestWrapType(' 1 ', 0, num), + ]; + + list.forEach((w) { + TypeWrap t = _seekType(w.start); + if (t.type != w.type) { + print( + "test failed\n type for ${w.json} should be ${w.type} but is ${t.type}"); + } + }); +} + +final _convMap = { + bool: _bytesToBool, + int: _bytesToInt, + String: _bytesToString, + double: _bytesToDouble +}; + +typedef int Seek(int start, {bool end}); +final _seekMap = { + bool: _seekNumberOrBool, + int: _seekNumberOrBool, + double: _seekNumberOrBool, + String: _seekString, + List: _seekList, + Element: _seekClass +}; diff --git a/lib/test/annotation_test.dart b/lib/test/annotation_test.dart new file mode 100644 index 0000000..ac01037 --- /dev/null +++ b/lib/test/annotation_test.dart @@ -0,0 +1,88 @@ +import 'package:json_conv/json_conv.dart' as conv; +import 'package:test/test.dart'; + +void main() { + test('validAnnotation', () { + final orig = new AnnotationSample.withValues(1, "test text"); + final json = '{"id":1, "test":"test text"}'; + final sample = conv.decodeTest(json, AnnotationSample); + + expect(sample, new isInstanceOf()); + expect(sample, equals(orig)); + }); + + test('wrongNamedAnnotation', () { + final json = '{"id":1, "test":"test text"}'; + final sample = conv.decodeTest(json, AnnotationSample2); + + expect(sample, isNotNull); + expect(sample, new isInstanceOf()); + expect(sample.id, equals(1)); + expect(sample.text, equals(null)); + }); + + test('ignoreAnnotation', () { + final json = '{"id":1, "text":"test text"}'; + final sample = conv.decodeTest(json, AnnotationSample3); + expect(sample, isNotNull); + expect(sample, new isInstanceOf()); + expect(sample.id, equals(1)); + expect(sample.text, equals(null)); + }); + + test('ignoreAnnotation2', () { + final json = '{"id":1, "text":"test text"}'; + final sample = conv.decodeTest(json, AnnotationSample4); + expect(sample, isNotNull); + expect(sample, new isInstanceOf()); + expect(sample.id, equals(1)); + expect(sample.text, equals("test text")); + }); +} + +class AnnotationSample { + int id; + + @conv.Property(name: "test") + String text; + + AnnotationSample(); + + AnnotationSample.withValues(this.id, this.text); + + bool operator ==(dynamic o) => + o is AnnotationSample && o.id == id && o.text == text; +} + +class AnnotationSample2 { + int id; + + @conv.Property(name: "test1") + String text; + + AnnotationSample2(); + + AnnotationSample2.withValues(this.id, this.text); +} + +class AnnotationSample3 { + int id; + + @conv.Property(ignore: true) + String text; + + AnnotationSample3(); + + AnnotationSample3.withValues(this.id, this.text); +} + +class AnnotationSample4 { + int id; + + @conv.Property(ignore: false) + String text; + + AnnotationSample4(); + + AnnotationSample4.withValues(this.id, this.text); +} diff --git a/lib/test/decode_test.dart b/lib/test/decode_test.dart new file mode 100644 index 0000000..6add925 --- /dev/null +++ b/lib/test/decode_test.dart @@ -0,0 +1,94 @@ +import 'package:json_conv/json_conv.dart' as conv; +import 'package:test/test.dart'; +import 'shared.dart'; + +void main() { + test('extendedDecoding', () { + final json = '{"id":1, "text":"test text", "isValid":false}'; + final sample = conv.decodeTest(json, ExtenderClass); + + expect(sample, new isInstanceOf()); + expect(sample.id, equals(1)); + expect(sample.text, equals("test text")); + expect(sample.isValid, equals(false)); + }); + + test('listComplexDecoding', () { + final json = + '{"list": [{"id":0, "text":"0"}, {"id":1, "text":"1"},{"id":2, "text":"2"}]}'; + final sample = conv.decodeTest(json, ListComplex); + + expect(sample, isNotNull); + expect(sample.list.length, equals(3)); + for (int i = 0; i < 3; i++) { + final val = sample.list.firstWhere( + (e) => e.text == i.toString() && e.id == i, + orElse: () => null); + expect(val, isNotNull); + } + }); + + test('listComplexDecoding2', () { + final json = + '[{"id":0, "text":"0"}, {"id":1, "text":"1"},{"id":2, "text":"2"}]'; + final sample = conv.decodeTest>( + json, new List().runtimeType); + + expect(sample, isNotNull); + expect(sample.length, equals(3)); + for (int i = 0; i < 3; i++) { + final val = sample.firstWhere((e) => e.text == i.toString() && e.id == i, + orElse: () => null); + expect(val, isNotNull); + } + }); + + test('listSimpleDecoding', () { + final json = '{"list": ["0", "1", "2", "3"]}'; + final sample = conv.decodeTest(json, ListSimple); + + expect(sample, isNotNull); + expect(sample.list.length, equals(4)); + for (int i = 0; i < 4; i++) { + final val = + sample.list.firstWhere((e) => e == i.toString(), orElse: () => null); + expect(val, isNotNull); + } + }); + + test('listSimpleDecoding2', () { + final json = '["0", "1", "2", "3"]'; + final sample = + conv.decodeTest>(json, new List().runtimeType); + + expect(sample, isNotNull); + expect(sample.length, equals(4)); + for (int i = 0; i < 4; i++) { + final val = + sample.firstWhere((e) => e == i.toString(), orElse: () => null); + expect(val, isNotNull); + } + }); + + test('embedDecoding', () { + final json = '{"test": "test string", "person":{"name": "name"}}'; + final sample = conv.decodeTest(json, Male); + + expect(sample, isNotNull); + expect(sample.test, equals("test string")); + expect(sample.person.name, equals("name")); + }); + + test('mapComplexDecoding', () { + final json = + '{"person1": {"name": "name"},"person2": {"name": "name2"},"person3": {"name": "name3"}}'; + final sample = conv.decodeTest(json, MapComplex); + + expect(sample, isNotNull); + expect(sample.persons, isNotNull); + expect(sample.persons.length, equals(3)); + expect(sample.persons["person1"].name, equals("name")); + expect(sample.persons["person2"].name, equals("name2")); + expect(sample.persons["person3"].name, equals("name3")); + }); +} diff --git a/lib/test/deserialization_test.dart b/lib/test/deserialization_test.dart deleted file mode 100644 index 087b8d8..0000000 --- a/lib/test/deserialization_test.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:json_conv/json_conv.dart' as conv; -import 'package:test/test.dart'; -import 'shared.dart'; - -main() { - conv.debug = false; - - group('deserialization', () { - test('deserialize primitives', testDeserializationOfPrimitives); - - test('deserialize maps', testDeserializationOfMaps); - - test('deserialize lists + reflection', - testDeserializationOfListsAsWellAsViaReflection); - - test('deserialize with schema validation', - testDeserializationWithSchemaValidation); - }); -} - -testDeserializationOfPrimitives() { - expect(conv.deserialize('1'), equals(1)); - expect(conv.deserialize('1.4'), equals(1.4)); - expect(conv.deserialize('"Hi!"'), equals("Hi!")); - expect(conv.deserialize("true"), equals(true)); - expect(conv.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"}}}'; - Map simple = conv.deserialize(simpleJson); - Map nested = conv.deserialize(nestedJson); - - 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')); -} - -testDeserializationOfListsAsWellAsViaReflection() { - String json = '''[ - { - "hello": "world", - "nested": [] - }, - { - "hello": "dolly", - "nested": [ - { - "bar": "baz" - }, - { - "bar": "fight" - } - ] - } - ] - '''; - - List list = - conv.deserialize(json, outputType: new List().runtimeType); - 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"]}'; - - BabelRc deserialized = conv.deserialize(babelRcJson, outputType: BabelRc); - - 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/lib/test/serialization_test.dart b/lib/test/serialization_test.dart deleted file mode 100644 index 71283c9..0000000 --- a/lib/test/serialization_test.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'dart:convert' show JSON; -import 'package:json_conv/json_conv.dart' as conv; -import 'package:test/test.dart'; -import 'shared.dart'; - -main() { - conv.debug = false; - - 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(conv.serialize(1), equals("1")); - expect(conv.serialize(1.4), equals("1.4")); - expect(conv.serialize("Hi!"), equals('"Hi!"')); - expect(conv.serialize(true), equals("true")); - expect(conv.serialize(null), equals("null")); -} - -testSerializationOfDates() { - DateTime date = new DateTime.now(); - String json = conv.serialize({'date': date}); - - print(json); - - Map deserialized = JSON.decode(json); - expect(deserialized['date'], equals(date.toIso8601String())); -} - -testSerializationOfMaps() { - Map simple = JSON.decode(conv.serialize( - {'hello': 'world', 'one': 1, 'class': new SampleClass('world')})); - Map nested = JSON.decode(conv.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 json = conv.serialize(pandorasBox); - print(json); - - List deserialized = JSON.decode(json); - - 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 json = conv.serialize(sample); - print(json); - - Map deserialized = JSON.decode(json); - 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 json = conv.serialize(babelRc); - print(json); - - Map deserialized = JSON.decode(json); - - 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 = conv.serialize(babelRc); - print(json2); -} diff --git a/lib/test/shared.dart b/lib/test/shared.dart index f6fb08a..8047114 100644 --- a/lib/test/shared.dart +++ b/lib/test/shared.dart @@ -1,43 +1,36 @@ import 'package:json_conv/json_conv.dart'; -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 {} +class ExtendeeClass { + int id; + String text; +} + +class ExtenderClass extends ExtendeeClass { + bool isValid; +} + +class ListSimple { + List list; +} + +class ListComplex { + List list; +} + +class MapComplex { + Map persons; +} + +class MapSimple { + Map persons; +} + +class Person { + String name; +} + +class Male { + Person person; + String test; +} + diff --git a/lib/test/to_json_test.dart b/lib/test/to_json_test.dart deleted file mode 100644 index 4e8269d..0000000 --- a/lib/test/to_json_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:json_conv/json_conv.dart' as conv; -import 'package:test/test.dart'; - -main() { - test('fromJson', () { - conv.debug = false; - Foo foo = conv.deserialize('{"bar":"baz"}', outputType: Foo); - - expect(foo, new isInstanceOf()); - expect(foo.text, equals('baz')); - }); - - test('toJson', () { - var foo = new Foo(text: 'baz'); - var data = conv.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']); - - Map toJson() => {'bar': text, 'foo': foo}; -}