diff --git a/JSONParser.class.nut b/JSONParser.class.nut index ac19d8f..6d6c166 100644 --- a/JSONParser.class.nut +++ b/JSONParser.class.nut @@ -3,7 +3,7 @@ * * @author Mikhail Yurasov * @package JSONParser - * @version 1.0.2 + * @version 2.0.0 */ /** @@ -13,7 +13,7 @@ class JSONParser { // should be the same for all components within JSONParser package - static version = "1.0.2"; + static version = "2.0.0"; /** * Parse JSON string into data structure @@ -44,15 +44,15 @@ class JSONParser { state = "colon"; }, ovalue = function () { - value = this._convert(value, "string", converter); + value = this._convert(value, "string", converter, key); state = "ocomma"; }.bindenv(this), firstavalue = function () { - value = this._convert(value, "string", converter); + value = this._convert(value, "string", converter, key); state = "acomma"; }.bindenv(this), avalue = function () { - value = this._convert(value, "string", converter); + value = this._convert(value, "string", converter, key); state = "acomma"; }.bindenv(this) }; @@ -63,15 +63,15 @@ class JSONParser { state = "ok"; }, ovalue = function () { - value = this._convert(value, "number", converter); + value = this._convert(value, "number", converter, key); state = "ocomma"; }.bindenv(this), firstavalue = function () { - value = this._convert(value, "number", converter); + value = this._convert(value, "number", converter, key); state = "acomma"; }.bindenv(this), avalue = function () { - value = this._convert(value, "number", converter); + value = this._convert(value, "number", converter, key); state = "acomma"; }.bindenv(this) }; @@ -254,7 +254,7 @@ class JSONParser { // current tokenizeing position local start = 0; local lastToken = null; - + try { local @@ -264,7 +264,7 @@ class JSONParser { while (token = tokenizer.nextToken(str, start)) { lastToken = token; - + if ("ptfn" == token.type) { // punctuation/true/false/null action[token.value][state](); @@ -295,9 +295,9 @@ class JSONParser { // if this is a standalone string or number, convert it if (lastToken.type == "string" || lastToken.type == "number") { - return this._convert(lastToken.value, lastToken.type, converter) + return this._convert(lastToken.value, lastToken.type, converter, null) } - + return value; } @@ -309,33 +309,33 @@ class JSONParser { * @param {string} type * @param {function|null} converter */ - function _convert(value, type, converter) { - if ("function" == typeof converter) { - - // # of params for converter function - - local parametercCount = 2; - - // .getinfos() is missing on ei platform - if ("getinfos" in converter) { - parametercCount = converter.getinfos().parameters.len() - - 1 /* "this" is also included */; - } - - if (parametercCount == 1) { - return converter(value); - } else if (parametercCount == 2) { - return converter(value, type); - } else { - throw "Error: converter function must take 1 or 2 parameters" - } - - } else if ("number" == type) { - return (value.find(".") == null && value.find("e") == null && value.find("E") == null) ? value.tointeger() : value.tofloat(); - } else { - return value; + function _convert(value, type, converter, key) { + if ("function" == typeof converter) { + // # of params for converter function + local parametercCount = 3; + + // .getinfos() is missing on ei platform + if ("getinfos" in converter) { + parametercCount = converter.getinfos().parameters.len() + - 1 /* "this" is also included */; + } + + if (parametercCount == 1) { + return converter(value); + } else if (parametercCount == 2) { + return converter(value, type); + } else if(parametercCount == 3) { + return converter(value, type, key); + } else { + throw "Error: converter function must take 1, 2, or 3 parameters" + } + + } else if ("number" == type) { + return (value.find(".") == null && value.find("e") == null && value.find("E") == null) ? value.tointeger() : value.tofloat(); + } else { + return value; + } } - } } /** diff --git a/README.md b/README.md index e2bb75f..91ed274 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Squirrel JSON Parser 1.0.2 # +# Squirrel JSON Parser 2.0.0 # This library parses JSON into Squirrel data types. -**To include this library in your project, add** `#require "JSONParser.class.nut:1.0.2"` **at the top of your code.** +**To include this library in your project, add** `#require "JSONParser.class.nut:2.0.0"` **at the top of your code.** ![Build Status](https://cse-ci.electricimp.com/app/rest/builds/buildType:(id:JSONParser_BuildAndTest)/statusIcon) @@ -10,7 +10,7 @@ This library parses JSON into Squirrel data types. JSONParser has no constructor and one public function, *parse()*. -### parse(*jsonString[, converter]*) +### parse(*jsonString[, converter]*) ### This method converts the supplied JSON to a table. @@ -27,6 +27,7 @@ An optional converter function can be passed into *parse()* to de-serialize cust - *value* — String representation of a value. - *type* — String indicating conversion type: `"string"` or `"number"`. +- *key* — If the value is in a table, this is the value's key. For example, the following code converts all numbers to floats and makes strings uppercase: @@ -78,7 +79,7 @@ s <- JSONEncoder.encode(o); server.log(s); // Displays '{"a":1,"c":"@mycustomtype:100500","b":"Something"}' -result <- JSONParser.parse(s, function (val, type) { +result <- JSONParser.parse(s, function (val, type, key) { if ("number" == type) { return val.tofloat(); } else if ("string" == type) { diff --git a/tests/badData.agent.test.nut b/tests/badData.agent.test.nut new file mode 100644 index 0000000..a93d5b9 --- /dev/null +++ b/tests/badData.agent.test.nut @@ -0,0 +1,89 @@ +/* + * Check for failure when passed bad JSON encoded string + */ +class badData extends ImpTestCase { + + function testBadBasic() { + // local data = { + // "a" : 1, + // "b" : 2, + // "c" : 3, + // }; + // this.info(JSONEncoder.encode(data)) + + // delete a comma + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":1\"c\":3,\"b\":2}"]); + + // delete a curly bracket + this.assertThrowsError(JSONParser.parse, JSONParser, ["\"a\":1,\"c\":3,\"b\":2}"]); + + // delete the value + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":1,\"c\":,\"b\":2}"]); + + // delete a colon + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\"1,\"c\":3,\"b\":2}"]); + + // delete the other bracket + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":1,\"c\":3,\"b\":2"]); + + // delete all the quotes + this.assertThrowsError(JSONParser.parse, JSONParser, ["{a:1,c:3,b:2}"]); + } + + function testBadArray() { + // local data1 = { + // a = [1,2,3], + // b = ["A", "B", "C"], + // c = [1.1, 2.2, 3.3] + // }; + // this.info(JSONEncoder.encode(data1)) + + // delete a bracket + this.assertThrowsError(JSONParser.parse, JSONParser, ["\"a\":[1,2,3],\"c\":[1.1,2.2,3.3],\"b\":[\"A\",\"B\",\"C\"]}"]); + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":[1,2,3],\"c\":[1.1,2.2,3.3],\"b\":[\"A\",\"B\",\"C\"]"]); + + // delete a square bracket + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":[1,2,3],\"c\":[1.1,2.2,3.3],\"b\":[\"A\",\"B\",\"C\"}"]); + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":[1,2,3],\"c\":1.1,2.2,3.3],\"b\":[\"A\",\"B\",\"C\"]}"]); + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":[1,2,3,\"c\":[1.1,2.2,3.3],\"b\":[\"A\",\"B\",\"C\"]}"]); + + // destroy colons + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\"[1,2,3],\"c\"[1.1,2.2,3.3],\"b\"[\"A\",\"B\",\"C\"]}"]); + + + // local data2 = [1, 2, "red", "blue", {"a": "table"}]; + // this.info(JSONEncoder.encode(data2)) + // "[1,2,\"red\",\"blue\",{\"a\":\"table\"}]" + + this.assertThrowsError(JSONParser.parse, JSONParser, ["1,2,\"red\",\"blue\",{\"a\":\"table\"}]"]); + this.assertThrowsError(JSONParser.parse, JSONParser, ["[1,2,\"red\",\"blue\",{\"a\":\"table\"}"]); + this.assertThrowsError(JSONParser.parse, JSONParser, ["[1,2,\"red\",\"blue\",{\"a\":\"table\"]"]); + this.assertThrowsError(JSONParser.parse, JSONParser, ["[1,2,\"red\",\"blue\",\"a\":\"table\"}]"]); + this.assertThrowsError(JSONParser.parse, JSONParser, ["[1,2,\"red\",\"blue\",{\"a\"\"table\"}]"]); + this.assertThrowsError(JSONParser.parse, JSONParser, ["[1,2\"red\"\"blue\"{\"a\":\"table\"}]"]); + } + + function testBadTable() { + // local data = { + // "a" : [1,2,3], + // "b" : "Hello world!" + // "c" : 1598.677 + // }; + // this.info(JSONEncoder.encode(data)) + + // remove commas + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":[1,2,3],\"c\":1598.68\"b\":\"Hello world!\"}"]) + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":[1,2,3]\"c\":1598.68,\"b\":\"Hello world!\"}"]) + + // remove brackets + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":[1,2,3],\"c\":1598.68,\"b\":\"Hello world!\""]) + this.assertThrowsError(JSONParser.parse, JSONParser, ["\"a\":[1,2,3],\"c\":1598.68,\"b\":\"Hello world!\"}"]) + + // and quotation marks + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":[1,2,3],\"c:1598.68,\"b\":\"Hello world!\"}"]) + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":[1,2,3],\"c\":1598.68,\"b\":\"Hello world!}"]) + + // whole chunk of data missing + this.assertThrowsError(JSONParser.parse, JSONParser, ["{\"a\":,\"c\":1598.68,\"b\":\"Hello world!\"}"]) + } +} diff --git a/tests/converter.agent.test.nut b/tests/converter.agent.test.nut index 1597f17..26906bf 100644 --- a/tests/converter.agent.test.nut +++ b/tests/converter.agent.test.nut @@ -27,7 +27,7 @@ class Custom_Converter_TestCase extends ImpTestCase { local s = "{\"a\":1,\"c\":\"@mycustomtype:abc\",\"b\":2}"; // use custom conveter to revreate MyCustomType - local d = JSONParser.parse(s, function (val, type) { + local d = JSONParser.parse(s, function (val, type, key) { if ("number" == type) { return val.tofloat(); } else if ("string" == type) { @@ -42,4 +42,91 @@ class Custom_Converter_TestCase extends ImpTestCase { this.assertTrue(d.c instanceof MyCustomType); this.assertTrue(d.c.getValue() == "abc"); } + + function test_2() { + local s = "\"Hello world!\""; + local d = JSONParser.parse(s, function(value, type, key){ + if (type == "string") { + return value; + } else if (type == "number") { + throw "JSONParse passed wrong type!" + } else { + throw "JSONParse passed invalid type!" + } + }); + + this.assertDeepEqual("Hello world!", d) + } + + function test_3() { + // Arrays with custom parsing + local s = "[1,2,3]"; + local d = JSONParser.parse(s, function(value, type, key){ + if (type == "string") { + throw "JSONParse passed wrong type!" + } else if (type == "number") { + return value.tointeger(); + } else { + throw "JSONParse passed invalid type!" + } + }); + + this.assertDeepEqual([1,2,3], d) + } + + function test_4() { + // Integers with custom parsing + local s = "77"; + local d = JSONParser.parse(s, function(value, type, key){ + if (type == "string") { + throw "JSONParse passed wrong type!" + } else if (type == "number") { + return value.tointeger(); + } else { + throw "JSONParse passed invalid type!" + } + }); + + this.assertDeepEqual(77, d) + } + + function test_5() { + local s = "77.8"; + local d = JSONParser.parse(s, function(value, type, key){ + if (type == "string") { + throw "JSONParse passed wrong type!" + } else if (type == "number") { + return value.tofloat(); + } else { + throw "JSONParse passed invalid type!" + } + }); + + this.assertDeepEqual(77.8, d) + } + + function test_6() { + local s = "{\"a\":\"Hello world!\",\"c\":1.667,\"b\":1024}" + local d = JSONParser.parse(s, function(value, type, key){ + if (type == "string" && key == "a") { + return value; + } else if (type == "number") { + if (key == "c") { + return value.tofloat(); + } else if (key == "b") { + return value.tointeger(); + } else { + throw "JSONParse passed bad key!" + } + } else { + throw "JSONParse key testing failed!" + } + }); + + this.assertDeepEqual({ + a = "Hello world!", + b = 1024, + c = 1.667 + }, d) + } } diff --git a/tests/parse.agent.test.nut b/tests/parse.agent.test.nut index 2f88cc0..8c39fa5 100644 --- a/tests/parse.agent.test.nut +++ b/tests/parse.agent.test.nut @@ -108,6 +108,24 @@ class Parsing_TestCase extends ImpTestCase { function test_5() { local s = "77"; local d = JSONParser.parse(s); - this.assertDeepEqual(d, 77); + this.assertDeepEqual(77, d); + } + + function test_6() { + local s = "\"Hello world!\""; + local d = JSONParser.parse(s) + this.assertDeepEqual("Hello world!", d) + } + + function test_7() { + local s = "77.8"; + local d = JSONParser.parse(s); + this.assertDeepEqual(77.8, d); + } + + function test_8() { + local s = "[1,2,3]"; + local d = JSONParser.parse(s); + this.assertDeepEqual([1,2,3], d); } }