├── .gitignore ├── test ├── test_to_string.dart ├── test_extend_object.dart ├── test_print_list.dart ├── test_list_serialization.dart ├── test_is_extendable.dart ├── test_json_stringify.dart ├── test_list.dart ├── test_strong_typing.dart ├── json_object_test.dart ├── test_sample_data.dart ├── test_todo_vo.dart ├── test_dartlang_article.dart └── test_mirrors_serialize.dart ├── pubspec.yaml ├── .project ├── CHANGELOG.md ├── lib ├── src │ ├── mirror_based_deserializer.dart │ └── mirror_based_serializer.dart └── json_object.dart ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | packages/ 2 | test/packages/ 3 | -------------------------------------------------------------------------------- /test/test_to_string.dart: -------------------------------------------------------------------------------- 1 | part of json_object_test; 2 | 3 | // test issue #4 4 | testToString() { 5 | var user = new User(); 6 | user.name = "Mike"; 7 | _log(user.toString()); 8 | expect(user.toString(),equals('{"name":"Mike"}')); 9 | } 10 | 11 | 12 | class User extends JsonObject { 13 | 14 | } 15 | 16 | -------------------------------------------------------------------------------- /test/test_extend_object.dart: -------------------------------------------------------------------------------- 1 | part of json_object_test; 2 | 3 | @proxy 4 | class Person2 extends JsonObject { 5 | 6 | } 7 | 8 | testExtendObject() { 9 | Person2 person = new Person2(); 10 | person.name = "blah"; 11 | var s = new JsonEncoder().convert(person); 12 | expect(s, equals('{"name":"blah"}')); 13 | 14 | 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: json_object 2 | version: 1.0.19+1 3 | authors: 4 | - Chris Buckett 5 | - Steven Roose 6 | description: Allow use of JSON Maps in dot notation format 7 | homepage: https://github.com/chrisbu/dartwatch-JsonObject 8 | environment: 9 | sdk: '>=0.6.0+1.r24898' 10 | dev_dependencies: 11 | unittest: '>=0.6.0+1' 12 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | dartwatch-JsonObject 4 | 5 | 6 | 7 | 8 | 9 | com.google.dart.tools.core.dartBuilder 10 | 11 | 12 | 13 | 14 | 15 | com.google.dart.tools.core.dartNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/test_print_list.dart: -------------------------------------------------------------------------------- 1 | 2 | part of json_object_test; 3 | 4 | // github: issue 15 5 | 6 | void testPrintList() { 7 | MyList2 list = new MyList2.fromString('[{"x":161,"y":37},{"x":143,"y":177}]'); 8 | _log(list[0]); 9 | _log(list.length); 10 | _log(list); 11 | } 12 | 13 | class MyList2 extends JsonObject { 14 | 15 | MyList2(); 16 | 17 | factory MyList2.fromString(String jsonString) { 18 | return new JsonObject.fromJsonString(jsonString, new MyList2()); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.19 4 | 5 | - Added a `recursive` parameter to constructors to allow for disabling recursive conversion to JsonObjects. 6 | 7 | ## 1.0.18+2 07/11/2013 8 | 9 | - Doc fixes. 10 | 11 | ## 1.0.18 07/11/2013 12 | 13 | - Changed stringify to encode 14 | - Removed meta package requirement 15 | - All tests passing with 0.8.10.3_r29803 16 | 17 | ## 1.0.17 17/Sept/2013 18 | 19 | - Added @proxy 20 | - Migrated from dart:json to dart:convert library 21 | - All tests passing with 0.7.2.1_r27268 -------------------------------------------------------------------------------- /test/test_list_serialization.dart: -------------------------------------------------------------------------------- 1 | part of json_object_test; 2 | 3 | testListSerialization() { 4 | var tags = new List(); 5 | tags.add("Foo"); 6 | tags.add("Bar"); 7 | var filter = searchElements(tags); 8 | 9 | expect(filter.filter.tags[0],"Foo"); 10 | expect(filter.filter.tags[1],"Bar"); 11 | } 12 | 13 | 14 | searchElements(List tags) { 15 | JsonObject filter = new JsonObject(); 16 | filter.filter = new JsonObject(); //this is on purpose 17 | if( tags != null && ! tags.isEmpty) { 18 | filter.filter.tags = tags; 19 | } 20 | 21 | return filter; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /lib/src/mirror_based_deserializer.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | // TODO! (for my reminder only...) 5 | 6 | // convert the json data into a real object 7 | //_extractElementsWithMirrors(className, jsonData); 8 | 9 | // ClassMirror requiredClass = currentMirrorSystem().isolate.rootLibrary.classes[className]; 10 | // var newInstance = requiredClass.newInstance("", []); 11 | // 12 | // 13 | // newInstance.then((InstanceMirror instance) { 14 | // 15 | // jsonMap.keys.forEach((key) { 16 | // print(key); 17 | // if (instance.type.setters.containsKey(key)) { 18 | // instance.setField(key, jsonMap[key]); 19 | // } 20 | // }); // for each key 21 | // this.instance = instance.reflectee; 22 | // }); // transform -------------------------------------------------------------------------------- /test/test_is_extendable.dart: -------------------------------------------------------------------------------- 1 | part of json_object_test; 2 | 3 | testIsExtendable() { 4 | _log("testIsExtendable"); 5 | 6 | JsonObject person = new JsonObject(); 7 | //isExtendable is currently set to true, so 8 | //we can dynamically add new items 9 | person.name = "Chris"; 10 | person.languages = ["Java","Dart","C#"]; 11 | expect(person.name, equals("Chris")); 12 | 13 | 14 | //but we can stop it being extendable, to provide a bit more checking 15 | person.isExtendable = false; 16 | JsonObjectException expectedException = null; 17 | try { 18 | person.namz = "Bob"; //check for our namz typo - this should throw exception 19 | } 20 | on JsonObjectException catch (ex) { 21 | expectedException = ex; 22 | } 23 | 24 | //assert 25 | // expect(expectedException, isNotNull); 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /test/test_json_stringify.dart: -------------------------------------------------------------------------------- 1 | part of json_object_test; 2 | 3 | testJsonStringify() { 4 | _log("testJsonStringify"); 5 | JsonObject person = new JsonObject(); 6 | 7 | // dynamically created some properties 8 | person.name = "Chris"; 9 | person.languages = new List(); 10 | person.languages.add("Dart"); 11 | person.languages.add("Java"); 12 | 13 | // create a new JsonObject that we will inject 14 | JsonObject address = new JsonObject(); 15 | address.line1 = "1 the street"; 16 | address.postcode = "AB12 3DE"; 17 | 18 | // add the address to the person 19 | person.address = address; 20 | 21 | // convert to a json string: 22 | String json = new JsonEncoder().convert(person); 23 | // convert back to a json map - the JSON stringifg changes periodically, 24 | // breaking this test 25 | Map convertedBack = new JsonDecoder(null).convert(json); 26 | 27 | // assert 28 | expect(convertedBack["address"]["line1"], equals(address.line1)); 29 | expect(convertedBack["name"], equals(person.name)); 30 | 31 | 32 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012 Chris Buckett (chrisbuckett@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/test_list.dart: -------------------------------------------------------------------------------- 1 | part of json_object_test; 2 | 3 | // http://stackoverflow.com/questions/14462007/parsing-json-list-with-jsonobject-library-in-dart 4 | 5 | testList() { 6 | var testJson = """ 7 | [{"Dis":1111.1,"Flag":0,"Obj":{"ID":1,"Title":"Volvo 140"}}, 8 | {"Dis":2222.2,"Flag":0,"Obj":{"ID":2,"Title":"Volvo 240"}}] 9 | """; 10 | MyList list = new MyList.fromString(testJson); 11 | _log(list[0].Obj.Title); 12 | expect(list[0].Obj.Title, equals("Volvo 140")); 13 | } 14 | 15 | testListIterator() { 16 | var testJson = """ 17 | [{"Dis":1111.1,"Flag":0,"Obj":{"ID":1,"Title":"Volvo 140"}}, 18 | {"Dis":2222.2,"Flag":0,"Obj":{"ID":2,"Title":"Volvo 240"}}] 19 | """; 20 | MyList jsonObject = new MyList.fromString(testJson); 21 | 22 | var firstTitle= ""; 23 | var secondTitle = ""; 24 | 25 | for (var item in jsonObject.toIterable()) { 26 | if (firstTitle == "") { 27 | firstTitle = item.Obj.Title; 28 | } 29 | else { 30 | secondTitle = item.Obj.Title; 31 | } 32 | } 33 | 34 | expect(firstTitle, equals("Volvo 140")); 35 | expect(secondTitle, equals("Volvo 240")); 36 | } 37 | 38 | class MyList extends JsonObject { 39 | MyList(); 40 | 41 | factory MyList.fromString(String jsonString) { 42 | return new JsonObject.fromJsonString(jsonString, new MyList()); 43 | } 44 | } -------------------------------------------------------------------------------- /test/test_strong_typing.dart: -------------------------------------------------------------------------------- 1 | part of json_object_test; 2 | 3 | @proxy 4 | class Person extends JsonObject { 5 | // need a default, private constructor 6 | Person(); 7 | 8 | factory Person.fromString(String jsonString) { 9 | return new JsonObject.fromJsonString(jsonString, new Person()); 10 | } 11 | } 12 | 13 | 14 | abstract class AddressList extends JsonObject implements List { 15 | Address address; 16 | } 17 | 18 | abstract class Address extends JsonObject { 19 | String line1; 20 | String postcode; 21 | } 22 | 23 | testStrongTyping_new() { 24 | Person person = new Person(); 25 | person.name = "Chris"; 26 | expect(person.name,equals("Chris")); 27 | } 28 | 29 | testStrongTyping_fromJsonString() { 30 | _log("testStrongTyping"); 31 | var jsonString = _getStrongTypingJsonString(); 32 | 33 | // Create a new JSON object which looks like our Person 34 | // A Person Interface extends the JsonObject, so no 35 | // warning is reported 36 | 37 | Person person = new Person.fromString(jsonString); // this will not fail 38 | 39 | 40 | 41 | //verify property access 42 | expect(person.addresses[0].address.line1, equals("1 the street")); 43 | 44 | var noSuchMethodException = null; 45 | try { 46 | //this should throw an exception 47 | //as it doesn't exist on Person 48 | //and it is a valid warning 49 | person.wibble; 50 | } 51 | catch (ex) { 52 | noSuchMethodException = ex; 53 | } 54 | 55 | //expect(noSuchMethodException != null); 56 | 57 | } 58 | 59 | 60 | _getStrongTypingJsonString() { 61 | //Create the JSON which looks like our interface structure 62 | var jsonString = """ 63 | { 64 | "addresses" : [ 65 | { "address": { 66 | "line1": "1 the street", 67 | "postcode": "ab12 3de" 68 | } 69 | }, 70 | { "address": { 71 | "line1": "1 the street", 72 | "postcode": "ab12 3de" 73 | } 74 | } 75 | ] 76 | } 77 | """; 78 | return jsonString; 79 | } -------------------------------------------------------------------------------- /test/json_object_test.dart: -------------------------------------------------------------------------------- 1 | library json_object_test; 2 | 3 | import "package:unittest/unittest.dart"; 4 | 5 | import "../lib/json_object.dart"; 6 | 7 | import "dart:convert"; 8 | //import 'package:meta/meta.dart'; 9 | 10 | part "test_strong_typing.dart"; 11 | part "test_sample_data.dart"; 12 | part "test_json_stringify.dart"; 13 | part "test_is_extendable.dart"; 14 | part "test_extend_object.dart"; 15 | part "test_to_string.dart"; 16 | part "test_dartlang_article.dart"; 17 | part "test_todo_vo.dart"; 18 | part "test_print_list.dart"; 19 | part "test_list_serialization.dart"; 20 | part "test_mirrors_serialize.dart"; 21 | part "test_list.dart"; 22 | 23 | void _log(obj) { 24 | if (enableJsonObjectDebugMessages) print(obj); 25 | } 26 | 27 | void main() { 28 | enableJsonObjectDebugMessages = true; 29 | 30 | test('sample data', () { 31 | testSampleData(); // passes build 14458 32 | }); 33 | 34 | group('strong typing', () { 35 | test('new', () { 36 | testStrongTyping_new(); // passes build 14458 37 | }); 38 | 39 | test('from json string', () { 40 | testStrongTyping_fromJsonString(); // passes build 14458 41 | }); 42 | }); 43 | 44 | test('json stringify', () { 45 | testJsonStringify(); // passes build 8942 46 | }); 47 | 48 | test('is extendable', () { 49 | testIsExtendable(); // passes build 14458 50 | }); 51 | 52 | test('extend object', () { 53 | testExtendObject(); // passes build 14458 54 | }); 55 | 56 | test('toString', () { 57 | testToString(); // passes build 14458 58 | }); 59 | 60 | group('dartlang article', () { 61 | test('fromJson', () { 62 | testDartlangArticle_fromJson(); // passes build 14458 63 | }); 64 | 65 | test('new', () { 66 | testDartlangArticle_new(); // passes build 14458 67 | }); 68 | 69 | }); 70 | 71 | test('toTodoVO', () { 72 | testTodoVO(); 73 | }); 74 | 75 | test('list', () { 76 | testList(); 77 | testListIterator(); 78 | testPrintList(); 79 | }); 80 | 81 | test('list seralization', () { 82 | testListSerialization(); 83 | }); 84 | 85 | 86 | 87 | // Broken 88 | // testMirrorsSerialize(); // tests converting a class to JSON 89 | } 90 | 91 | 92 | -------------------------------------------------------------------------------- /test/test_sample_data.dart: -------------------------------------------------------------------------------- 1 | part of json_object_test; 2 | 3 | bool testSampleData() { 4 | _log("testSampleData"); 5 | var jsonString = """ 6 | { 7 | "name" : "Chris", 8 | "languages" : ["Dart","Java","C#","Python"], 9 | "handles" : { 10 | "googlePlus": {"name":"+Chris Buckett"}, 11 | "twitter" : {"name":"@ChrisDoesDev"} 12 | }, 13 | "blogs" : [ 14 | { 15 | "name": "Dartwatch", 16 | "url": "http://dartwatch.com" 17 | }, 18 | { 19 | "name": "ChrisDoesDev", 20 | "url": "http://chrisdoesdev.com" 21 | } 22 | ], 23 | "books" : [ 24 | { 25 | "title": "Dart in Action", 26 | "chapters": [ 27 | { 28 | "chapter1" : "Introduction to Dart", 29 | "pages" : ["page1","page2","page3"] 30 | }, 31 | { "chapter2" : "Dart tools"} 32 | ] 33 | } 34 | ] 35 | } 36 | """; 37 | 38 | var o = new JsonObject.fromJsonString(jsonString); 39 | 40 | //basic access 41 | expect("Chris", equals(o.name)); 42 | expect("Dart", equals(o.languages[0])); 43 | expect("Java", equals(o.languages[1])); 44 | 45 | //maps within maps 46 | expect("+Chris Buckett", equals(o.handles.googlePlus.name)); 47 | expect("@ChrisDoesDev", equals(o.handles.twitter.name)); 48 | 49 | //maps within lists 50 | expect("Dartwatch", equals(o.blogs[0].name)); 51 | expect("http://dartwatch.com", equals(o.blogs[0].url)); 52 | expect("ChrisDoesDev",equals(o.blogs[1].name)); 53 | expect("http://chrisdoesdev.com", equals(o.blogs[1].url)); 54 | 55 | //maps within lists within maps 56 | expect("Introduction to Dart", equals(o.books[0].chapters[0].chapter1)); 57 | expect("page1", equals(o.books[0].chapters[0].pages[0])); 58 | expect("page2", equals(o.books[0].chapters[0].pages[1])); 59 | 60 | //try an update 61 | o.handles.googlePlus.name="+ChrisB"; 62 | expect("+ChrisB",equals(o.handles.googlePlus.name)); 63 | 64 | for (JsonObject o in o.books) { 65 | _log(o.chapters); 66 | } 67 | 68 | 69 | } -------------------------------------------------------------------------------- /test/test_todo_vo.dart: -------------------------------------------------------------------------------- 1 | part of json_object_test; 2 | 3 | @proxy 4 | abstract class TodoVO { 5 | 6 | // Instance members 7 | String id; 8 | String title; 9 | bool completed; 10 | 11 | // Filter settings 12 | static const String FILTER_ALL = "filter/setting/all"; 13 | static const String FILTER_ACTIVE = "filter/setting/active"; 14 | static const String FILTER_COMPLETED = "filter/setting/completed"; 15 | 16 | // the from JsonString constructor 17 | factory TodoVO.fromString( String jsonString ) => new _TodoVOImpl.fromString(jsonString); 18 | 19 | // the default constructor 20 | factory TodoVO() => new _TodoVOImpl(); 21 | 22 | // Serialize to JSON 23 | String toJson(); 24 | } 25 | 26 | @proxy 27 | class _TodoVOImpl extends JsonObject implements TodoVO { 28 | // Instance members not required - they are implemented by noSuchMethod 29 | //String id = ''; 30 | //String title = ''; 31 | //bool completed = false; 32 | 33 | // need a default, private constructor 34 | _TodoVOImpl(); 35 | 36 | factory _TodoVOImpl.fromString( String jsonString ) { 37 | return new JsonObject.fromJsonString( jsonString, new _TodoVOImpl() ); 38 | } 39 | 40 | // Serialize this object to JSON 41 | String toJson() { 42 | 43 | StringBuffer buffer = new StringBuffer(); 44 | buffer.write('{'); 45 | buffer.write('"id":"'); // add the missing " 46 | buffer.write(id); 47 | buffer.write('", '); // add the missing " 48 | 49 | buffer.write('"title":"'); 50 | buffer.write( title ); 51 | buffer.write('", '); 52 | 53 | buffer.write('"completed":'); 54 | buffer.write(completed.toString()); 55 | buffer.write('}'); 56 | 57 | print(buffer.toString()); // add a print for debugging 58 | return buffer.toString(); 59 | } 60 | } 61 | 62 | testTodoVO() { 63 | 64 | // Create a Todo 65 | TodoVO todo = new TodoVO(); 66 | todo.id="99-plural-z-alpha"; 67 | todo.title="Make way for new hyperspace bypass"; 68 | todo.completed = false; 69 | 70 | // marshall and unmarshall 71 | String json = todo.toJson(); 72 | TodoVO reconstituted = new TodoVO.fromString( json ); 73 | 74 | // test 75 | expect( reconstituted.id, equals( todo.id) ); 76 | expect( reconstituted.title, equals( todo.title) ); 77 | expect( reconstituted.completed, equals( todo.completed) ); 78 | 79 | } -------------------------------------------------------------------------------- /test/test_dartlang_article.dart: -------------------------------------------------------------------------------- 1 | part of json_object_test; 2 | 3 | @proxy 4 | class LanguageWebsite extends JsonObject { 5 | LanguageWebsite(); // default constructor (empty) implementation 6 | 7 | factory LanguageWebsite.fromJsonString(String json) { // from JSON constructor implementation 8 | var languageWebsite = new LanguageWebsite(); // create an empty instance of this class 9 | var jsonObject = new JsonObject.fromJsonString(json, languageWebsite); // create an instance of JsonObject, 10 | // populated with the json string and 11 | // injecting the _LanguageWebsite instance. 12 | return jsonObject; // return the populated JsonObject instance 13 | } 14 | 15 | factory LanguageWebsite.fromJsonObject(JsonObject jsonObject) { 16 | return JsonObject.toTypedJsonObject(jsonObject, new LanguageWebsite()); 17 | } 18 | } 19 | 20 | @proxy 21 | class Language extends JsonObject { 22 | Language(); // empty, default constructor 23 | 24 | factory Language.fromJsonString(String json) { // from JSON constructor implementation 25 | return new JsonObject.fromJsonString(json, new Language()); // as _LangaugeWebsite, return an instance 26 | // of JsonObject, containing the json string and 27 | // injecting a _Language instance 28 | } 29 | } 30 | 31 | void testDartlangArticle_fromJson() { 32 | final responseText = """ 33 | { 34 | "language": "dart", 35 | "targets": ["dartium","javascript"], 36 | "website": { 37 | "homepage": "www.dartlang.org", 38 | "api": "api.dartlang.org" 39 | } 40 | }"""; 41 | 42 | 43 | Language data = new Language.fromJsonString(responseText); 44 | 45 | 46 | 47 | // tools can now validate the property access 48 | expect(data.language, equals("dart")); 49 | expect(data.targets[0], equals("dartium")); 50 | 51 | // nested types are also strongly typed 52 | LanguageWebsite website = new LanguageWebsite.fromJsonObject(data.website); // contains a JsonObject 53 | website.homepage = "http://www.dartlang.org"; 54 | expect(website.homepage,equals("http://www.dartlang.org")); 55 | 56 | 57 | } 58 | 59 | void testDartlangArticle_new() { 60 | Language data = new Language(); 61 | data.language = "Dart"; 62 | data.website = new LanguageWebsite(); 63 | data.website.homepage = "http://www.dartlang.org"; 64 | 65 | expect(data.website.homepage, equals("http://www.dartlang.org")); 66 | 67 | } 68 | 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JsonObject for DART (http://www.dartlang.org) 2 | 3 | Version 1.0.19 4 | 5 | 6 | [![](https://drone.io/chrisbu/json_object/status.png)](https://drone.io/chrisbu/json_object/latest) 7 | 8 | Usage: Add to pubspec.yaml: 9 | 10 | dependencies: 11 | json_object: any 12 | 13 | Now *M7* (Dart Beta) compatible. 14 | 15 | All tests passing with SDK **0.8.10.3_r29803** 16 | 17 | See Changelog. 18 | 19 | 20 | 21 | You can use JsonObject in two different ways. 22 | 23 | ## 1 Accessing JSON Maps in a class-based fashion 24 | 25 | Read the article about using this on the dartlang website: http://www.dartlang.org/articles/json-web-service/ 26 | 27 | JsonObject takes a json string representation, and uses the `dart:json` library to parse 28 | it back into a map. JsonObject then takes the parsed output, 29 | and converts any maps (recursively) into 30 | JsonObjects, which allow use of dot notation for property access 31 | (via `noSuchMethod`). 32 | 33 | Examples: 34 | 35 | // create from existing JSON 36 | var person = new JsonObject.fromJsonString('{"name":"Chris"}'); 37 | print(person.name); 38 | person.name = "Chris B"; 39 | person.namz = "Bob"; //throws an exception, as it wasn't in the original json 40 | //good for catching typos 41 | 42 | person.isExtendable = true; 43 | person.namz = "Bob" //this is allowed now 44 | String jsonString = JSON.encode(person); // convert back to JSON 45 | 46 | It implements Map, so you can convert it back to Json using JSON.encode(): 47 | 48 | // starting from an empty map 49 | var animal = new JsonObject(); 50 | animal.legs = 4; // equivalent to animal["legs"] = 4; 51 | animal.name = "Fido"; // equivalent to animal["name"] = "Fido"; 52 | String jsonString = JSON.encode(animal); // convert to JSON 53 | 54 | 55 | Take a look at the unit tests to get an idea of how you can use it. 56 | 57 | ---- 58 | 59 | ## 2. Experimental Using reflection to serialize from a real class instance to JSON 60 | 61 | (Requires use of a the experimental `mirrors` branch) 62 | 63 | 64 | Use `objectToJson(myObj)` to return a future containing the serialized string. 65 | 66 | Example: 67 | import 'package:json_object/json_object.dart'; 68 | 69 | class Other { 70 | String name = "My Name"; 71 | } 72 | 73 | class Basic { 74 | String myString = "foo"; 75 | int myInt = 42; 76 | Other name = new Other(); 77 | } 78 | 79 | main() { 80 | var basic = new Basic(); 81 | objectToJson(basic).then((jsonStr) => print(jsonStr)); 82 | } 83 | 84 | ---- 85 | 86 | 87 | 88 | TODO: 89 | * I still feel that there aren't enough tests - let me know if it works for you. 90 | 91 | Many of the unit tests are based around specific questions from users, 92 | either here or on stack overflow. 93 | -------------------------------------------------------------------------------- /lib/src/mirror_based_serializer.dart: -------------------------------------------------------------------------------- 1 | part of json_object; 2 | 3 | /// Uses mirror based reflection to convert the object passed in to a string 4 | /// of json. The object passed in may be any object, list or map. 5 | /// see test_mirrors.dart for examples. 6 | Future objectToJson(Object object) { 7 | var completer = new Completer(); 8 | 9 | var onSuccess = (value) { 10 | _log("About to stringify: $value"); 11 | var string = JSON.encode(value); 12 | completer.complete(string); 13 | }; 14 | 15 | var onError = (error) { 16 | _log("JsonObject Future Error: $object"); 17 | _log("Object: ${object.runtimeType}"); 18 | _log("Stringified: ${JSON.encode(object)}"); 19 | completer.completeError(error, error.stackTrace); 20 | }; 21 | 22 | objectToSerializable(object).then(onSuccess, onError:onError); 23 | 24 | return completer.future; 25 | 26 | } 27 | 28 | class _KeyValuePair { 29 | var key; 30 | var value; 31 | 32 | _KeyValuePair(this.key,this.value); 33 | } 34 | 35 | /** Optional [key] is required in case the object passed in came from a map 36 | * we need the original map's key to be able to re-constitute the map 37 | * later when the future completes. 38 | */ 39 | Future objectToSerializable(Object object, [key=null]) { 40 | var completer = new Completer(); 41 | 42 | if (isPrimative(object)) { 43 | _serializeNative(object, completer, key); 44 | } 45 | else if (object is Map) { 46 | _serializeMap(object, completer, key); 47 | } 48 | else if (object is List) { 49 | _serializeList(object, completer, key); 50 | } 51 | else { 52 | var instanceMirror = mirrors.reflect(object); 53 | _serializeObject(instanceMirror, completer, key); 54 | // all other processing of regular classes 55 | } 56 | 57 | return completer.future; 58 | } 59 | 60 | bool isPrimative(Object object){ 61 | if (object is num || 62 | object is bool || 63 | object is String || 64 | object == null) { 65 | return true; 66 | } else { 67 | return false; 68 | } 69 | } 70 | 71 | void _serializeNative(Object object, Completer completer, key) { 72 | _log("native: $object"); 73 | // "native" object types - just complete with that type 74 | _complete(completer,object,key); 75 | } 76 | 77 | void _serializeMap(Map object, Completer completer, key) { 78 | _log("map: $object"); 79 | 80 | // convert the map into a serialized map 81 | // each value in the map may itself be a complex object or a "native" type. 82 | // so we need to test for this 83 | Map mapItemsToComplete = new Map(); 84 | object.forEach((key,value) { 85 | mapItemsToComplete[key] = objectToSerializable(value,key); 86 | }); 87 | 88 | var onAllItemsComplete = (List keyValuePairs) { 89 | // at this point (via the Future.wait callback) 90 | // all items in the map are complete. 91 | // but how to match the items back to the keys? 92 | var mapResult = new Map(); 93 | keyValuePairs.forEach((kv) => mapResult[kv.key] = kv.value); 94 | _complete(completer,mapResult,key); 95 | }; 96 | 97 | var onItemsError = (error) => completer.completeError(error); 98 | 99 | Future.wait(mapItemsToComplete.values) 100 | .then(onAllItemsComplete, onError:onItemsError); 101 | } 102 | 103 | void _serializeList(List object, Completer completer, key) { 104 | _log("list: $object"); 105 | 106 | // each item in the list will be an object to serialize. 107 | List listItemsToComplete = new List(); 108 | object.forEach((item) { 109 | listItemsToComplete.add(objectToSerializable(item)); 110 | }); 111 | 112 | var onAllItemsComplete = (items) => _complete(completer,items,key); 113 | var onItemsError = (error) => completer.completeError(error); 114 | 115 | Future.wait(listItemsToComplete) 116 | .then(onAllItemsComplete, onError:onItemsError); 117 | } 118 | 119 | void _serializeObject(mirrors.InstanceMirror instanceMirror, Completer completer, key) { 120 | _log("object: $instanceMirror"); 121 | var classMirror = instanceMirror.type; 122 | 123 | var resultMap = new Map(); 124 | var futuresList = new List(); 125 | 126 | // for each getter: 127 | classMirror.getters.forEach((getterKey, getter) { 128 | if (!getter.isPrivate && !getter.isStatic) { 129 | _log("getter: ${getter.qualifiedName}"); 130 | var futureField = instanceMirror.getField(getterKey); 131 | _log("got future field: $futureField"); 132 | 133 | var onGetFutureFieldError = (error) { 134 | _log("Error: $error"); 135 | completer.completeError(error); 136 | }; 137 | 138 | var onGetFutureFieldSuccess = (mirrors.InstanceMirror instanceMirrorField) { 139 | Object reflectee = instanceMirrorField.reflectee; 140 | _log("Got reflectee for $getterKey: ${reflectee}"); 141 | if (isPrimative(reflectee)){ 142 | resultMap[getterKey] = reflectee; 143 | } else { 144 | Future recursed = objectToJson(reflectee).catchError(onGetFutureFieldError); 145 | recursed.then((json) => resultMap[getterKey] = json); 146 | futuresList.add(recursed); 147 | } 148 | }; 149 | 150 | futureField.then(onGetFutureFieldSuccess, onError:onGetFutureFieldError); 151 | futuresList.add(futureField); 152 | } 153 | }); 154 | 155 | // for each field 156 | classMirror.variables.forEach((varKey, variable) { 157 | if (!variable.isPrivate && !variable.isStatic) { 158 | var futureField = instanceMirror.getField(varKey); 159 | 160 | var onGetFutureFieldError = (error) => completer.completeError(error); 161 | 162 | var onGetFutureFieldSuccess = (mirrors.InstanceMirror instanceMirrorField) { 163 | Object reflectee = instanceMirrorField.reflectee; 164 | _log("Got reflectee for $varKey: ${reflectee}"); 165 | if (isPrimative(reflectee)){ 166 | resultMap[varKey] = reflectee; 167 | } else { 168 | Future recursed = objectToJson(reflectee).catchError(onGetFutureFieldError); 169 | recursed.then((json) => resultMap[varKey] = json); 170 | futuresList.add(recursed); 171 | } 172 | }; 173 | 174 | futureField.then(onGetFutureFieldSuccess, onError:onGetFutureFieldError); 175 | futuresList.add(futureField); 176 | } 177 | 178 | }); 179 | 180 | 181 | Future.wait(futuresList).then((vals) { 182 | _complete(completer,resultMap,key); 183 | }); 184 | 185 | 186 | } 187 | 188 | void _complete(Completer completer, object, key) { 189 | if (key != null) { 190 | completer.complete(new _KeyValuePair(key,object)); // complete, because we can't reflect any deeper 191 | } 192 | else { 193 | completer.complete(object); // complete, because we can't reflect any deeper 194 | } 195 | } 196 | 197 | ///// Converts an [object] to JSON. if the object is a List, then each item in the list 198 | ///// must be of the same type. The default implementation is to use reflection, and will 199 | ///// convert each getter / field into a Map, on which JSON.stringify() is called. 200 | ///// The [deserializer] argument takes a custom function that will be used to convert 201 | ///// the object to a string. The [deserializerList] is used to provide a function 202 | ///// for each class that might be encountered. (eg, Vehicle.Make could contain a 203 | ///// "Vehicle" deserializer function and a "Make" deserializer function). 204 | //dynamic objectToSerializable(Object object) { 205 | // var result; 206 | // 207 | // if (object is num || 208 | // object is bool || 209 | // object is String || 210 | // object == null) { 211 | // print("std"); 212 | // result = object; // is it just a standard serializable object? 213 | // } 214 | // else if (object is Map) { 215 | // print("map"); 216 | // // convert the map into a serialized map. 217 | // // each value in the map may be a complex object, so we need to test this. 218 | // result = _serializeEachValue(object); 219 | // } 220 | // else if (object is List) { 221 | // // is the object passed in a list? If so, we need to convert each 222 | // // item in the list to something that is serializable, and add it to the list 223 | // result = new List(); 224 | // object.forEach((value) => result.add(objectToSerializable(value))); 225 | // } 226 | // else { 227 | // print("else"); 228 | // // otherwise, it is one of our classes, in which case, we need to 229 | // // try and serialize it 230 | // var instanceMirror = mirrors.reflect(object); 231 | // result = _getFutureValues(instanceMirror); 232 | // } 233 | // 234 | // return result; 235 | // 236 | //} 237 | // 238 | // 239 | ///// When we get a reflected instance, the values come back as futures. This 240 | ///// method waits for all the future values to be returned, and then returns 241 | ///// an object map containing the serialized values. 242 | ///// For example: 243 | ///// class { 244 | ///// String aString = "foo"; 245 | ///// } 246 | ///// 247 | ///// becomes 248 | ///// map = {"aString":"foo"}; 249 | ///// 250 | //_getFutureValues(instanceMirror) { 251 | // var objectMap; // this will contain our objects k:v pairs. 252 | // 253 | // if (instanceMirror.reflectee is Map == false) { 254 | // objectMap = new Map(); 255 | // 256 | // var classMirror = instanceMirror.type; 257 | // 258 | // // for each field (that is not private or static 259 | // var futureValues = new List(); 260 | // 261 | // print("Getters: ${classMirror.getters}"); 262 | // print("Variables: ${classMirror.variables}"); 263 | // 264 | // // for each getter: 265 | // classMirror.getters.forEach((key, getter) { 266 | // if (!getter.isPrivate && !getter.isStatic) { 267 | // _getFutureValue(objectMap,key,instanceMirror,getter,futureValues); 268 | // } 269 | // }); 270 | // 271 | // // for each field 272 | // classMirror.variables.forEach((key, variable) { 273 | // print("Variable: $key:$variable"); 274 | // if (!variable.isPrivate && !variable.isStatic) { 275 | // print("recursing: $objectMap,$key,$instanceMirror,$variable,$futureValues"); 276 | // _getFutureValue(objectMap,key,instanceMirror,variable,futureValues); 277 | // } 278 | // }); 279 | // 280 | // // wait for all the future values to be retrieved 281 | // Future.wait(futureValues); // TODO: Convert this to be really async. 282 | // } 283 | // else { 284 | // // the input is a map, so each value needs to be serialized 285 | // objectMap = objectToSerializable(instanceMirror.reflectee); 286 | // } 287 | // 288 | // return objectMap; 289 | //} 290 | // 291 | ///// Adds the future value to the futureValues list. In the future's oncomplete 292 | ///// function, if the type is a simple type, it will populate the 293 | ///// objectMap's value for the specified key. If it is 294 | //_getFutureValue(objectMap, key, instanceMirror, value, futureValues) { 295 | // Future futureValue = instanceMirror.getField(key); 296 | // print("Getting future: $key"); 297 | // futureValue.asStream().listen((value) { 298 | // print("FutureValue has value"); 299 | // if (value.hasValue) { 300 | // var instanceMirror = value.value; 301 | // if (instanceMirror.hasReflectee) { 302 | // 303 | // // If not directly serializable, we need to recurse. 304 | // // Directly serializable are: [num], [String], [bool], [Null], [List] and [Map]. 305 | // // Lists are handled differently - ie, we assume that a list contains some 306 | // // JSON parsed data 307 | // if (instanceMirror.reflectee is num || 308 | // instanceMirror.reflectee is bool || 309 | // instanceMirror.reflectee is String || 310 | // instanceMirror.reflectee == null) { 311 | // // directly serializable 312 | // objectMap[key] = instanceMirror.reflectee; 313 | // } 314 | // else if (instanceMirror.reflectee is Map) { 315 | // // if any of the map's values are not one of the serializable types 316 | // // then we need to recurse 317 | // objectMap[key] = _serializeEachValue(instanceMirror.reflectee); 318 | // } 319 | // else { 320 | // // it's some non directly serializable object or a list 321 | // // recurse 322 | // // print("RECURSE: some other object $key"); 323 | // objectMap[key] = objectToSerializable(instanceMirror.reflectee); 324 | // } 325 | // } 326 | // } 327 | // }); 328 | // 329 | // futureValues.add(futureValue); 330 | //} 331 | // 332 | //// each item in the map may be a simple item, or 333 | //// a complex type. Serialize it properly 334 | //_serializeEachValue(Map inputMap) { 335 | // var map = new Map(); 336 | // 337 | // inputMap.forEach((key,value) { 338 | // map[key] = objectToSerializable(value); 339 | // }); 340 | // 341 | // return map; 342 | //} 343 | -------------------------------------------------------------------------------- /lib/json_object.dart: -------------------------------------------------------------------------------- 1 | /* (C) 2013 Chris Buckett (chrisbuckett@gmail.com) 2 | * Released under the MIT licence 3 | * See LICENCE file 4 | * http://github.com/chrisbu/dartwatch-JsonObject 5 | */ 6 | 7 | 8 | library json_object; 9 | 10 | import "dart:convert"; 11 | import "dart:async"; 12 | import 'dart:mirrors' as mirrors; 13 | // import 'package:meta/meta.dart'; 14 | 15 | part "src/mirror_based_serializer.dart"; 16 | 17 | // Set to true to as required 18 | var enableJsonObjectDebugMessages = false; 19 | void _log(obj) { 20 | if (enableJsonObjectDebugMessages) print(obj); 21 | } 22 | 23 | /** JsonObject allows .property name access to JSON by using 24 | * noSuchMethod. 25 | * 26 | * When used with the generic type annotation, 27 | * it uses Dart's mirror system to return a real instance of 28 | * the specified type. 29 | */ 30 | @proxy 31 | class JsonObject extends Object implements Map, Iterable { 32 | /// Contains either a [List] or [Map] 33 | var _objectData; 34 | 35 | static JsonEncoder encoder = new JsonEncoder(); 36 | static JsonDecoder decoder = new JsonDecoder(null); 37 | 38 | /** 39 | * Returns a [JSON.decode] representation of the underlying object data 40 | */ 41 | toString() { 42 | return encoder.convert(_objectData); 43 | } 44 | 45 | /** 46 | * Returns either the underlying parsed data as an iterable list (if the 47 | * underlying data contains a list), or returns the map.values (if the 48 | * underlying data contains a map). 49 | * 50 | * Returns an empty list if neither of the above is true. 51 | */ 52 | Iterable toIterable() { 53 | if (_objectData is Iterable) { 54 | return _objectData; 55 | } 56 | else if (_objectData is Map) { 57 | return _objectData.values; 58 | } 59 | else { 60 | return new List(); // return an empty list, rather than return null 61 | 62 | } 63 | } 64 | 65 | /** [isExtendable] decides if a new item can be added to the internal 66 | * map via the noSuchMethod property, or the functions inherited from the 67 | * map interface. 68 | * 69 | * If set to false, then only the properties that were 70 | * in the original map or json string passed in can be used. 71 | * 72 | * If set to true, then calling o.blah="123" will create a new blah property 73 | * if it didn't already exist. 74 | * 75 | * Setting this to false can help with checking for typos, and is false by 76 | * default when a JsonObject is created with [JsonObject.fromJsonString()] 77 | * or [JsonObject.fromMap()]. 78 | * The default constructor [JsonObject()], however will set this value to 79 | * true, otherwise you can't actually add any new properties. 80 | */ 81 | bool isExtendable; 82 | 83 | /** default constructor. 84 | * creates a new empty map. 85 | */ 86 | JsonObject() { 87 | _objectData = new Map(); 88 | isExtendable = true; 89 | } 90 | 91 | /** 92 | * Eager constructor parses [jsonString] using [JsonDecoder]. 93 | * 94 | * If [t] is given, will replace [t]'s contents from the string and return [t]. 95 | * 96 | * If [recursive] is true, replaces all maps recursively with JsonObjects. 97 | * The default value is [true]. 98 | */ 99 | factory JsonObject.fromJsonString(String jsonString, [JsonObject t, bool recursive = true]) { 100 | if (t == null) { 101 | t = new JsonObject(); 102 | } 103 | t._objectData = decoder.convert(jsonString); 104 | if(recursive) { 105 | t._extractElements(t._objectData); 106 | } 107 | t.isExtendable = false; 108 | return t; 109 | } 110 | 111 | /** An alternate constructor, allows creating directly from a map 112 | * rather than a json string. 113 | * 114 | * If [recursive] is true, all values of the map will be converted 115 | * to [JsonObject]s as well. The default value is [true]. 116 | */ 117 | JsonObject.fromMap(Map map, [bool recursive = true]) { 118 | _objectData = map; 119 | if(recursive) { 120 | _extractElements(_objectData); 121 | } 122 | isExtendable = false; 123 | } 124 | 125 | static JsonObject toTypedJsonObject(JsonObject src, JsonObject dest) { 126 | dest._objectData = src._objectData; 127 | dest.isExtendable = false; 128 | return dest; 129 | } 130 | 131 | /** noSuchMethod() is where the magic happens. 132 | * If we try to access a property using dot notation (eg: o.wibble ), then 133 | * noSuchMethod will be invoked, and identify the getter or setter name. 134 | * It then looks up in the map contained in _objectData (represented using 135 | * this (as this class implements [Map], and forwards it's calls to that 136 | * class. 137 | * If it finds the getter or setter then it either updates the value, or 138 | * replaces the value. 139 | * 140 | * If isExtendable = true, then it will allow the property access 141 | * even if the property doesn't yet exist. 142 | */ 143 | noSuchMethod(Invocation mirror) { 144 | int positionalArgs = 0; 145 | if (mirror.positionalArguments != null) positionalArgs = mirror.positionalArguments.length; 146 | 147 | var property = _symbolToString(mirror.memberName); 148 | 149 | if (mirror.isGetter && (positionalArgs == 0)) { 150 | //synthetic getter 151 | if (this.containsKey(property)) { 152 | return this[property]; 153 | } 154 | } 155 | else if (mirror.isSetter && positionalArgs == 1) { 156 | //synthetic setter 157 | //if the property doesn't exist, it will only be added 158 | //if isExtendable = true 159 | property = property.replaceAll("=", ""); 160 | this[property] = mirror.positionalArguments[0]; // args[0]; 161 | return this[property]; 162 | } 163 | 164 | //if we get here, then we've not found it - throw. 165 | _log("Not found: ${property}"); 166 | _log("IsGetter: ${mirror.isGetter}"); 167 | _log("IsSetter: ${mirror.isGetter}"); 168 | _log("isAccessor: ${mirror.isAccessor}"); 169 | super.noSuchMethod(mirror); 170 | } 171 | 172 | /** 173 | * If the [data] object passed in is a MAP, then we iterate through each of 174 | * the values of the map, and if any value is a map, then we create a new 175 | * [JsonObject] replacing that map in the original data with that [JsonObject] 176 | * to a new [JsonObject]. If the value is a Collection, then we call this 177 | * function recursively. 178 | * 179 | * If the [data] object passed in is a Collection, then we iterate through 180 | * each item. If that item is a map, then we replace the item with a 181 | * [JsonObject] created from the map. If the item is a Collection, then we 182 | * call this function recursively. 183 | */ 184 | _extractElements(data) { 185 | if (data is Map) { 186 | //iterate through each of the k,v pairs, replacing maps with jsonObjects 187 | data.forEach((key,value) { 188 | 189 | if (value is Map) { 190 | //replace the existing Map with a JsonObject 191 | data[key] = new JsonObject.fromMap(value); 192 | } 193 | else if (value is List) { 194 | //recurse 195 | _extractElements(value); 196 | } 197 | 198 | }); 199 | } 200 | else if (data is List) { 201 | //iterate through each of the items 202 | //if any of them is a list, check to see if it contains a map 203 | 204 | for (int i = 0; i < data.length; i++) { 205 | //use the for loop so that we can index the item to replace it if req'd 206 | var listItem = data[i]; 207 | if (listItem is List) { 208 | //recurse 209 | _extractElements(listItem); 210 | } 211 | else if (listItem is Map) { 212 | //replace the existing Map with a JsonObject 213 | data[i] = new JsonObject.fromMap(listItem); 214 | } 215 | } 216 | } 217 | 218 | } 219 | 220 | String _symbolToString(value) { 221 | if (value is Symbol) { 222 | return mirrors.MirrorSystem.getName(value); 223 | } 224 | else { 225 | return value.toString(); 226 | } 227 | } 228 | 229 | /*************************************************************************** 230 | * Iterable implementation methods and properties * 231 | */ 232 | 233 | // pass through to the underlying iterator 234 | Iterator get iterator => this.toIterable().iterator; 235 | 236 | Iterable map(f(E element)) => this.toIterable().map(f); 237 | 238 | Iterable where(bool f(E element)) => this.toIterable().where(f); 239 | 240 | Iterable expand(Iterable f(E element)) => this.toIterable().expand(f); 241 | 242 | bool contains(E element) => this.toIterable().contains(element); 243 | 244 | dynamic reduce(E combine(E value, E element)) => this.toIterable().reduce(combine); 245 | 246 | bool every(bool f(E element)) => this.toIterable().every(f); 247 | 248 | String join([String separator = ""]) => this.toIterable().join(separator); 249 | 250 | bool any(bool f(E element)) => this.toIterable().any(f); 251 | 252 | Iterable take(int n) => this.toIterable().take(n); 253 | 254 | Iterable takeWhile(bool test(E value)) => this.toIterable().takeWhile(test); 255 | 256 | Iterable skip(int n) => this.toIterable().skip(n); 257 | 258 | Iterable skipWhile(bool test(E value)) => this.toIterable().skipWhile(test); 259 | 260 | E get first => this.toIterable().first; 261 | 262 | E get last => this.toIterable().last; 263 | 264 | E get single => this.toIterable().single; 265 | 266 | E fold(initialValue, dynamic combine(a,b)) => this.toIterable().fold(initialValue, combine); 267 | 268 | @deprecated 269 | E firstMatching(bool test(E value), { E orElse() : null }) { 270 | if (orElse != null) return this.toIterable().firstWhere(test, orElse: orElse); 271 | else return this.toIterable().firstWhere(test); 272 | } 273 | 274 | @deprecated 275 | E lastMatching(bool test(E value), {E orElse() : null}) { 276 | if (orElse != null) return this.toIterable().lastWhere(test, orElse: orElse); 277 | else return this.toIterable().lastWhere(test); 278 | } 279 | 280 | @deprecated 281 | E singleMatching(bool test(E value)) => this.toIterable().singleWhere(test); 282 | 283 | E elementAt(int index) => this.toIterable().elementAt(index); 284 | 285 | List toList({ bool growable: true }) => this.toIterable().toList(growable:growable); 286 | 287 | Set toSet() => this.toIterable().toSet(); 288 | 289 | @deprecated 290 | E min([int compare(E a, E b)]) { throw "Deprecated in iterable interface"; } 291 | 292 | @deprecated 293 | E max([int compare(E a, E b)]) { throw "Deprecated in iterable interface"; } 294 | 295 | dynamic firstWhere(test, {orElse}) => this.toIterable().firstWhere(test, orElse:orElse); 296 | dynamic lastWhere(test, {orElse}) => this.toIterable().firstWhere(test, orElse:orElse); 297 | dynamic singleWhere(test, {orElse}) => this.toIterable().firstWhere(test, orElse:orElse); 298 | // 299 | /*************************************************************************** 300 | * Map implementation methods and properties * 301 | * 302 | */ 303 | 304 | // Pass through to the inner _objectData map. 305 | bool containsValue(value) => _objectData.containsValue(value); 306 | 307 | // Pass through to the inner _objectData map. 308 | bool containsKey(value) { 309 | return _objectData.containsKey(_symbolToString(value)); 310 | } 311 | 312 | // Pass through to the innter _objectData map. 313 | bool get isNotEmpty => _objectData.isNotEmpty; 314 | 315 | // Pass through to the inner _objectData map. 316 | operator [](key) => _objectData[key]; 317 | 318 | // Pass through to the inner _objectData map. 319 | forEach(func) => _objectData.forEach(func); 320 | 321 | // Pass through to the inner _objectData map. 322 | Iterable get keys => _objectData.keys; 323 | 324 | // Pass through to the inner _objectData map. 325 | Iterable get values => _objectData.values; 326 | 327 | // Pass through to the inner _objectData map. 328 | int get length => _objectData.length; 329 | 330 | // Pass through to the inner _objectData map. 331 | bool get isEmpty => _objectData.isEmpty; 332 | 333 | // Pass through to the inner _objectData map. 334 | addAll(items) => _objectData.addAll(items); 335 | 336 | /** 337 | * Specific implementations which check isExtendable to determine if an 338 | * 339 | * unknown key should be allowed 340 | * 341 | * If [isExtendable] is true, or the key already exists, 342 | * then allow the edit. 343 | * Throw [JsonObjectException] if we're not allowed to add a new 344 | * key 345 | */ 346 | operator []=(key,value) { 347 | //if the map isExtendable, or it already contains the key, then 348 | if (this.isExtendable == true || this.containsKey(key)) { 349 | //allow the edit, as we don't care if it's a new key or not 350 | return _objectData[key] = value; 351 | } 352 | else { 353 | throw new JsonObjectException("JsonObject is not extendable"); 354 | } 355 | } 356 | 357 | /** If [isExtendable] is true, or the key already exists, 358 | * then allow the edit. 359 | * Throw [JsonObjectException] if we're not allowed to add a new 360 | * key 361 | */ 362 | putIfAbsent(key,ifAbsent()) { 363 | if (this.isExtendable == true || this.containsKey(key)) { 364 | return _objectData.putIfAbsent(key, ifAbsent); 365 | } 366 | else { 367 | throw new JsonObjectException("JsonObject is not extendable"); 368 | } 369 | } 370 | 371 | /** If [isExtendable] is true, or the key already exists, 372 | * then allow the removal. 373 | * Throw [JsonObjectException] if we're not allowed to remove a 374 | * key 375 | */ 376 | remove(key) { 377 | if (this.isExtendable == true || this.containsKey(key)) { 378 | return _objectData.remove(key); 379 | } 380 | else { 381 | throw new JsonObjectException("JsonObject is not extendable"); 382 | } 383 | } 384 | 385 | /** If [isExtendable] is true, then allow the map to be cleared 386 | * Throw [JsonObjectException] if we're not allowed to clear. 387 | */ 388 | clear() { 389 | if (this.isExtendable == true) { 390 | _objectData.clear(); 391 | } 392 | else { 393 | throw new JsonObjectException("JsonObject is not extendable"); 394 | } 395 | } 396 | } 397 | 398 | /** 399 | * Exception class thrown by JSON Object 400 | */ 401 | class JsonObjectException implements Exception { 402 | const JsonObjectException([String message]) : this._message = message; 403 | String toString() => (this._message != null 404 | ? "JsonObjectException: $_message" 405 | : "JsonObjectException"); 406 | final String _message; 407 | } 408 | 409 | 410 | 411 | 412 | -------------------------------------------------------------------------------- /test/test_mirrors_serialize.dart: -------------------------------------------------------------------------------- 1 | part of json_object_test; 2 | 3 | // classes that we will try and serialize 4 | class Basic { 5 | String aString = "This is a string"; 6 | bool aBool = true; 7 | num aNum = 123; 8 | double aDouble = 234.56; 9 | int anInt = 234; 10 | var aNull = null; 11 | final aFinal = "final"; 12 | } 13 | 14 | class ContainsBasicList { 15 | List strings = ["aaa","bbb","ccc"]; 16 | List ints = [1,2,3,4]; 17 | List doubles = [1.1,2.2,3.3]; 18 | List nulls = [null, null]; 19 | List bools = [true,false,true]; 20 | List mixed = ["String",123,true,null]; 21 | } 22 | 23 | class ContainsBasicMap { 24 | Map strings = {"Foo":"Foo1","Bar":"Bar1"}; 25 | Map stringInts = {"Foo":1,"Bar":2}; 26 | Map stringBools = {"Foo":true,"Bar":false}; 27 | Map mixed = {"nullVal":null,"string":"aString","aNum":123}; 28 | 29 | } 30 | 31 | class ContainsGetters { 32 | String _aString = "This is a string"; 33 | String get aString => _aString; 34 | 35 | bool _aBool = true; 36 | bool get aBool => _aBool; 37 | set aBool(value) => _aBool = value; 38 | 39 | num _aNum = 123; 40 | num get aNum => _aNum; 41 | set aNum(value) => _aNum = value; 42 | 43 | double _aDouble = 234.56; 44 | double get aDouble => _aDouble; 45 | set aDouble(value) => _aDouble = value; 46 | 47 | int _anInt = 234; 48 | int get anInt => _anInt; 49 | set anInt(value) => _anInt = value; 50 | 51 | var _aNull = null; 52 | get aNull => _aNull; 53 | set aNull(value) => _aNull = value; 54 | 55 | final _aFinal = "final"; 56 | get aFinal => _aFinal; 57 | } 58 | 59 | class ContainsGettersAndObject { 60 | String _aString = "This is a string"; 61 | String get aString => _aString; 62 | 63 | bool _aBool = true; 64 | bool get aBool => _aBool; 65 | set aBool(value) => _aBool = value; 66 | 67 | num _aNum = 123; 68 | num get aNum => _aNum; 69 | set aNum(value) => _aNum = value; 70 | 71 | double _aDouble = 234.56; 72 | double get aDouble => _aDouble; 73 | set aDouble(value) => _aDouble = value; 74 | 75 | int _anInt = 234; 76 | int get anInt => _anInt; 77 | set anInt(value) => _anInt = value; 78 | 79 | var _aNull = null; 80 | get aNull => _aNull; 81 | set aNull(value) => _aNull = value; 82 | 83 | final _aFinal = "final"; 84 | get aFinal => _aFinal; 85 | 86 | Basic _basic = new Basic(); 87 | Basic get basic => _basic; 88 | set basic(value) => _basic = value; 89 | } 90 | 91 | class ContainsStatic { 92 | static String iAmStatic = "static"; 93 | 94 | var iAmInstance = "instance"; 95 | } 96 | 97 | class ContainsPrivate { 98 | var _iAmPrivate = "private"; 99 | 100 | var iAmPublic = "public"; 101 | } 102 | 103 | class ContainsMethods { 104 | _privateMethod() => "private method"; 105 | 106 | publicMethod() => "public method"; 107 | 108 | var field = "serialize me"; 109 | } 110 | 111 | class AnObject { 112 | Basic basic = new Basic(); 113 | } 114 | 115 | class ContainsSimpleObject { 116 | AnObject anObject = new AnObject(); 117 | AnObject anObject2 = new AnObject(); 118 | AnObject anObject3 = new AnObject(); 119 | List objects = new List(); 120 | Map objMap = new Map(); 121 | 122 | ContainsSimpleObject() { 123 | objects.add(new AnObject()); 124 | objects.add(new AnObject()); 125 | objMap["aaa"] = new AnObject(); 126 | objMap["bbb"] = new AnObject(); 127 | } 128 | } 129 | 130 | class ContainsObject { 131 | Basic basic = new Basic(); 132 | String aString = "aString"; 133 | } 134 | 135 | class ContainsObjectList { 136 | List basicList = new List(); 137 | String aString = "aString"; 138 | 139 | ContainsObjectList() { 140 | basicList.add(new Basic()); 141 | basicList.add(new Basic()); 142 | } 143 | } 144 | 145 | class ContainsObjectMap { 146 | Map basicMap = new Map(); 147 | Map objectMap = new Map(); 148 | Map objectListMap = new Map(); 149 | Map> listObjectMap = new Map>(); 150 | 151 | ContainsObjectMap() { 152 | basicMap["basic1"] = new Basic(); 153 | basicMap["basic2"] = new Basic(); 154 | 155 | objectMap["object1"] = new ContainsObject(); 156 | objectMap["object2"] = new ContainsObject(); 157 | 158 | objectListMap["objectList1"] = new ContainsObjectList(); 159 | objectListMap["objectList2"] = new ContainsObjectList(); 160 | 161 | listObjectMap["list1"] = new List(); 162 | listObjectMap["list1"].add(new Basic()); 163 | listObjectMap["list1"].add(new Basic()); 164 | listObjectMap["list2"] = new List(); 165 | listObjectMap["list2"].add(new Basic()); 166 | } 167 | } 168 | 169 | 170 | 171 | // Main test method 172 | 173 | testMirrorsSerialize() { 174 | 175 | group('mirrors:', () { 176 | var obj = new ContainsSimpleObject(); 177 | test('ContainsSimpleObject', () => 178 | objectToJson(obj).then((string) => print("--> $string")) 179 | ); 180 | 181 | /* There are two types of objects: 182 | 1. Those that contain ONLY 183 | basic seralizable types num, String, bool, Null, or Maps and lists 184 | that only contain the serializable types. 185 | 2. Those that contain child objects, or maps and lists of child objects 186 | (possibly along with the serializable types). 187 | */ 188 | 189 | group('native', () { 190 | // "native" types should be parsed in exactly the same way as JSON.encode/ 191 | test('string', () { 192 | var val = "String"; 193 | var future = objectToJson(val); 194 | future.catchError((error) => registerException(error)); 195 | expect(future, completion(JSON.encode(val))); 196 | }); 197 | 198 | test('bool', () { 199 | var val = true; 200 | var future = objectToJson(val); 201 | future.catchError((error) => registerException(error)); 202 | expect(future, completion(JSON.encode(val))); 203 | }); 204 | 205 | test('num', () { 206 | var val = 123; 207 | var future = objectToJson(val); 208 | future.catchError((error) => registerException(error)); 209 | expect(future, completion(JSON.encode(val))); 210 | }); 211 | 212 | test('double', () { 213 | var val = 123.45; 214 | var future = objectToJson(val); 215 | future.catchError((error) => registerException(error)); 216 | expect(future, completion(JSON.encode(val))); 217 | }); 218 | 219 | test('null', () { 220 | var val = null; 221 | var future = objectToJson(val); 222 | future.catchError((error) => registerException(error)); 223 | expect(future, completion(JSON.encode(val))); 224 | }); 225 | 226 | }); 227 | 228 | group('listOfNative', () { 229 | // "native types should be parsed in exactly the same way as JSON.encode/ 230 | test('string', () { 231 | var val = ["String","String2"]; 232 | var future = objectToJson(val); 233 | future.catchError((error) => registerException(error)); 234 | expect(future, completion(JSON.stringify(val))); 235 | }); 236 | 237 | test('bool', () { 238 | var val = [true,false]; 239 | var future = objectToJson(val); 240 | future.catchError((error) => registerException(error)); 241 | expect(future, completion(JSON.stringify(val))); 242 | }); 243 | 244 | test('num', () { 245 | var val = [123,456]; 246 | var future = objectToJson(val); 247 | future.catchError((error) => registerException(error)); 248 | expect(future, completion(JSON.stringify(val))); 249 | }); 250 | 251 | test('double', () { 252 | var val = [123.45,6.789]; 253 | var future = objectToJson(val); 254 | future.catchError((error) => registerException(error)); 255 | expect(future, completion(JSON.stringify(val))); 256 | }); 257 | 258 | test('null', () { 259 | var val = [null,null]; 260 | var future = objectToJson(val); 261 | future.catchError((error) => registerException(error)); 262 | expect(future, completion(JSON.stringify(val))); 263 | }); 264 | 265 | test('mixed', () { 266 | var val = ["String",true,123,35.6,null]; 267 | var future = objectToJson(val); 268 | future.catchError((error) => registerException(error)); 269 | expect(future, completion(JSON.stringify(val))); 270 | }); 271 | 272 | test('list', () { 273 | var val = [[1,2],["a","b"]]; 274 | var future = objectToJson(val); 275 | future.catchError((error) => registerException(error)); 276 | expect(future, completion(JSON.stringify(val))); 277 | }); 278 | 279 | }); 280 | 281 | group('mapOfNative', () { 282 | // "native types should be parsed in exactly the same way as JSON.encode/ 283 | test('string', () { 284 | var val = {"key1":"string1","key2":"string2"}; 285 | var future = objectToJson(val); 286 | future.catchError((error) => registerException(error)); 287 | expect(future, completion(new JsonMapMatcher(val))); 288 | }); 289 | 290 | test('bool', () { 291 | var val = {"key1":true,"key2":false}; 292 | var future = objectToJson(val); 293 | future.catchError((error) => registerException(error)); 294 | expect(future, completion(new JsonMapMatcher(val))); 295 | }); 296 | 297 | test('num', () { 298 | var val = {"key1":123,"key2":456}; 299 | var future = objectToJson(val); 300 | future.catchError((error) => registerException(error)); 301 | expect(future, completion(new JsonMapMatcher(val))); 302 | }); 303 | 304 | test('double', () { 305 | var val = {"key1":123.45,"key2":456.78}; 306 | var future = objectToJson(val); 307 | future.catchError((error) => registerException(error)); 308 | expect(future, completion(new JsonMapMatcher(val))); 309 | }); 310 | 311 | test('null', () { 312 | var val = {"key1":null,"key2":null}; 313 | var future = objectToJson(val); 314 | future.catchError((error) => registerException(error)); 315 | expect(future, completion(new JsonMapMatcher(val))); 316 | }); 317 | 318 | test('mixed', () { 319 | var val = {"key1":"string","key2":true,"key3":123,"key4":123.45,"key5":null}; 320 | var future = objectToJson(val); 321 | future.catchError((error) => registerException(error)); 322 | expect(future, completion(new JsonMapMatcher(val))); 323 | }); 324 | 325 | test('list', () { 326 | var val = {"list1":[1,2]}; 327 | var future = objectToJson(val); 328 | future.catchError((error) => registerException(error)); 329 | expect(future, completion(new JsonMapMatcher(val))); 330 | }); 331 | 332 | }); 333 | 334 | group('basic:', () { 335 | // Check we can serialize basic types: 336 | // [num], [String], [bool], [Null], 337 | test('Basic', () { 338 | // Test a class that contains basic type fields 339 | var object = new Basic(); 340 | var future = objectToJson(object); 341 | future.catchError((error) => registerException(error)); 342 | var expectation = new Map(); 343 | expectation["aString"] = object.aString; 344 | expectation["aNum"] = object.aNum; 345 | expectation["aDouble"] = object.aDouble; 346 | expectation["aBool"] = object.aBool; 347 | expectation["anInt"] = object.anInt; 348 | expectation["aNull"] = object.aNull; 349 | expectation["aFinal"] = object.aFinal; 350 | 351 | expect(future, completion(new JsonMapMatcher(expectation))); 352 | }); 353 | 354 | test('ContainsBasicList', () { 355 | // Test a class that contains lists 356 | var object = new ContainsBasicList(); 357 | var future = objectToJson(object); 358 | future.catchError((error) => registerException(error)); 359 | var expectation = new Map(); 360 | 361 | expectation["strings"] = object.strings; 362 | expectation["ints"] = object.ints; 363 | expectation["doubles"] = object.doubles; 364 | expectation["bools"] = object.bools; 365 | expectation["mixed"] = object.mixed; 366 | expectation["nulls"] = object.nulls; 367 | 368 | expect(future, completion(new JsonMapMatcher(expectation))); 369 | }); 370 | 371 | test('ContainsBasicMap', () { 372 | // Test a class that contains maps 373 | var object = new ContainsBasicMap(); 374 | var future = objectToJson(object); 375 | future.catchError((error) => registerException(error)); 376 | var expectation = new Map(); 377 | 378 | expectation["strings"] = object.strings; 379 | expectation["stringInts"] = object.stringInts; 380 | expectation["stringBools"] = object.stringBools; 381 | expectation["mixed"] = object.mixed; 382 | 383 | expect(future, completion(new JsonMapMatcher(expectation))); 384 | }); 385 | 386 | }); 387 | 388 | group('complex', () { 389 | test('ContainsSimpleObject', () { 390 | // Test a class that contains a child object 391 | var object = new ContainsSimpleObject(); 392 | var future = objectToJson(object); 393 | future.catchError((error) => registerException(error)); 394 | var expectation = new Map(); 395 | expectation["anObject"] = new Map(); 396 | expectation["anObject2"] = new Map(); 397 | expectation["anObject3"] = new Map(); 398 | 399 | expect(future, completion(new JsonMapMatcher(expectation))); 400 | }); 401 | 402 | test('ContainsObject', () { 403 | // Test a class that contains a child object 404 | var object = new ContainsObject(); 405 | var future = objectToJson(object); 406 | future.catchError((error) => registerException(error)); 407 | var expectation = new Map(); 408 | expectation["aString"] = object.aString; 409 | expectation["basic"] = new Map(); 410 | expectation["basic"]["aString"] = object.basic.aString; 411 | expectation["basic"]["aBool"] = object.basic.aBool; 412 | expectation["basic"]["aNum"] = object.basic.aNum; 413 | expectation["basic"]["aDouble"] = object.basic.aDouble; 414 | expectation["basic"]["anInt"] = object.basic.anInt; 415 | expectation["basic"]["aNull"] = object.basic.aNull; 416 | expectation["basic"]["aFinal"] = object.basic.aFinal; 417 | 418 | expect(future, completion(new JsonMapMatcher(expectation))); 419 | }); 420 | 421 | test('ContainsObjectList', () { 422 | // Test a class that has a list of child objects 423 | var object = new ContainsObjectList(); 424 | var future = objectToJson(object); 425 | future.catchError((error) => registerException(error)); 426 | 427 | var expectation = new Map(); 428 | expectation["aString"] = object.aString; 429 | expectation["basicList"] = new List(); 430 | expectation["basicList"].add(new Map()); 431 | expectation["basicList"][0]["aString"] = object.basicList[0].aString; 432 | expectation["basicList"][0]["aBool"] = object.basicList[0].aBool; 433 | expectation["basicList"][0]["aNum"] = object.basicList[0].aNum; 434 | expectation["basicList"][0]["aDouble"] = object.basicList[0].aDouble; 435 | expectation["basicList"][0]["anInt"] = object.basicList[0].anInt; 436 | expectation["basicList"][0]["aNull"] = object.basicList[0].aNull; 437 | expectation["basicList"][0]["aFinal"] = object.basicList[0].aFinal; 438 | 439 | expectation["basicList"].add(new Map()); 440 | expectation["basicList"][1]["aString"] = object.basicList[1].aString; 441 | expectation["basicList"][1]["aBool"] = object.basicList[1].aBool; 442 | expectation["basicList"][1]["aNum"] = object.basicList[1].aNum; 443 | expectation["basicList"][1]["aDouble"] = object.basicList[1].aDouble; 444 | expectation["basicList"][1]["anInt"] = object.basicList[1].anInt; 445 | expectation["basicList"][1]["aNull"] = object.basicList[1].aNull; 446 | expectation["basicList"][1]["aFinal"] = object.basicList[1].aFinal; 447 | 448 | expect(future, completion(new JsonMapMatcher(expectation))); 449 | }); 450 | 451 | 452 | test('ContainsObjectMap', () { 453 | // Test a class that contains maps of real objects 454 | var object = new ContainsObjectMap(); 455 | 456 | // The call under test 457 | var future = objectToJson(object); 458 | future.catchError((error) => registerException(error)); 459 | 460 | var expectation = new Map(); 461 | 462 | // Parse and test the output json 463 | expectation["basicMap"] = new Map(); 464 | expectation["basicMap"]["basic1"] = new Map(); 465 | expectation["basicMap"]["basic1"]["aString"] = object.basicMap["basic1"].aString; 466 | expectation["basicMap"]["basic1"]["aBool"] = object.basicMap["basic1"].aBool; 467 | expectation["basicMap"]["basic1"]["aNum"] = object.basicMap["basic1"].aNum; 468 | expectation["basicMap"]["basic1"]["aDouble"] = object.basicMap["basic1"].aDouble; 469 | expectation["basicMap"]["basic1"]["anInt"] = object.basicMap["basic1"].anInt; 470 | expectation["basicMap"]["basic1"]["aNull"] = object.basicMap["basic1"].aNull; 471 | expectation["basicMap"]["basic1"]["aFinal"] = object.basicMap["basic1"].aFinal; 472 | expectation["basicMap"]["basic2"] = new Map(); 473 | expectation["basicMap"]["basic2"]["aString"] = object.basicMap["basic2"].aString; 474 | 475 | expectation["objectMap"] = new Map(); 476 | expectation["objectMap"]["object1"] = new Map(); 477 | expectation["objectMap"]["object1"]["basic"] = new Map(); 478 | expectation["objectMap"]["object1"]["basic"]["aString"] = object.objectMap["object1"].basic.aString; 479 | expectation["objectMap"]["object2"] = new Map(); 480 | expectation["objectMap"]["object2"]["basic"] = new Map(); 481 | expectation["objectMap"]["object2"]["basic"]["aString"] = object.objectMap["object2"].basic.aString; 482 | 483 | expectation["objectListMap"] = new Map(); 484 | expectation["objectListMap"]["objectList1"] = new Map(); 485 | expectation["objectListMap"]["objectList1"]["basicList"] = new List(); 486 | expectation["objectListMap"]["objectList1"]["basicList"].add(new Map()); 487 | expectation["objectListMap"]["objectList1"]["basicList"][0]["aString"] = object.objectListMap["objectList1"].basicList[0].aString; 488 | 489 | expectation["listObjectMap"] = new Map(); 490 | expectation["listObjectMap"]["list1"] = new List(); 491 | expectation["listObjectMap"]["list1"].add(new Map()); 492 | expectation["listObjectMap"]["list1"][0]["aString"] = object.listObjectMap["list1"][0].aString; 493 | expectation["listObjectMap"]["list1"].add(new Map()); 494 | expectation["listObjectMap"]["list1"][1]["aString"] = object.listObjectMap["list1"][1].aString; 495 | expectation["listObjectMap"]["list2"] = new List(); 496 | expectation["listObjectMap"]["list2"].add(new Map()); 497 | expectation["listObjectMap"]["list2"][0]["aString"] = object.listObjectMap["list2"][0].aString; 498 | 499 | expect(future, completion(new JsonMapMatcher(expectation))); 500 | }); 501 | }); 502 | 503 | group('lists and maps', () { 504 | test('List', () { 505 | var list = new List(); 506 | list.add(new Basic()); 507 | list.add(new Basic()); 508 | 509 | // The call under test 510 | var future = objectToJson(list); 511 | future.catchError((error) => registerException(error)); 512 | 513 | var expectation = new List(); 514 | expectation.add(new Map()); 515 | expectation[0]["aString"] = list[0].aString; 516 | expectation[0]["aBool"] = list[0].aBool; 517 | expectation[0]["aNum"] = list[0].aNum; 518 | expectation[0]["aDouble"] = list[0].aDouble; 519 | expectation[0]["anInt"] = list[0].anInt; 520 | expectation[0]["aNull"] = list[0].aNull; 521 | expectation[0]["aFinal"] = list[0].aFinal; 522 | expectation.add(new Map()); 523 | expectation[1]["aString"] = list[1].aString; 524 | expectation[1]["aString"] = list[1].aString; 525 | expectation[1]["aBool"] = list[1].aBool; 526 | expectation[1]["aNum"] = list[1].aNum; 527 | expectation[1]["aDouble"] = list[1].aDouble; 528 | expectation[1]["anInt"] = list[1].anInt; 529 | expectation[1]["aNull"] = list[1].aNull; 530 | expectation[1]["aFinal"] = list[1].aFinal; 531 | 532 | 533 | expect(future, completion(new JsonMapMatcher(expectation))); 534 | }); 535 | 536 | test('Map', () { 537 | var map = new Map(); 538 | map["item1"] = new Basic(); 539 | map["item2"] = new Basic(); 540 | 541 | // The call under test 542 | var future = objectToJson(map); 543 | future.catchError((error) => registerException(error)); 544 | 545 | var expectation = new Map(); 546 | 547 | expectation["item1"] = new Map(); 548 | expectation["item1"]["aString"] = map["item1"].aString; 549 | expectation["item1"]["aBool"] = map["item1"].aBool; 550 | expectation["item1"]["aNum"] = map["item1"].aNum; 551 | expectation["item1"]["aDouble"] = map["item1"].aDouble; 552 | expectation["item1"]["anInt"] = map["item1"].anInt; 553 | expectation["item1"]["aNull"] = map["item1"].aNull; 554 | expectation["item1"]["aFinal"] = map["item1"].aFinal; 555 | expectation["item2"] = new Map(); 556 | expectation["item2"]["aString"] = map["item2"].aString; 557 | expectation["item2"]["aBool"] = map["item2"].aBool; 558 | expectation["item2"]["aNum"] = map["item2"].aNum; 559 | expectation["item2"]["aDouble"] = map["item2"].aDouble; 560 | expectation["item2"]["anInt"] = map["item2"].anInt; 561 | expectation["item2"]["aNull"] = map["item2"].aNull; 562 | expectation["item2"]["aFinal"] = map["item2"].aFinal; 563 | 564 | expect(future, completion(new JsonMapMatcher(expectation))); 565 | }); 566 | }); 567 | 568 | group('getters setters private static', () { 569 | test('ContainsGetters', () { 570 | var object = new ContainsGetters(); 571 | 572 | // The call under test 573 | var future = objectToJson(object); 574 | future.catchError((error) => registerException(error)); 575 | 576 | var expectation = new Map(); 577 | 578 | expectation["aString"] = object.aString; 579 | expectation["aNum"] = object.aNum; 580 | expectation["aDouble"] = object.aDouble; 581 | expectation["aBool"] = object.aBool; 582 | expectation["anInt"] = object.anInt; 583 | expectation["aNull"] = object.aNull; 584 | expectation["aFinal"] = object.aFinal; 585 | 586 | expect(future, completion(new JsonMapMatcher(expectation))); 587 | }); 588 | 589 | test('ContainsGettersAndObject', () { 590 | var object = new ContainsGettersAndObject(); 591 | 592 | // The call under test 593 | var future = objectToJson(object); 594 | future.catchError((error) => registerException(error)); 595 | 596 | var expectation = new Map(); 597 | 598 | expectation["aString"] = object.aString; 599 | expectation["aNum"] = object.aNum; 600 | expectation["aDouble"] = object.aDouble; 601 | expectation["aBool"] = object.aBool; 602 | expectation["anInt"] = object.anInt; 603 | expectation["aNull"] = object.aNull; 604 | expectation["aFinal"] = object.aFinal; 605 | expectation["basic"] = new Map(); 606 | expectation["basic"]["aString"] = object.aString; 607 | 608 | 609 | 610 | expect(future, completion(new JsonMapMatcher(expectation))); 611 | }); 612 | 613 | test('ContainsPrivate', () { 614 | var object = new ContainsPrivate(); 615 | 616 | // The call under test 617 | var future = objectToJson(object); 618 | future.catchError((error) => registerException(error)); 619 | 620 | var expectation = new Map(); 621 | 622 | expectation["_iAmPrivate"] = null; 623 | expectation["iAmPublic"] = object.iAmPublic; 624 | 625 | expect(future, completion(new JsonMapMatcher(expectation))); 626 | }); 627 | 628 | test('ContainsStatic', () { 629 | var object = new ContainsStatic(); 630 | 631 | // The call under test 632 | var future = objectToJson(object); 633 | future.catchError((error) => registerException(error)); 634 | 635 | var expectation = new Map(); 636 | 637 | expectation["iAmStatic"] = null; 638 | expectation["iAmInstance"] = object.iAmInstance; 639 | 640 | expect(future, completion(new JsonMapMatcher(expectation))); 641 | }); 642 | 643 | test('ContainsMethods', () { 644 | var object = new ContainsMethods(); 645 | 646 | // The call under test 647 | var future = objectToJson(object); 648 | future.catchError((error) => registerException(error)); 649 | 650 | var expectation = new Map(); 651 | 652 | expectation["field"] = object.field; 653 | 654 | expect(future, completion(new JsonMapMatcher(expectation))); 655 | }); 656 | }); 657 | 658 | }); 659 | 660 | } 661 | 662 | 663 | 664 | 665 | 666 | /* 667 | * A map serialized to JSON may contain the same elements but in a different 668 | * order. 669 | */ 670 | class JsonMapMatcher implements Matcher { 671 | var _map; 672 | 673 | 674 | JsonMapMatcher(this._map); 675 | 676 | // JSON parse the item back into a map, and compare the two maps 677 | // (brute force, innefficient) 678 | bool matches(String item, matchState) { 679 | var result = true; 680 | _log("matcher before JSON: $item"); 681 | _log("matcher after JSON: $item"); 682 | _log("matcher map: ${JSON.stringify(_map)}"); 683 | 684 | if (JSON.stringify(_map) == item) { 685 | // if the map and item are equal, then pass 686 | return true; 687 | } 688 | else { 689 | var map = JSON.parse(item); 690 | // try and compare the item and the map 691 | return _mapsAreEqual(map, _map); 692 | } 693 | } 694 | 695 | Description describe(Description description) { 696 | description.add("_map: ${_map.toString()}"); 697 | return description; 698 | } 699 | 700 | Description describeMismatch(item, Description mismatchDescription, 701 | matchState, bool verbose) { 702 | mismatchDescription.add("item: ${item.toString()}"); 703 | return mismatchDescription; 704 | 705 | } 706 | 707 | bool _listsAreEqual(List one, List two) { 708 | var i = -1; 709 | return one.every((element) { 710 | i++; 711 | 712 | return two[i] == element; 713 | }); 714 | } 715 | 716 | bool _mapsAreEqual(Map one, Map two) { 717 | var result = true; 718 | 719 | one.forEach((k,v) { 720 | if (two[k] != v) { 721 | 722 | if (v is List) { 723 | if (!_listsAreEqual(one[k], v)) { 724 | result = false; 725 | } 726 | } 727 | else if (v is Map) { 728 | if (!_mapsAreEqual(one[k], v)) { 729 | result = false; 730 | } 731 | } 732 | else { 733 | result = false; 734 | } 735 | 736 | } 737 | }); 738 | 739 | two.forEach((k,v) { 740 | if (one[k] != v) { 741 | 742 | if (v is List) { 743 | if (!_listsAreEqual(two[k], v)) { 744 | result = false; 745 | } 746 | } 747 | else if (v is Map) { 748 | if (!_mapsAreEqual(two[k], v)) { 749 | result = false; 750 | } 751 | } 752 | else { 753 | result = false; 754 | } 755 | } 756 | }); 757 | 758 | return result; 759 | } 760 | } --------------------------------------------------------------------------------