diff --git a/README.md b/README.md index 74d2764..e1597d6 100644 --- a/README.md +++ b/README.md @@ -55,20 +55,47 @@ trace(j.keys['key'].items[2].keys['r'].items[1]); ### JsonPrinter -This library includes a configurable JSON pretty-printer, with three pre-defined configurations for convenience: +This library includes a configurable JSON pretty-printer, with three pre-defined configurations for convenience. + +Other custom configurations are possible by adjusting the values of `JsonPrinterOptions`. #### Standard -As you would find from jsonlint: +Similar to [jsonlint][jsonlint]: ```json { "array": [ 1, - 23 + [ + 23, + 45 + ], + [ + 67, + [ + 666 + ], + [ + 777 + ], + 89 + ] ], "bool": true, - "string": "one two three" + "dictionary": { + "a": [ + 65, + 97 + ], + "z": { + "A": 65, + "a": 97 + } + }, + "nulls": "loom dictionaries delete null values", + "number": 987.6543, + "string": "aA bB cC" } ``` @@ -78,9 +105,19 @@ A tighter formatting that retains readability: ```json { - "array": [ 1, 23 ], + "array": [ + 1, + [ 23, 45 ], + [ 67, [ 666 ], [ 777 ], 89 ] + ], "bool": true, - "string": "one two three" + "dictionary": { + "a": [ 65, 97 ], + "z": { "A": 65, "a": 97 } + }, + "nulls": "loom dictionaries delete null values", + "number": 987.6543, + "string": "aA bB cC" } ``` @@ -89,7 +126,92 @@ A tighter formatting that retains readability: No extra whitespace: ```json -{"array":[1,23],"bool":true,"string":"one two three"} +{"array":[1,[23,45],[67,[666],[777],89]],"bool":true,"dictionary":{"a":[65,97],"z":{"A":65,"a":97}},"nulls":"loom dictionaries delete null values","number":987.6543,"string":"aA bB cC"} +``` + + +### YamlPrinter + +This library includes a configurable YAML pretty-printer, with two pre-defined configurations for convenience: + +Other custom configurations are possible by adjusting the values of `YamlPrinterOptions`. + +#### Standard + +As you would find from [yamllint][yamllint]: + +```yaml +--- +array: + - 1 + - + - 23 + - 45 + - + - 67 + - + - 666 + - + - 777 + - 89 +bool: true +dictionary: + a: + - 65 + - 97 + z: + A: 65 + a: 97 +nulls: "loom dictionaries delete null values" +number: 987.6543 +string: "aA bB cC" +``` + +#### Compact + +A tighter formatting similar to [yaml.org][yaml.org]: + +```yaml +--- +array: + - 1 + - [ 23, 45 ] + - [ 67, [ 666 ], [ 777 ], 89 ] +bool: true +dictionary: + a: [ 65, 97 ] + z: { A: 65, a: 97 } +nulls: "loom dictionaries delete null values" +number: 987.6543 +string: "aA bB cC" +``` + +#### Custom + +Custom formatting can be achieved by configuring the `YamlPrinterOptions` parameter: + +```yaml +--- +array: + - 1 + - - 23 + - 45 + - - 67 + - - 666 + - - 777 + - 89 +bool: true +dictionary: + a: + - 65 + - 97 + z: + A: 65 + a: 97 +nulls: "loom dictionaries delete null values" +number: 987.6543 +string: "aA bB cC" +... ``` ### JsonDemo @@ -109,7 +231,7 @@ you can compile and run the demo from the command line: Download the library into its matching sdk folder: $ curl -L -o ~/.loom/sdks/sprint34/libs/Json.loomlib \ - https://github.com/pixeldroid/json-ls/releases/download/v0.0.3/Json-sprint34.loomlib + https://github.com/pixeldroid/json-ls/releases/download/v1.0.0/Json-sprint34.loomlib To uninstall, simply delete the file: @@ -166,7 +288,10 @@ this will build the Json library, install it in the currently configured sdk, bu Pull requests are welcome! -[loomtasks]: https://github.com/pixeldroid/loomtasks "loomtasks" -[loom-json]: http://docs.theengine.co/loom/1.1.4813/api/system/JSON.html "Loom JSON class" [JsonDemoCLI.build]: ./cli/src/JsonDemoCLI.build "build file for the CLI demo" [JsonDemoCLI.ls]: ./cli/src/JsonDemoCLI.ls "source file for the CLI demo" +[jsonlint]: https://jsonlint.com/ "jsonlint" +[loom-json]: http://docs.theengine.co/loom/1.1.4813/api/system/JSON.html "Loom JSON class" +[loomtasks]: https://github.com/pixeldroid/loomtasks "loomtasks" +[yaml.org]: http://www.yaml.org/start.html "yaml.org" +[yamllint]: http://www.yamllint.com/ "yamllint" diff --git a/cli/src/JsonDemoCLI.ls b/cli/src/JsonDemoCLI.ls index 14e455c..602247b 100644 --- a/cli/src/JsonDemoCLI.ls +++ b/cli/src/JsonDemoCLI.ls @@ -5,31 +5,89 @@ package import pixeldroid.json.Json; import pixeldroid.json.JsonPrinter; import pixeldroid.json.JsonPrinterOptions; + import pixeldroid.json.YamlPrinter; + import pixeldroid.json.YamlPrinterOptions; public class JsonDemoCLI extends ConsoleApplication { override public function run():void { - var jsonObject:Dictionary. = { "bool": true, "array": [1,23], "string": "aA bB cC" }; - trace('source:\n' +dictionaryToString(jsonObject)); + var jsonObject:Dictionary. = { + 'nulls': "loom dictionaries delete null values", + 'bool': true, + 'number': 987.6543, + 'string-simple': "aA bB cC", + 'string-escapes': "\'single quote,\'\n\"double quote\non two lines\"", + 'array': [1, [23, 45], [67, [666], [777], 89]], + 'dictionary': { 'a': [65, 97], 'z': { 'A': 65, 'a': 97 } } + }; + trace('\nsource:\n' +objectToString(jsonObject)); var j:Json = Json.fromObject(jsonObject); - trace('standard:\n' +JsonPrinter.print(j, JsonPrinterOptions.standard)); - trace('compact:\n' +JsonPrinter.print(j, JsonPrinterOptions.compact)); - trace('minified:\n' +JsonPrinter.print(j, JsonPrinterOptions.minified)); + printJson(j); + printYaml(j); } - private function dictionaryToString(d:Dictionary.):String + + private function printJson(j:Json):void { - var s:String = ''; + trace('\njson'); + trace('\nstandard:\n' +JsonPrinter.print(j, JsonPrinterOptions.standard)); + trace('\ncompact:\n' +JsonPrinter.print(j, JsonPrinterOptions.compact)); + trace('\nminified:\n' +JsonPrinter.print(j, JsonPrinterOptions.minified)); + } - for (var k:String in d) + private function printYaml(j:Json):void + { + trace('\nyaml'); + trace('\nstandard:\n' +YamlPrinter.print(j, YamlPrinterOptions.standard)); + trace('\ncompact:\n' +YamlPrinter.print(j, YamlPrinterOptions.compact)); + var opts:YamlPrinterOptions = new YamlPrinterOptions(); + opts.printDocumentEnd = true; + opts.tabSize = 4; + opts.tightLists = true; + trace('\ncustom:\n' +YamlPrinter.print(j, opts)); + } + + + private function objectToString(o:Object):String + { + // this simplified implementation assumes dictionaries are tuples + var s:String; + + switch (o.getFullTypeName()) { - s += '"' +k +'": ' +d[k].toString() +'\n'; + case 'system.Null' : s = 'null'; break; + case 'system.Boolean' : s = o.toString(); break; + case 'system.Number' : s = o.toString(); break; + case 'system.String' : s = '"' + o.toString() + '"'; break; + case 'system.Vector' : s = vectorToString(o as Vector.); break; + case 'system.Dictionary' : s = dictionaryToString(o as Dictionary.); break; + default: s = o.toString(); } return s; } + + private function dictionaryToString(d:Dictionary.):String + { + var l:Vector. = []; + + for (var k:String in d) + l.push('"' +k +'": ' +objectToString(d[k])); + + return '{ ' +l.join(', ') +' }'; + } + + private function vectorToString(v:Vector.):String + { + var l:Vector. = []; + + for each(var o:Object in v) + l.push(objectToString(o)); + + return '[ ' +l.join(', ') +' ]'; + } } } diff --git a/lib/src/Json.build b/lib/src/Json.build index 02bd698..c341abf 100644 --- a/lib/src/Json.build +++ b/lib/src/Json.build @@ -1,6 +1,6 @@ { "name": "Json", - "version": "0.0.3", + "version": "1.0.0", "outputDir": "./build", "references": [ "System" @@ -8,7 +8,7 @@ "modules": [ { "name": "Json", - "version": "0.0.3", + "version": "1.0.0", "sourcePath": [ "." ] diff --git a/lib/src/pixeldroid/json/Json.ls b/lib/src/pixeldroid/json/Json.ls index b6384cc..a098feb 100644 --- a/lib/src/pixeldroid/json/Json.ls +++ b/lib/src/pixeldroid/json/Json.ls @@ -6,7 +6,26 @@ package pixeldroid.json public class Json { - public static const version:String = '0.0.3'; + public static const version:String = '1.0.0'; + + /** + Merges key:value pairs of second parameter into first, overriding any duplicates. + + *Note* this method does not clone non-primitive values. + During the merge, new keys in the first Json object will pointed to values in the second. + After the merge, modifying values in the second Json object would be the same as modifying them in the first. + + @param j1 Target Json object; will have j2 merged into it + @param j2 Json object to merge into j1 + */ + static public function merge(j1:Json, j2:Json):void + { + Debug.assert(j1.type == Dictionary.getType(), "merge operand root data type must be Dictionary"); + Debug.assert(j2.type == Dictionary.getType(), "merge operand root data type must be Dictionary"); + + for (var k:String in j2.keys) + j1.keys[k] = j2.keys[k]; + } static public function fromString(value:String):Json { @@ -22,6 +41,13 @@ package pixeldroid.json return itemToJson(value); } + static public function fromJSON(value:JSON):Json + { + Debug.assert(value.getJSONType() == JSONType.JSON_OBJECT, "JSON root data type must be Object"); + + return JSONObjectToJson(value); + } + public var keys:Dictionary. = {}; public var items:Vector. = []; public var value:Object = null; diff --git a/lib/src/pixeldroid/json/JsonPrinter.ls b/lib/src/pixeldroid/json/JsonPrinter.ls index f624b96..d070138 100644 --- a/lib/src/pixeldroid/json/JsonPrinter.ls +++ b/lib/src/pixeldroid/json/JsonPrinter.ls @@ -14,10 +14,10 @@ package pixeldroid.json switch (json.type.getFullName()) { - case 'system.Null' : s = 'null'; break; + // 'system.Null' : loom dictionaries delete any keys with values set to null case 'system.Boolean' : s = json.value.toString(); break; case 'system.Number' : s = json.value.toString(); break; - case 'system.String' : s = '"' + json.value + '"'; break; + case 'system.String' : s = stringToJsonString(json.value.toString()); break; case 'system.Vector' : s = vectorToJsonString(options, json.items, indentLevel); break; case 'system.Dictionary' : s = dictionaryToJsonString(options, json.keys, indentLevel); break; } @@ -39,6 +39,23 @@ package pixeldroid.json return s; } + static private function stringToJsonString(s:String):String + { + var result:String = s; + + result = result.split('\\').join('\\\\'); // expand backslash before others + + result = result.split('"').join('\\"'); + result = result.split('\b').join('\\b'); + result = result.split('\f').join('\\f'); + result = result.split('\n').join('\\n'); + result = result.split('\r').join('\\r'); + result = result.split('\t').join('\\t'); + // result = result.split('\u').join('\\u'); // FIXME: need to match \uXXXX (u+4) + + return '"' +result +'"'; + } + static private function containerToJsonString(options:JsonPrinterOptions, items:Vector., indentLevel:Number, openBrace:String, closeBrace:String):String { var s:String; diff --git a/lib/src/pixeldroid/json/YamlPrinter.ls b/lib/src/pixeldroid/json/YamlPrinter.ls new file mode 100644 index 0000000..1531703 --- /dev/null +++ b/lib/src/pixeldroid/json/YamlPrinter.ls @@ -0,0 +1,152 @@ +package pixeldroid.json +{ + + import pixeldroid.json.Json; + import pixeldroid.json.YamlPrinterOptions; + + public class YamlPrinter + { + private static const documentStart:String = '---'; + private static const documentEnd:String = '...'; + + static public function print(json:Json, options:YamlPrinterOptions = null, indentLevel:Number = 0):String + { + if (options == null) options = YamlPrinterOptions.standard; + + var s:String = documentStart + printItem(json, options, indentLevel); + if (options.printDocumentEnd) s += '\n' + documentEnd; + + return s; + } + + static public function printItem(json:Json, options:YamlPrinterOptions = null, indentLevel:Number = 0, nestingLevel:Number = 0, inList:Boolean = false):String + { + var s:String; + + switch (json.type.getFullName()) + { + // 'system.Null' : loom dictionaries delete any keys with values set to null + case 'system.Boolean' : s = json.value.toString(); break; + case 'system.Number' : s = json.value.toString(); break; + case 'system.String' : s = stringToYamlString(json.value.toString()); break; + case 'system.Vector' : s = vectorToYamlString(options, json.items, indentLevel, nestingLevel, inList); break; + case 'system.Dictionary' : s = dictionaryToYamlString(options, json.keys, indentLevel, nestingLevel, inList); break; + } + + return s; + } + + + static private function indent(level:Number, tabSize:Number, char:String = ' '):String + { + var s:String = ''; + var n:Number = level * tabSize; + + while (n > 0) { + s += char; + n--; + } + + return s; + } + + static private function stringToYamlString(s:String):String + { + var result:String = s; + + result = result.split('\\').join('\\\\'); // expand backslash before others + + result = result.split('"').join('\\"'); + result = result.split('\b').join('\\b'); + result = result.split('\f').join('\\f'); + result = result.split('\n').join('\\n'); + result = result.split('\r').join('\\r'); + result = result.split('\t').join('\\t'); + // result = result.split('\u').join('\\u'); // FIXME: need to match \uXXXX (u+4) + + return '"' +result +'"'; + } + + static private function dictionaryToYamlString(options:YamlPrinterOptions, d:Dictionary., indentLevel:Number, nestingLevel:Number = 0, inList:Boolean = false):String + { + if (d.length == 0) + return '{}'; + + var key:String; + + var k:Vector. = []; + for (key in d) k.push(key); + k.sort(); + + var compactForm:Boolean = (options.compactNestingLevel > 0) && (nestingLevel >= options.compactNestingLevel); + var tightList:Boolean = ((inList || compactForm) && options.tightLists); + var ident:String = indent(indentLevel, options.tabSize); + var lines:Vector. = []; + var n:Number = k.length; + var i:Number; + var val:Json; + + if (compactForm) + { + var line:Vector. = []; + for (i = 0; i < n; i++) + { + key = k[i]; + val = d[key]; + line.push(key + ':' + options.fieldSeparator + printItem(val, options, indentLevel + 1, nestingLevel + 1, true)); + } + lines.push((tightList ? '' : ident) + '{ ' + line.join(',' + options.fieldSeparator) + ' }'); + } + else + { + var ind:String; + for (i = 0; i < n; i++) + { + key = k[i]; + val = d[key]; + ind = ((tightList && (i == 0)) ? '' : ident); + lines.push(ind + key + ':' + options.fieldSeparator + printItem(val, options, indentLevel + 1, nestingLevel + 1)); + } + } + + return (tightList ? '' : '\n') + lines.join('\n'); + } + + static private function vectorToYamlString(options:YamlPrinterOptions, v:Vector., indentLevel:Number, nestingLevel:Number = 0, inList:Boolean = false):String + { + if (v.length == 0) + return '[]'; + + var compactForm:Boolean = (options.compactNestingLevel > 0) && (nestingLevel >= options.compactNestingLevel); + var tightList:Boolean = ((inList || compactForm) && options.tightLists); + var ident:String = indent(indentLevel, options.tabSize); + var lines:Vector. = []; + var n:Number = v.length; + var i:Number; + var val:Json; + + if (compactForm) + { + var line:Vector. = []; + for (i = 0; i < n; i++) + { + val = v[i]; + line.push(printItem(val, options, indentLevel + 1, nestingLevel + 1, true)); + } + lines.push((tightList ? '' : ident) + '[ ' + line.join(',' + options.fieldSeparator) + ' ]'); + } + else + { + var ind:String; + for (i = 0; i < n; i++) + { + val = v[i]; + ind = tightList ? (i == 0 ? '' : indent(indentLevel - 1, options.tabSize) + indent(nestingLevel, 1)) : ident; + lines.push(ind + '-' + options.fieldSeparator + printItem(val, options, indentLevel + 1, nestingLevel + 1, true)); + } + } + + return (tightList ? '' : '\n') + lines.join('\n'); + } + } +} diff --git a/lib/src/pixeldroid/json/YamlPrinterOptions.ls b/lib/src/pixeldroid/json/YamlPrinterOptions.ls new file mode 100644 index 0000000..3900dea --- /dev/null +++ b/lib/src/pixeldroid/json/YamlPrinterOptions.ls @@ -0,0 +1,30 @@ +package pixeldroid.json +{ + + public class YamlPrinterOptions + { + + static public function get compact():YamlPrinterOptions + { + var opts:YamlPrinterOptions = new YamlPrinterOptions(); + + opts.compactNestingLevel = 2; + opts.itemSeparator = ' '; + opts.tightLists = true; + + return opts; + } + + static public function get standard():YamlPrinterOptions + { + return new YamlPrinterOptions(); + } + + public var compactNestingLevel:Number = 0; + public var fieldSeparator:String = ' '; + public var itemSeparator:String = '\n'; + public var printDocumentEnd:Boolean = false; + public var tabSize:Number = 2; + public var tightLists:Boolean = false; + } +} diff --git a/test/fixtures/json.json b/test/fixtures/json.json index d5bdf4a..9061994 100644 --- a/test/fixtures/json.json +++ b/test/fixtures/json.json @@ -3,33 +3,47 @@ "key_bool_true": true, "key_bool_false": false, "key_string": "abcdefg ABCDEFG 0123_4567", + "key_string_escapes": { + "quotation_mark": "\"", + "reverse_solidus": "\\", + "solidus": "\/", + "backspace": "\b", + "formfeed": "\f", + "newline": "\n", + "carriage_return": "\r", + "horizontal_tab": "\t", + "unicode_smily": "\u263A", + "two_backslashes": "\\\\", + "quoted_backslash": "\"\\\"" + }, "key_number_integer": 1234, "key_number_real": 0.9876, + "key_empty_array": [], "key_array": [ "array_1", null, "array_3" ], + "key_nested_monotype_array": [ + [1, 2, 3], + [[4], [5], [6], null] + ], + "key_nested_multitype_array": [ + [1,2.5,3], + {"a":1, "b":2, "c":{"z":"Z"}}, + null, + true + ], + "key_empty_object": {}, "key_object": { "object_1": 1, "object_2": 2, "object_3": 3 }, - "key_nested_in_object": { + "key_nested_multitype_object": { "nested_1": [1,2.5,3], "nested_2": {"a":1, "b":2, "c":{"z":"Z"}}, "nested_3": null, "nested_4": true - }, - "key_nested_in_array": [ - {"a":1.23, "b":45.67}, - {"x":8, "y":9}, - {"q":[1,2], "r":[3,4], "n":null} - ], - "key_nested_arrays": [ - [1, 2, 3], - [[4], [5], [6], null] - ], - "key_empty_array": [], - "key_empty_object": {} + } } diff --git a/test/src/app/JsonTest.ls b/test/src/app/JsonTest.ls index 5488183..fb6eee6 100644 --- a/test/src/app/JsonTest.ls +++ b/test/src/app/JsonTest.ls @@ -7,7 +7,7 @@ package import JsonSpec; import JsonPrinterSpec; - + import YamlPrinterSpec; public class JsonTest extends ConsoleApplication { @@ -17,7 +17,8 @@ package var returnCode:Number = SpecExecutor.exec([ JsonSpec, - JsonPrinterSpec + JsonPrinterSpec, + YamlPrinterSpec ]); Process.exit(returnCode); diff --git a/test/src/spec/JsonPrinterSpec.ls b/test/src/spec/JsonPrinterSpec.ls index e3d8465..825f20d 100644 --- a/test/src/spec/JsonPrinterSpec.ls +++ b/test/src/spec/JsonPrinterSpec.ls @@ -10,14 +10,12 @@ package public static class JsonPrinterSpec { - private static const it:Thing = Spec.describe('JsonPrinter'); + private static var it:Thing; + private static var _jsonFixture:Json; - public static function describe():void + public static function specify(specifier:Spec):void { - var jsonFile:String = 'fixtures/json.json'; - var jsonString:String = File.loadTextFile(jsonFile); - - json = Json.fromString(jsonString); + it = specifier.describe('JsonPrinter'); it.should('generate a valid JSON string', be_valid_json); it.should('generate a valid JSON string with compact formatting', be_valid_when_compact); @@ -26,10 +24,21 @@ package } - private static var json:Json; + private static function get jsonFixture():Json + { + if (!_jsonFixture) + { + var jsonFile:String = 'fixtures/json.json'; + var jsonString:String = File.loadTextFile(jsonFile); + _jsonFixture = Json.fromString(jsonString); + } + + return _jsonFixture; + } private static function be_valid_json():void { + var json:Json = jsonFixture; var prettyString:String = JsonPrinter.print(json); it.expects(prettyString).not.toBeNull(); @@ -38,6 +47,7 @@ package private static function be_valid_when_compact():void { + var json:Json = jsonFixture; var compact:String = JsonPrinter.print(json, JsonPrinterOptions.compact); it.expects(Json.fromString(compact)).not.toBeNull(); @@ -45,6 +55,7 @@ package private static function be_valid_when_minified():void { + var json:Json = jsonFixture; var minified:String = JsonPrinter.print(json, JsonPrinterOptions.minified); it.expects(Json.fromString(minified)).not.toBeNull(); @@ -52,6 +63,7 @@ package private static function default_to_standard_formatting():void { + var json:Json = jsonFixture; var prettyString:String = JsonPrinter.print(json); var standard:String = JsonPrinter.print(json, JsonPrinterOptions.standard); diff --git a/test/src/spec/JsonSpec.ls b/test/src/spec/JsonSpec.ls index 642e418..b757c83 100644 --- a/test/src/spec/JsonSpec.ls +++ b/test/src/spec/JsonSpec.ls @@ -8,24 +8,35 @@ package public static class JsonSpec { - private static const it:Thing = Spec.describe('Json'); + private static var it:Thing; + private static var _jsonFixture:Json; - public static function describe():void + public static function specify(specifier:Spec):void { + it = specifier.describe('Json'); + it.should('be versioned', be_versioned); it.should('initialize from a valid JSON string', init_from_string); it.should('initialize from a native Loom object', init_from_object); - } - - - private static var json:Json; - - private static function describeValidInstance():void - { + it.should('initialize from a Loom JSON object', init_from_loom_json); it.should('report the native Loom type each value can be extracted as', report_native_types); it.should('retrieve native Loom types for the non-collection JSON types', get_native_types); it.should('retrieve array elements via items[]', get_array_elements_via_items); it.should('retrieve object properties via keys[]', get_object_props_via_keys); + it.should('provide an object merge utility', merge_objects); + } + + + private static function get jsonFixture():Json + { + if (!_jsonFixture) + { + var jsonFile:String = 'fixtures/json.json'; + var jsonString:String = File.loadTextFile(jsonFile); + _jsonFixture = Json.fromString(jsonString); + } + + return _jsonFixture; } @@ -38,18 +49,17 @@ package { var jsonFile:String = 'fixtures/json.json'; var jsonString:String = File.loadTextFile(jsonFile); + var result:Json = Json.fromString(jsonString); - json = null; - json = Json.fromString(jsonString); - - it.expects(json).not.toBeNull(); - describeValidInstance(); + it.asserts(result).isNotNull().or('init from string failed'); + it.expects(result.keys.length).toEqual(14); } private static function init_from_object():void { var jsonObject:Dictionary. = {}; + jsonObject['key_null'] = null; // will not create an entry in the jsonObject dictionary jsonObject['key_bool_true'] = true; jsonObject['key_bool_false'] = false; jsonObject['key_string'] = 'abcdefg ABCDEFG 0123_4567'; @@ -58,30 +68,51 @@ package jsonObject['key_empty_array'] = []; jsonObject['key_array'] = ["array_1", null, "array_3"]; - jsonObject['key_nested_arrays'] = [ + jsonObject['key_nested_monotype_array'] = [ [1, 2, 3], [[4], [5], [6], null] ]; + jsonObject['key_nested_multitype_array'] = [ + [1,2.5,3], + {"a":1, "b":2, "c":{"z":"Z"}}, + null, + true + ]; jsonObject['key_empty_object'] = {}; jsonObject['key_object'] = {"object_1": 1, "object_2": 2, "object_3": 3}; - jsonObject['key_nested_in_object'] = { + jsonObject['key_nested_multitype_object'] = { "nested_1": [1,2.5,3], "nested_2": {"a":1, "b":2, "c":{"z":"Z"}}, "nested_3": null, "nested_4": true }; - json = null; - json = Json.fromObject(jsonObject); + var result:Json = Json.fromObject(jsonObject); - it.expects(json).not.toBeNull(); - describeValidInstance(); + it.asserts(result).isNotNull().or('init from object failed'); + it.expects(result.keys.length).toEqual(12); + it.expects(result.keys.fetch('key_null', 'not found')).toEqual('not found'); // wasn't in in object, so couldn't be put into json } + private static function init_from_loom_json():void + { + var jsonFile:String = 'fixtures/json.json'; + var jsonString:String = File.loadTextFile(jsonFile); + + var j:JSON = new JSON(); + it.asserts(j.loadString(jsonString)).isEqualTo(true).or('json fixture failed to parse'); + + var result:Json = Json.fromJSON(j); + + it.asserts(result).isNotNull().or('init from Loom JSON failed'); + it.expects(result.keys.length).toEqual(14); + } private static function report_native_types():void { + var json:Json = jsonFixture; + if (json.keys['key_null']) it.expects(json.keys['key_null'].type).toEqual(Null.getType()); it.expects(json.keys['key_bool_true'].type).toEqual(Boolean.getType()); it.expects(json.keys['key_string'].type).toEqual(String.getType()); @@ -93,6 +124,8 @@ package private static function get_native_types():void { + var json:Json = jsonFixture; + if (json.keys['key_null']) it.expects(json.keys['key_null'].value).toBeA(Null); it.expects(json.keys['key_bool_true'].value).toBeA(Boolean); @@ -106,20 +139,56 @@ package it.expects(json.keys['key_number_real'].value).toBeA(Number); it.expects(json.keys['key_number_real'].value).toEqual(0.9876); + + it.expects(json.keys['key_array'].value).toBeA(Vector); + it.expects(json.keys['key_object'].value).toBeA(Dictionary); } private static function get_array_elements_via_items():void { + var json:Json = jsonFixture; + it.expects(json.keys['key_empty_array'].items.length).toEqual(0); it.expects(json.keys['key_array'].items[0].value).toEqual('array_1'); - it.expects(json.keys['key_nested_arrays'].items[1].items[0].items[0].value).toEqual(4); + it.expects(json.keys['key_nested_monotype_array'].items[1].items[0].items[0].value).toEqual(4); + it.expects(json.keys['key_nested_multitype_array'].items[1].keys['c'].keys['z'].value).toEqual('Z'); + it.expects(json.keys['key_nested_multitype_array'].items[3].value).toEqual(true); } private static function get_object_props_via_keys():void { + var json:Json = jsonFixture; + it.expects(json.keys['key_empty_object'].keys.length).toEqual(0); it.expects(json.keys['key_object'].keys['object_1'].value).toEqual(1); - it.expects(json.keys['key_nested_in_object'].keys['nested_2'].keys['c'].keys['z'].value).toEqual('Z'); + it.expects(json.keys['key_nested_multitype_object'].keys['nested_2'].keys['c'].keys['z'].value).toEqual('Z'); + } + + private static function merge_objects():void + { + var jsonObject1:Dictionary. = {}; + var jsonObject2:Dictionary. = {}; + + jsonObject1['key_string'] = 'original value'; + + jsonObject2['key_string'] = 'overridden value'; + jsonObject2['key_merged_array'] = ["merged_array"]; + jsonObject2['key_merged_object'] = {"merged_object": true}; + + var j1:Json = Json.fromObject(jsonObject1); + var j2:Json = Json.fromObject(jsonObject2); + + it.asserts(j1).isNotNull(); + it.asserts(j2).isNotNull(); + + it.expects(j1.keys.length).toEqual(1); + it.expects(j1.keys['key_string'].value).toEqual('original value'); + + Json.merge(j1, j2); + it.expects(j1.keys.length).toEqual(3); + it.expects(j1.keys['key_string'].value).toEqual('overridden value'); + it.expects(j1.keys['key_merged_array'].value).toBeA(Vector); + it.expects(j1.keys['key_merged_object'].value).toBeA(Dictionary); } } } diff --git a/test/src/spec/YamlPrinterSpec.ls b/test/src/spec/YamlPrinterSpec.ls new file mode 100644 index 0000000..da81012 --- /dev/null +++ b/test/src/spec/YamlPrinterSpec.ls @@ -0,0 +1,46 @@ +package +{ + import pixeldroid.bdd.Spec; + import pixeldroid.bdd.Thing; + + import pixeldroid.json.Json; + import pixeldroid.json.YamlPrinter; + import pixeldroid.json.YamlPrinterOptions; + + + public static class YamlPrinterSpec + { + private static var it:Thing; + private static var json:Json; + + public static function specify(specifier:Spec):void + { + it = specifier.describe('YamlPrinter'); + var jsonFile:String = 'fixtures/json.json'; + var jsonString:String = File.loadTextFile(jsonFile); + + json = Json.fromString(jsonString); + + // validation of the YAML itself is not testable here until a loom YAML parser is available + // in the meantime, output is manually validated with the following linters: + // https://codebeautify.org/yaml-validator + // http://www.yamllint.com/ + + it.should('default to standard formatting options', default_to_standard_formatting); + } + + + private static function default_to_standard_formatting():void + { + var prettyString:String = YamlPrinter.print(json); + var standard:String = YamlPrinter.print(json, YamlPrinterOptions.standard); + + it.expects(prettyString == standard).toBeTruthy(); + // it.expects(prettyString).toEqual(standard); + // ^ this format would generally be preferred, + // but will print the full strings to the log, + // so we use a more basic assertion instead + } + + } +}