From 2b743aaa06bfcf7baf5da011d3eaee0294ffa2ca Mon Sep 17 00:00:00 2001 From: smittytone Date: Wed, 5 Jun 2019 15:31:32 +0100 Subject: [PATCH 01/23] Code tidy; bump to 2.0.1; throw on bad unicode --- JSONEncoder.class.nut | 352 ++++++++++++++++++++--------------------- LICENSE.txt => LICENSE | 3 +- README.md | 69 ++++---- 3 files changed, 212 insertions(+), 212 deletions(-) rename LICENSE.txt => LICENSE (96%) diff --git a/JSONEncoder.class.nut b/JSONEncoder.class.nut index b7a9f47..79bae05 100644 --- a/JSONEncoder.class.nut +++ b/JSONEncoder.class.nut @@ -1,196 +1,192 @@ -// Copyright (c) 2017 Electric Imp +// Copyright (c) 2017-19 Electric Imp // This file is licensed under the MIT License // http://opensource.org/licenses/MIT class JSONEncoder { - static VERSION = "2.0.0"; - - // max structure depth - // anything above probably has a cyclic ref - static _maxDepth = 32; - - /** - * Encode value to JSON - * @param {table|array|*} value - * @returns {string} - */ - function encode(value) { - return this._encode(value); - } - - /** - * @param {table|array} val - * @param {integer=0} depth – current depth level - * @private - */ - function _encode(val, depth = 0) { - - // detect cyclic reference - if (depth > this._maxDepth) { - throw "Possible cyclic reference"; - } - - local - r = "", - s = "", - i = 0; - - switch (typeof val) { - - case "table": - case "class": - s = ""; + static VERSION = "2.0.1"; - // serialize properties, but not functions - foreach (k, v in val) { - if (typeof v != "function") { - s += ",\"" + k + "\":" + this._encode(v, depth + 1); - } - } - - s = s.len() > 0 ? s.slice(1) : s; - r += "{" + s + "}"; - break; + // max structure depth + // anything above probably has a cyclic ref + static _maxDepth = 32; - case "array": - s = ""; + /** + * Encode value to JSON + * @param {table|array|*} value + * @returns {string} + */ + function encode(value) { + return this._encode(value); + } - for (i = 0; i < val.len(); i++) { - s += "," + this._encode(val[i], depth + 1); + /** + * @param {table|array} val + * @param {integer=0} depth – current depth level + * @private + */ + function _encode(val, depth = 0) { + // Detect cyclic reference + if (depth > this._maxDepth) throw "Possible cyclic reference"; + + local r = "", s = "", i = 0; + + switch (typeof val) { + case "table": + case "class": + s = ""; + // serialize properties, but not functions + foreach (k, v in val) { + if (typeof v != "function") { + s += ",\"" + k + "\":" + this._encode(v, depth + 1); + } + } + + s = s.len() > 0 ? s.slice(1) : s; + r += "{" + s + "}"; + break; + + case "array": + s = ""; + + for (i = 0; i < val.len(); i++) { + s += "," + this._encode(val[i], depth + 1); + } + + s = (i > 0) ? s.slice(1) : s; + r += "[" + s + "]"; + break; + + case "integer": + case "float": + case "bool": + r += val; + break; + + case "null": + r += "null"; + break; + + case "instance": + if ("_serializeRaw" in val && typeof val._serializeRaw == "function") { + // include value produced by _serializeRaw() + r += val._serializeRaw().tostring(); + } else if ("_serialize" in val && typeof val._serialize == "function") { + // serialize instances by calling _serialize method + r += this._encode(val._serialize(), depth + 1); + } else { + s = ""; + + try { + // Iterate through instances which implement _nexti meta-method + foreach (k, v in val) { + s += ",\"" + k + "\":" + this._encode(v, depth + 1); + } + } catch (e) { + // Iterate through instances w/o _nexti + // serialize properties, but not functions + foreach (k, v in val.getclass()) { + if (typeof v != "function") { + s += ",\"" + k + "\":" + this._encode(val[k], depth + 1); + } + } + } + + s = s.len() > 0 ? s.slice(1) : s; + r += "{" + s + "}"; + } + + break; + + case "blob": + // This is a workaround for a known bug: + // on device side Blob.tostring() returns null + // (instaead of an empty string) + r += "\"" + (val.len() ? this._escape(val.tostring()) : "") + "\""; + break; + + // Strings and all other + default: + r += "\"" + this._escape(val.tostring()) + "\""; + break; } - s = (i > 0) ? s.slice(1) : s; - r += "[" + s + "]"; - break; - - case "integer": - case "float": - case "bool": - r += val; - break; - - case "null": - r += "null"; - break; - - case "instance": - - if ("_serializeRaw" in val && typeof val._serializeRaw == "function") { - - // include value produced by _serializeRaw() - r += val._serializeRaw().tostring(); - - } else if ("_serialize" in val && typeof val._serialize == "function") { - - // serialize instances by calling _serialize method - r += this._encode(val._serialize(), depth + 1); - - } else { - - s = ""; - - try { - - // iterate through instances which implement _nexti meta-method - foreach (k, v in val) { - s += ",\"" + k + "\":" + this._encode(v, depth + 1); - } - - } catch (e) { + return r; + } - // iterate through instances w/o _nexti - // serialize properties, but not functions - foreach (k, v in val.getclass()) { - if (typeof v != "function") { - s += ",\"" + k + "\":" + this._encode(val[k], depth + 1); - } + /** + * Escape strings according to http://www.json.org/ spec + * @param {string} str + */ + function _escape(str) { + local res = ""; + for (local i = 0; i < str.len(); i++) { + local ch1 = (str[i] & 0xFF); + if ((ch1 & 0x80) == 0x00) { + // 7-bit Ascii + ch1 = format("%c", ch1); + if (ch1 == "\"") { + res += "\\\""; + } else if (ch1 == "\\") { + res += "\\\\"; + } else if (ch1 == "/") { + res += "\\/"; + } else if (ch1 == "\b") { + res += "\\b"; + } else if (ch1 == "\f") { + res += "\\f"; + } else if (ch1 == "\n") { + res += "\\n"; + } else if (ch1 == "\r") { + res += "\\r"; + } else if (ch1 == "\t") { + res += "\\t"; + } else if (ch1[0] < 31) { + // U+0000 to U+001F (0-31) MUST be escaped as per + // https://www.ietf.org/rfc/rfc4627.txt + res += ("\\u00" + _toHex(ch1[0])); + } else { + res += ch1; + } + } else { + if ((ch1 & 0xE0) == 0xC0) { + // 110xxxxx = 2-byte unicode + if (i + 1 < str.len()) { + local ch2 = (str[++i] & 0xFF); + res += format("%c%c", ch1, ch2); + } else { + throw "Insufficient data for 2-byte Unicode"; + } + } else if ((ch1 & 0xF0) == 0xE0) { + // 1110xxxx = 3-byte unicode + if (i + 2 < str.len()) { + local ch2 = (str[++i] & 0xFF); + local ch3 = (str[++i] & 0xFF); + res += format("%c%c%c", ch1, ch2, ch3); + } else { + throw "Insufficient data for 3-byte Unicode"; + } + } else if ((ch1 & 0xF8) == 0xF0) { + // 11110xxx = 4 byte unicode + if (i + 3 < str.len()) { + local ch2 = (str[++i] & 0xFF); + local ch3 = (str[++i] & 0xFF); + local ch4 = (str[++i] & 0xFF); + res += format("%c%c%c%c", ch1, ch2, ch3, ch4); + } else { + throw "Insufficient data for 4-byte Unicode"; + } + } else { + throw "String contains invalid Unicode"; + } } - - } - - s = s.len() > 0 ? s.slice(1) : s; - r += "{" + s + "}"; } - break; - - case "blob": - // This is a workaround for a known bug: - // on device side Blob.tostring() returns null - // (instaead of an empty string) - r += "\"" + (val.len() ? this._escape(val.tostring()) : "") + "\""; - break; - - // strings and all other - default: - r += "\"" + this._escape(val.tostring()) + "\""; - break; + return res; } - return r; - } - - /** - * Escape strings according to http://www.json.org/ spec - * @param {string} str - */ - function _escape(str) { - local res = ""; - - for (local i = 0; i < str.len(); i++) { - - local ch1 = (str[i] & 0xFF); - - if ((ch1 & 0x80) == 0x00) { - // 7-bit Ascii - - ch1 = format("%c", ch1); - - if (ch1 == "\"") { - res += "\\\""; - } else if (ch1 == "\\") { - res += "\\\\"; - } else if (ch1 == "/") { - res += "\\/"; - } else if (ch1 == "\b") { - res += "\\b"; - } else if (ch1 == "\f") { - res += "\\f"; - } else if (ch1 == "\n") { - res += "\\n"; - } else if (ch1 == "\r") { - res += "\\r"; - } else if (ch1 == "\t") { - res += "\\t"; - } else if (ch1 == "\0") { - res += "\\u0000"; - } else { - res += ch1; - } - - } else { - - if ((ch1 & 0xE0) == 0xC0) { - // 110xxxxx = 2-byte unicode - local ch2 = (str[++i] & 0xFF); - res += format("%c%c", ch1, ch2); - } else if ((ch1 & 0xF0) == 0xE0) { - // 1110xxxx = 3-byte unicode - local ch2 = (str[++i] & 0xFF); - local ch3 = (str[++i] & 0xFF); - res += format("%c%c%c", ch1, ch2, ch3); - } else if ((ch1 & 0xF8) == 0xF0) { - // 11110xxx = 4 byte unicode - local ch2 = (str[++i] & 0xFF); - local ch3 = (str[++i] & 0xFF); - local ch4 = (str[++i] & 0xFF); - res += format("%c%c%c%c", ch1, ch2, ch3, ch4); - } - - } + function _toHex(i, l = 2) { + if (l % 2 != 0) l++; + local fs = "%0" + l.tostring() + "x"; + return format(fs, i); } - - return res; - } } diff --git a/LICENSE.txt b/LICENSE similarity index 96% rename from LICENSE.txt rename to LICENSE index 31faaba..3938369 100644 --- a/LICENSE.txt +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Electric Imp +Copyright (c) 2016-19 Electric Imp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/README.md b/README.md index 2cf750d..1c43fa0 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,20 @@ -# JSON Encoder +# JSON Encoder # This library can be used to encode Squirrel data structures into JSON. -**To add this library to your project, add** `#require "JSONEncoder.class.nut:2.0.0"` **to the top of your code.** +**To include this library in your project, add** `#require "JSONEncoder.class.nut:2.0.1"` **at the top of your code.** ![Build Status](https://cse-ci.electricimp.com/app/rest/builds/buildType:(id:JSONEncoder_BuildAndTest)/statusIcon) -## Usage +## Usage ## JSONEncoder has no constructor and one public function, *encode()*. -### encode(*data*) +### encode(*data*) ### The *encode()* method takes one required parameter: the data to be encoded. It returns a JSON string version of that data. -#### Basic Example: +#### Basic Example #### ```squirrel data <- { "one": 1 }; @@ -27,29 +27,34 @@ server.log(jsonString); // Displays '{"one":1}' ``` -## Serialization Details +## Serialization Details ## + +### Unicode Strings ### -### Unicode Strings The class’ current implementation suggests that Squirrel is compiled with single-byte strings (as is the case with the Electric Imp Platform) and correctly handles UTF-8 characters. -### Class Serialization +### Class Serialization ### + When serializing classes, functions are ignored and only properties are exposed. -### Instance Serialization +### Instance Serialization ### + When serializing instances, functions are ignored and only properties are exposed. If the instance implements the *_nexti()* metamethod, it can define a custom serialization behavior. Another way for defining custom representation in JSON is to implement a *_serialize()* method in your class. -#### Custom Serialization with \_serialize() Method +#### Custom Serialization with \_serialize() Method ### + Instances can contain a *_serialize()* method that is called during the encoding to get the representation of an instance as (for example) a table or an array. See the extended example below. -#### Serializing As-is +#### Serializing As-is ### + In some cases it may be useful to provide a ‘raw’ representation of an instance to the JSON encoder. In order to do so, an instance can define a *_serializeRaw()* method returning a string value. This value is then inserted into the resulting JSON output without further processing or escaping. ```squirrel class A { - function _serializeRaw() { - // Very long integer - return "12345678901234567890"; - } + function _serializeRaw() { + // Very long integer + return "12345678901234567890"; + } }; value <- JSONEncoder.encode( [ A() ] ); @@ -62,29 +67,29 @@ server.log(value); **Note** While this method may be useful in certain cases, it has the potential to produce a non-valid JSON output. -## Extended Example +## Extended Example ## ```squirrel class A { - _field = 123; + _field = 123; - // returns instance representation as table - function _serialize() { - return { - field = this._field + // returns instance representation as table + function _serialize() { + return { + field = this._field + } } - } } t <- { - a = 123, - b = [1, 2, 3, 4], - c = A, - d = 5.125, - e = A(), - f = null, - g = true, - h = "Some\nùnicode\rstring ø∆ø" + a = 123, + b = [1, 2, 3, 4], + c = A, + d = 5.125, + e = A(), + f = null, + g = true, + h = "Some\nùnicode\rstring ø∆ø" }; server.log(JSONEncoder.encode(t)); @@ -92,6 +97,6 @@ server.log(JSONEncoder.encode(t)); // Note: this output is a JSON string ``` -## License +## License ## -The code in this repository is licensed under [MIT License](https://github.com/electricimp/serializer/tree/master/LICENSE). +This library is licensed under the [MIT License](LICENSE). From 519c4d74233cb4619496e2c89e45bad3de3e8d4d Mon Sep 17 00:00:00 2001 From: smittytone Date: Thu, 6 Jun 2019 17:21:16 +0100 Subject: [PATCH 02/23] Add base64encoding for blobs As recommended by http.jsonencode() page --- JSONEncoder.class.nut | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/JSONEncoder.class.nut b/JSONEncoder.class.nut index 79bae05..a601106 100644 --- a/JSONEncoder.class.nut +++ b/JSONEncoder.class.nut @@ -98,10 +98,10 @@ class JSONEncoder { break; case "blob": - // This is a workaround for a known bug: - // on device side Blob.tostring() returns null - // (instaead of an empty string) - r += "\"" + (val.len() ? this._escape(val.tostring()) : "") + "\""; + // Workaround a known bug: on device side Blob.tostring() + // returns null instead of an empty string. And base64-encode + // the blob as per http.jsonencode() page suggests. + r += "\"" + (val.len() ? this._base64encode(val) : "") + "\""; break; // Strings and all other @@ -184,6 +184,36 @@ class JSONEncoder { return res; } + function _base64encode(input) { + local charStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + local output = ""; + // Copy the input as it may be changed by the code below + local data = input.tostring(); + // Add one or two padding bytes + while (data.len() % 3 != 0) data += "\x0"; + // Perform the encode + for (local i = 0 ; i < data.len() ; i += 3) { + local bitfield = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2]; + local padFlag = false; + for (local j = 0 ; j < 4 ; j++) { + local shift = 6 * (3 - j); + local group = (bitfield & (0x3F << shift)) >> shift; + // Display the base64 padding indicators as required + if (g == 0 && padFlag) { + op += "==".slice(0, 4 - j); + break; + } + // Display the current character + output += charStr[group].tochar(); + padFlag = false; + // Check for what may be the last 'nibble' + if (j == 1 && (g & 0x0F) == 0) padFlag = true; + if (j == 2 && (g & 0x03) == 0) padFlag = true; + } + } + return output; + } + function _toHex(i, l = 2) { if (l % 2 != 0) l++; local fs = "%0" + l.tostring() + "x"; From 3d633312ca049eb9c8cd3a22ad6a84ffc4b7c8aa Mon Sep 17 00:00:00 2001 From: smittytone Date: Mon, 10 Jun 2019 11:39:39 +0100 Subject: [PATCH 03/23] Fix incorrect var names --- JSONEncoder.class.nut | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/JSONEncoder.class.nut b/JSONEncoder.class.nut index a601106..77e91d7 100644 --- a/JSONEncoder.class.nut +++ b/JSONEncoder.class.nut @@ -104,7 +104,7 @@ class JSONEncoder { r += "\"" + (val.len() ? this._base64encode(val) : "") + "\""; break; - // Strings and all other + // Strings and all other types default: r += "\"" + this._escape(val.tostring()) + "\""; break; @@ -199,7 +199,7 @@ class JSONEncoder { local shift = 6 * (3 - j); local group = (bitfield & (0x3F << shift)) >> shift; // Display the base64 padding indicators as required - if (g == 0 && padFlag) { + if (group == 0 && padFlag) { op += "==".slice(0, 4 - j); break; } @@ -207,8 +207,8 @@ class JSONEncoder { output += charStr[group].tochar(); padFlag = false; // Check for what may be the last 'nibble' - if (j == 1 && (g & 0x0F) == 0) padFlag = true; - if (j == 2 && (g & 0x03) == 0) padFlag = true; + if (j == 1 && (group & 0x0F) == 0) padFlag = true; + if (j == 2 && (group & 0x03) == 0) padFlag = true; } } return output; From 507e637fc902c09787db81f952fcea1404c662d9 Mon Sep 17 00:00:00 2001 From: smittytone Date: Tue, 11 Jun 2019 11:55:06 +0100 Subject: [PATCH 04/23] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c43fa0..987335a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# JSON Encoder # +# JSON Encoder 2.0.1 # This library can be used to encode Squirrel data structures into JSON. From 32f76f10d8b9b2c002512248df75e419c41cb54e Mon Sep 17 00:00:00 2001 From: smittytone Date: Tue, 11 Jun 2019 17:17:16 +0100 Subject: [PATCH 05/23] Update JSONEncoder.class.nut --- JSONEncoder.class.nut | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JSONEncoder.class.nut b/JSONEncoder.class.nut index 77e91d7..bdcc92f 100644 --- a/JSONEncoder.class.nut +++ b/JSONEncoder.class.nut @@ -200,7 +200,7 @@ class JSONEncoder { local group = (bitfield & (0x3F << shift)) >> shift; // Display the base64 padding indicators as required if (group == 0 && padFlag) { - op += "==".slice(0, 4 - j); + output += "==".slice(0, 4 - j); break; } // Display the current character From cbac91f41c6fff0efd2bc20a4fb1ef540800faa7 Mon Sep 17 00:00:00 2001 From: smittytone Date: Wed, 12 Jun 2019 10:25:31 +0100 Subject: [PATCH 06/23] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 987335a..b07a96d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,14 @@ server.log(jsonString); The class’ current implementation suggests that Squirrel is compiled with single-byte strings (as is the case with the Electric Imp Platform) and correctly handles UTF-8 characters. +### Binary Strings ### + +Binary strings may be interpreted as Unicode strings. We recommend adding binary strings as blobs. + +### Blobs ### + +Blobs are stored as base64-encoded strings. + ### Class Serialization ### When serializing classes, functions are ignored and only properties are exposed. From 2d3ad00776cb2a001ce39437ccecb1f087cbaf6b Mon Sep 17 00:00:00 2001 From: smittytone Date: Tue, 2 Jul 2019 11:57:09 +1000 Subject: [PATCH 07/23] Add new private method comments --- JSONEncoder.class.nut | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/JSONEncoder.class.nut b/JSONEncoder.class.nut index bdcc92f..1f7c957 100644 --- a/JSONEncoder.class.nut +++ b/JSONEncoder.class.nut @@ -184,6 +184,12 @@ class JSONEncoder { return res; } + /** + * Base64-encode the supplied string. + * @param {string} input - The input string. + * @returns {string} - The base64 encode. + * @private + */ function _base64encode(input) { local charStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; local output = ""; @@ -214,9 +220,16 @@ class JSONEncoder { return output; } - function _toHex(i, l = 2) { - if (l % 2 != 0) l++; - local fs = "%0" + l.tostring() + "x"; + /** + * Convert integer to hex string. + * @param {integer} i - The input integer value. + * @param {integer} n - The number of digits in the output. Default: 2 + * @returns {string} - The hex string output. + * @private + */ + function _toHex(i, n = 2) { + if (n % 2 != 0) n++; + local fs = "%0" + n.tostring() + "x"; return format(fs, i); } } From 8270efb729aafb01b31ba291c2dcadc428fd8184 Mon Sep 17 00:00:00 2001 From: smittytone Date: Tue, 20 Aug 2019 14:08:03 +0100 Subject: [PATCH 08/23] Update examples --- examples/example-a.nut | 2 +- examples/example-b.nut | 2 +- examples/example-c.nut | 2 +- examples/example-d.nut | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/example-a.nut b/examples/example-a.nut index ebda9dc..9fa9433 100644 --- a/examples/example-a.nut +++ b/examples/example-a.nut @@ -1,4 +1,4 @@ -#require "JSONEncoder.class.nut:1.0.0" +#require "JSONEncoder.class.nut:2.0.1" class A { field = 1 diff --git a/examples/example-b.nut b/examples/example-b.nut index 618a7f7..c6fcfaf 100644 --- a/examples/example-b.nut +++ b/examples/example-b.nut @@ -1,4 +1,4 @@ -#require "JSONEncoder.class.nut:1.0.0" +#require "JSONEncoder.class.nut:2.0.1" // contains _serialize function to allow custom representation of an instance class A { diff --git a/examples/example-c.nut b/examples/example-c.nut index 8dbfa2d..7a5695b 100644 --- a/examples/example-c.nut +++ b/examples/example-c.nut @@ -1,4 +1,4 @@ -#require "JSONEncoder.class.nut:1.0.0" +#require "JSONEncoder.class.nut:2.0.1" t <- { a = 123, diff --git a/examples/example-d.nut b/examples/example-d.nut index 572572c..9dddcd4 100644 --- a/examples/example-d.nut +++ b/examples/example-d.nut @@ -1,4 +1,4 @@ -#require "JSONEncoder.class.nut:1.0.0" +#require "JSONEncoder.class.nut:2.0.1" /** * Example of as-is serialization From 86efb8e6a15c191a8f49758f22580b3e7a787a8f Mon Sep 17 00:00:00 2001 From: smittytone Date: Tue, 20 Aug 2019 15:37:42 +0100 Subject: [PATCH 09/23] Add blob example --- README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b07a96d..abf35c4 100644 --- a/README.md +++ b/README.md @@ -39,17 +39,28 @@ Binary strings may be interpreted as Unicode strings. We recommend adding binary ### Blobs ### -Blobs are stored as base64-encoded strings. +Blobs are stored as base64-encoded strings *(from version 2.0.1)*. -### Class Serialization ### +```squirrel +local b = blob(144); +b.writestring("Welcome to the Electric Imp Dev Center. We’ve collected everything you’ll need to build great connected products with the Electric Imp Platform.") + +local j = JSONEncoder.encode({"a_blob":b}); +server.log(j); + +// Logs: +// {"a_blob":"V2VsY29tZSB0byB0aGUgRWxlY3RyaWMgSW1wIERldiBDZW50ZXIuIFdl4oCZdmUgY29sbGVjdGVkIGV2ZXJ5dGhpbmcgeW914oCZbGwgbmVlZCB0byBidWlsZCBncmVhdCBjb25uZWN0ZWQgcHJvZHVjdHMgd2l0aCB0aGUgRWxlY3RyaWMgSW1wIFBsYXRmb3JtLg=="} +``` + +### Classes ### When serializing classes, functions are ignored and only properties are exposed. -### Instance Serialization ### +### Instances ### When serializing instances, functions are ignored and only properties are exposed. If the instance implements the *_nexti()* metamethod, it can define a custom serialization behavior. Another way for defining custom representation in JSON is to implement a *_serialize()* method in your class. -#### Custom Serialization with \_serialize() Method ### +#### Custom Serialization with the \_serialize() Method ### Instances can contain a *_serialize()* method that is called during the encoding to get the representation of an instance as (for example) a table or an array. See the extended example below. From 161d29f6084ef50d3d3da6371a15ed159df8a1e0 Mon Sep 17 00:00:00 2001 From: smittytone Date: Fri, 18 Oct 2019 13:31:48 +0100 Subject: [PATCH 10/23] Add examples, extra test --- examples/example-e.nut | 14 ++++++++++++++ tests/blob.agent.nut | 16 ++++++++++++++++ tests/blob.device.test.nut | 29 +++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 examples/example-e.nut create mode 100644 tests/blob.agent.nut create mode 100644 tests/blob.device.test.nut diff --git a/examples/example-e.nut b/examples/example-e.nut new file mode 100644 index 0000000..906d9e6 --- /dev/null +++ b/examples/example-e.nut @@ -0,0 +1,14 @@ +#require "JSONEncoder.class.nut:2.0.1" + +/** + * Example of blob serialization + */ + +local b = blob(144); +b.writestring("Welcome to the Electric Imp Dev Center. We’ve collected everything you’ll need to build great connected products with the Electric Imp Platform.") + +local j = JSONEncoder.encode({"binary_data":b}); +server.log(j); + +// Logs: +// {"binary_data":"V2VsY29tZSB0byB0aGUgRWxlY3RyaWMgSW1wIERldiBDZW50ZXIuIFdl4oCZdmUgY29sbGVjdGVkIGV2ZXJ5dGhpbmcgeW914oCZbGwgbmVlZCB0byBidWlsZCBncmVhdCBjb25uZWN0ZWQgcHJvZHVjdHMgd2l0aCB0aGUgRWxlY3RyaWMgSW1wIFBsYXRmb3JtLg=="} \ No newline at end of file diff --git a/tests/blob.agent.nut b/tests/blob.agent.nut new file mode 100644 index 0000000..110d00d --- /dev/null +++ b/tests/blob.agent.nut @@ -0,0 +1,16 @@ +// Copyright (c) 2019 Electric Imp +// This file is licensed under the MIT License +// http://opensource.org/licenses/MIT + +device.on("get.json", function(s) { + + // Native base64-encode the received string + local b = http.base64encode(s); + local t = {"binary_data":b}; + + // Native JSON-encode the new table + local j = http.jsonencode(t); + + // Send it back to the device to complete the test + device.send("set.json", j); +}); \ No newline at end of file diff --git a/tests/blob.device.test.nut b/tests/blob.device.test.nut new file mode 100644 index 0000000..8c7eb63 --- /dev/null +++ b/tests/blob.device.test.nut @@ -0,0 +1,29 @@ +// Copyright (c) 2019 Electric Imp +// This file is licensed under the MIT License +// http://opensource.org/licenses/MIT + +@include "github:electricimp/JSONEncoder/JSONEncoder.class.nut@fix-binary-string-issue" +@include "github:electricimp/JSONParser/JSONParser.class.nut" + +class BlobDeviceTestCase extends ImpTestCase { + + function test01() { + // Create a blob and add meaningful data to it + local b = blob(144); + local s = "Welcome to the Electric Imp Dev Center. We’ve collected everything you’ll need to build great connected products with the Electric Imp Platform."; + b.writestring(s) + + // Encode the data and send to the agent for testing + local j = ::JSONEncoder.encode({"binary_data":b}); + + agent.on("set.json", function(js) { + local t1 = ::JSONParser.parse(j); + local b1 = t1.binary_data; + local t2 = ::JSONParser.parse(js); + local b2 = t2.binary_data; + this.assertEqual(b1, b2); + }.bindenv(this)); + + agent.send("get.json", s); + } +} \ No newline at end of file From 5a7623892c52d2992ca7b5f93e77326bf8d738a7 Mon Sep 17 00:00:00 2001 From: smittytone Date: Fri, 18 Oct 2019 13:36:53 +0100 Subject: [PATCH 11/23] Update blob.device.test.nut --- tests/blob.device.test.nut | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/blob.device.test.nut b/tests/blob.device.test.nut index 8c7eb63..d27ca94 100644 --- a/tests/blob.device.test.nut +++ b/tests/blob.device.test.nut @@ -2,7 +2,7 @@ // This file is licensed under the MIT License // http://opensource.org/licenses/MIT -@include "github:electricimp/JSONEncoder/JSONEncoder.class.nut@fix-binary-string-issue" +//@include "github:electricimp/JSONEncoder/JSONEncoder.class.nut@fix-binary-string-issue" @include "github:electricimp/JSONParser/JSONParser.class.nut" class BlobDeviceTestCase extends ImpTestCase { From 261c39f0fbac7f1075b4b43cff22340886f35c20 Mon Sep 17 00:00:00 2001 From: smittytone Date: Fri, 18 Oct 2019 13:57:05 +0100 Subject: [PATCH 12/23] Update Basic.agent.test.nut --- tests/Basic.agent.test.nut | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/Basic.agent.test.nut b/tests/Basic.agent.test.nut index da708e5..b31c32f 100644 --- a/tests/Basic.agent.test.nut +++ b/tests/Basic.agent.test.nut @@ -37,14 +37,4 @@ class BasicAgentTestCase extends ImpTestCase { this.assertEqual("{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"a\\ta\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}", res); } - /** - * @see https://github.com/electricimp/JSONEncoder/issues/2 - */ - function test02_Nullchars() { - local obj = blob(7); - obj.writestring("Hello"); - local str = ::JSONEncoder.encode(obj); - this.assertEqual("\"Hello\\u0000\\u0000\"", str); - } - } From 7a5c7b233c738e6fd0c8a901ef66f13f07c2c2e6 Mon Sep 17 00:00:00 2001 From: smittytone Date: Fri, 18 Oct 2019 14:01:47 +0100 Subject: [PATCH 13/23] Update tests for v3.0.0 operation on blobs --- tests/Basic.agent.test.nut | 2 +- tests/CustomType.agent.test.nut | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Basic.agent.test.nut b/tests/Basic.agent.test.nut index b31c32f..d43f21b 100644 --- a/tests/Basic.agent.test.nut +++ b/tests/Basic.agent.test.nut @@ -34,7 +34,7 @@ class BasicAgentTestCase extends ImpTestCase { local res = ::JSONEncoder.encode(data); - this.assertEqual("{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"a\\ta\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}", res); + this.assertEqual("{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"YQlh",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}", res); } } diff --git a/tests/CustomType.agent.test.nut b/tests/CustomType.agent.test.nut index 8907869..41a268f 100644 --- a/tests/CustomType.agent.test.nut +++ b/tests/CustomType.agent.test.nut @@ -34,7 +34,7 @@ class CustomTypeTestCase extends ImpTestCase { local res = ::JSONEncoder.encode(data); - this.assertEqual("{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"a\\ta\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}", res); + this.assertEqual("{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"YQlh\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}", res); } } From 51af2a4c61ea3861ee22f8c69b5e6ef3f4296cdf Mon Sep 17 00:00:00 2001 From: smittytone Date: Fri, 18 Oct 2019 14:03:59 +0100 Subject: [PATCH 14/23] Update Basic.agent.test.nut --- tests/Basic.agent.test.nut | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Basic.agent.test.nut b/tests/Basic.agent.test.nut index d43f21b..96cbe96 100644 --- a/tests/Basic.agent.test.nut +++ b/tests/Basic.agent.test.nut @@ -34,7 +34,7 @@ class BasicAgentTestCase extends ImpTestCase { local res = ::JSONEncoder.encode(data); - this.assertEqual("{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"YQlh",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}", res); + this.assertEqual("{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"YQlh\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}", res); } } From 7af8337a4ea3a68ec1196ebf944614fe6b1c1920 Mon Sep 17 00:00:00 2001 From: smittytone Date: Fri, 18 Oct 2019 14:06:46 +0100 Subject: [PATCH 15/23] Update examples: library v3.0.0 --- JSONEncoder.class.nut | 2 +- README.md | 8 ++++---- examples/example-a.nut | 2 +- examples/example-b.nut | 4 ++-- examples/example-c.nut | 2 +- examples/example-d.nut | 2 +- examples/example-e.nut | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/JSONEncoder.class.nut b/JSONEncoder.class.nut index 1f7c957..79ca54c 100644 --- a/JSONEncoder.class.nut +++ b/JSONEncoder.class.nut @@ -4,7 +4,7 @@ class JSONEncoder { - static VERSION = "2.0.1"; + static VERSION = "3.0.0"; // max structure depth // anything above probably has a cyclic ref diff --git a/README.md b/README.md index abf35c4..8db35e3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# JSON Encoder 2.0.1 # +# JSON Encoder 3.0.0 # This library can be used to encode Squirrel data structures into JSON. @@ -39,17 +39,17 @@ Binary strings may be interpreted as Unicode strings. We recommend adding binary ### Blobs ### -Blobs are stored as base64-encoded strings *(from version 2.0.1)*. +Blobs are stored as base64-encoded strings *(from version 3.0.0)*. ```squirrel local b = blob(144); b.writestring("Welcome to the Electric Imp Dev Center. We’ve collected everything you’ll need to build great connected products with the Electric Imp Platform.") -local j = JSONEncoder.encode({"a_blob":b}); +local j = JSONEncoder.encode({"binary_data":b}); server.log(j); // Logs: -// {"a_blob":"V2VsY29tZSB0byB0aGUgRWxlY3RyaWMgSW1wIERldiBDZW50ZXIuIFdl4oCZdmUgY29sbGVjdGVkIGV2ZXJ5dGhpbmcgeW914oCZbGwgbmVlZCB0byBidWlsZCBncmVhdCBjb25uZWN0ZWQgcHJvZHVjdHMgd2l0aCB0aGUgRWxlY3RyaWMgSW1wIFBsYXRmb3JtLg=="} +// {"binary_data":"V2VsY29tZSB0byB0aGUgRWxlY3RyaWMgSW1wIERldiBDZW50ZXIuIFdl4oCZdmUgY29sbGVjdGVkIGV2ZXJ5dGhpbmcgeW914oCZbGwgbmVlZCB0byBidWlsZCBncmVhdCBjb25uZWN0ZWQgcHJvZHVjdHMgd2l0aCB0aGUgRWxlY3RyaWMgSW1wIFBsYXRmb3JtLg=="} ``` ### Classes ### diff --git a/examples/example-a.nut b/examples/example-a.nut index 9fa9433..5db88f7 100644 --- a/examples/example-a.nut +++ b/examples/example-a.nut @@ -1,4 +1,4 @@ -#require "JSONEncoder.class.nut:2.0.1" +#require "JSONEncoder.class.nut:3.0.0" class A { field = 1 diff --git a/examples/example-b.nut b/examples/example-b.nut index c6fcfaf..6e38ce7 100644 --- a/examples/example-b.nut +++ b/examples/example-b.nut @@ -1,4 +1,4 @@ -#require "JSONEncoder.class.nut:2.0.1" +#require "JSONEncoder.class.nut:3.0.0" // contains _serialize function to allow custom representation of an instance class A { @@ -29,4 +29,4 @@ t <- { s <- JSONEncoder.encode(t); try { server.log(s) } catch (e) { ::print(s) }; -assert(s == "{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"a\\ta\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}"); +assert(s == "{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"YQlh\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}"); diff --git a/examples/example-c.nut b/examples/example-c.nut index 7a5695b..32ac7c1 100644 --- a/examples/example-c.nut +++ b/examples/example-c.nut @@ -1,4 +1,4 @@ -#require "JSONEncoder.class.nut:2.0.1" +#require "JSONEncoder.class.nut:3.0.0" t <- { a = 123, diff --git a/examples/example-d.nut b/examples/example-d.nut index 9dddcd4..4a2d8c9 100644 --- a/examples/example-d.nut +++ b/examples/example-d.nut @@ -1,4 +1,4 @@ -#require "JSONEncoder.class.nut:2.0.1" +#require "JSONEncoder.class.nut:3.0.0" /** * Example of as-is serialization diff --git a/examples/example-e.nut b/examples/example-e.nut index 906d9e6..b3a56e8 100644 --- a/examples/example-e.nut +++ b/examples/example-e.nut @@ -1,4 +1,4 @@ -#require "JSONEncoder.class.nut:2.0.1" +#require "JSONEncoder.class.nut:3.0.0" /** * Example of blob serialization From 8f5bcb969e415d74c56bf9e4e75fbd495f76f044 Mon Sep 17 00:00:00 2001 From: smittytone Date: Tue, 22 Oct 2019 14:37:06 +0100 Subject: [PATCH 16/23] Revise to display binary strings --- JSONEncoder.class.nut | 82 +++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/JSONEncoder.class.nut b/JSONEncoder.class.nut index 79ca54c..6bcedac 100644 --- a/JSONEncoder.class.nut +++ b/JSONEncoder.class.nut @@ -4,7 +4,7 @@ class JSONEncoder { - static VERSION = "3.0.0"; + static VERSION = "2.1.0"; // max structure depth // anything above probably has a cyclic ref @@ -101,12 +101,25 @@ class JSONEncoder { // Workaround a known bug: on device side Blob.tostring() // returns null instead of an empty string. And base64-encode // the blob as per http.jsonencode() page suggests. - r += "\"" + (val.len() ? this._base64encode(val) : "") + "\""; - break; + if (val.len() == 0) { + r += "\"\""; + break; + } + + // Fallthrough to string handler // Strings and all other types default: - r += "\"" + this._escape(val.tostring()) + "\""; + // Try to get the data as viable human-readable string, ie. + // it contains unicode/Ascii + local x = this._escape(val.tostring()); + if (x != null) { + // The string is *probably* valid, so add it to the JSON + r += "\"" + x + "\""; + } else { + // The string doesn't appear to be valid so just dump it + r += "\"" + this._dumpBytes(val) + "\""; + } break; } @@ -154,7 +167,9 @@ class JSONEncoder { local ch2 = (str[++i] & 0xFF); res += format("%c%c", ch1, ch2); } else { - throw "Insufficient data for 2-byte Unicode"; + //throw "Insufficient data for 2-byte Unicode"; + res = null; + break; } } else if ((ch1 & 0xF0) == 0xE0) { // 1110xxxx = 3-byte unicode @@ -163,20 +178,25 @@ class JSONEncoder { local ch3 = (str[++i] & 0xFF); res += format("%c%c%c", ch1, ch2, ch3); } else { - throw "Insufficient data for 3-byte Unicode"; + //throw "Insufficient data for 3-byte Unicode"; + res = null; + break; } } else if ((ch1 & 0xF8) == 0xF0) { - // 11110xxx = 4 byte unicode + // 11110xxx = 4-byte unicode if (i + 3 < str.len()) { local ch2 = (str[++i] & 0xFF); local ch3 = (str[++i] & 0xFF); local ch4 = (str[++i] & 0xFF); res += format("%c%c%c%c", ch1, ch2, ch3, ch4); } else { - throw "Insufficient data for 4-byte Unicode"; + //throw "Insufficient data for 4-byte Unicode"; + res = null; + break; } } else { - throw "String contains invalid Unicode"; + res = null; + break; } } } @@ -184,42 +204,6 @@ class JSONEncoder { return res; } - /** - * Base64-encode the supplied string. - * @param {string} input - The input string. - * @returns {string} - The base64 encode. - * @private - */ - function _base64encode(input) { - local charStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - local output = ""; - // Copy the input as it may be changed by the code below - local data = input.tostring(); - // Add one or two padding bytes - while (data.len() % 3 != 0) data += "\x0"; - // Perform the encode - for (local i = 0 ; i < data.len() ; i += 3) { - local bitfield = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2]; - local padFlag = false; - for (local j = 0 ; j < 4 ; j++) { - local shift = 6 * (3 - j); - local group = (bitfield & (0x3F << shift)) >> shift; - // Display the base64 padding indicators as required - if (group == 0 && padFlag) { - output += "==".slice(0, 4 - j); - break; - } - // Display the current character - output += charStr[group].tochar(); - padFlag = false; - // Check for what may be the last 'nibble' - if (j == 1 && (group & 0x0F) == 0) padFlag = true; - if (j == 2 && (group & 0x03) == 0) padFlag = true; - } - } - return output; - } - /** * Convert integer to hex string. * @param {integer} i - The input integer value. @@ -232,4 +216,12 @@ class JSONEncoder { local fs = "%0" + n.tostring() + "x"; return format(fs, i); } + + function _dumpBytes(b) { + local rs = ""; + for (local i = 0 ; i < b.len() ; i++) { + rs += "\\x" + _toHex(b[i]) + } + return rs; + } } From 0c48d87f0903be28a9a7280bdff271fcddc045bd Mon Sep 17 00:00:00 2001 From: smittytone Date: Tue, 22 Oct 2019 14:52:50 +0100 Subject: [PATCH 17/23] Update README.md --- README.md | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 8db35e3..59268bb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# JSON Encoder 3.0.0 # +# JSON Encoder 2.1.0 # This library can be used to encode Squirrel data structures into JSON. -**To include this library in your project, add** `#require "JSONEncoder.class.nut:2.0.1"` **at the top of your code.** +**To include this library in your project, add** `#require "JSONEncoder.class.nut:2.1.0"` **at the top of your code.** ![Build Status](https://cse-ci.electricimp.com/app/rest/builds/buildType:(id:JSONEncoder_BuildAndTest)/statusIcon) @@ -29,28 +29,15 @@ server.log(jsonString); ## Serialization Details ## -### Unicode Strings ### +### Unicode And Binary Strings ### -The class’ current implementation suggests that Squirrel is compiled with single-byte strings (as is the case with the Electric Imp Platform) and correctly handles UTF-8 characters. +The class’ current implementation checks strings to see if they contain valid unicode. Strings that do are stored as Unicode; strings that do not are stored as Ascii-encoded hex strings. -### Binary Strings ### - -Binary strings may be interpreted as Unicode strings. We recommend adding binary strings as blobs. +It is important to note that the library can’t determine what the string is intended to contain: a string with valid Unicode could actually contain binary data as returned, for example, by an imp API method such as **i2c.read()**. ### Blobs ### -Blobs are stored as base64-encoded strings *(from version 3.0.0)*. - -```squirrel -local b = blob(144); -b.writestring("Welcome to the Electric Imp Dev Center. We’ve collected everything you’ll need to build great connected products with the Electric Imp Platform.") - -local j = JSONEncoder.encode({"binary_data":b}); -server.log(j); - -// Logs: -// {"binary_data":"V2VsY29tZSB0byB0aGUgRWxlY3RyaWMgSW1wIERldiBDZW50ZXIuIFdl4oCZdmUgY29sbGVjdGVkIGV2ZXJ5dGhpbmcgeW914oCZbGwgbmVlZCB0byBidWlsZCBncmVhdCBjb25uZWN0ZWQgcHJvZHVjdHMgd2l0aCB0aGUgRWxlY3RyaWMgSW1wIFBsYXRmb3JtLg=="} -``` +Blobs are stored as strings which are in turn stored as detailed above. Zero-length blobs are stored as empty strings, eg. `{ "empty_blob" : "" }`. ### Classes ### From a8a5bf7abac8731bd45e1f6533bde7db0181f901 Mon Sep 17 00:00:00 2001 From: smittytone Date: Tue, 22 Oct 2019 14:53:04 +0100 Subject: [PATCH 18/23] Remove old tests --- tests/blob.agent.nut | 16 ---------------- tests/blob.device.test.nut | 29 ----------------------------- 2 files changed, 45 deletions(-) delete mode 100644 tests/blob.agent.nut delete mode 100644 tests/blob.device.test.nut diff --git a/tests/blob.agent.nut b/tests/blob.agent.nut deleted file mode 100644 index 110d00d..0000000 --- a/tests/blob.agent.nut +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2019 Electric Imp -// This file is licensed under the MIT License -// http://opensource.org/licenses/MIT - -device.on("get.json", function(s) { - - // Native base64-encode the received string - local b = http.base64encode(s); - local t = {"binary_data":b}; - - // Native JSON-encode the new table - local j = http.jsonencode(t); - - // Send it back to the device to complete the test - device.send("set.json", j); -}); \ No newline at end of file diff --git a/tests/blob.device.test.nut b/tests/blob.device.test.nut deleted file mode 100644 index d27ca94..0000000 --- a/tests/blob.device.test.nut +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2019 Electric Imp -// This file is licensed under the MIT License -// http://opensource.org/licenses/MIT - -//@include "github:electricimp/JSONEncoder/JSONEncoder.class.nut@fix-binary-string-issue" -@include "github:electricimp/JSONParser/JSONParser.class.nut" - -class BlobDeviceTestCase extends ImpTestCase { - - function test01() { - // Create a blob and add meaningful data to it - local b = blob(144); - local s = "Welcome to the Electric Imp Dev Center. We’ve collected everything you’ll need to build great connected products with the Electric Imp Platform."; - b.writestring(s) - - // Encode the data and send to the agent for testing - local j = ::JSONEncoder.encode({"binary_data":b}); - - agent.on("set.json", function(js) { - local t1 = ::JSONParser.parse(j); - local b1 = t1.binary_data; - local t2 = ::JSONParser.parse(js); - local b2 = t2.binary_data; - this.assertEqual(b1, b2); - }.bindenv(this)); - - agent.send("get.json", s); - } -} \ No newline at end of file From 61c868cf8e7e80f45315dcf033d6ea50d85d0651 Mon Sep 17 00:00:00 2001 From: smittytone Date: Tue, 22 Oct 2019 15:05:58 +0100 Subject: [PATCH 19/23] Adjust tests for new lib version --- tests/Basic.agent.test.nut | 51 ++++++++++++------------- tests/Basic.device.test.nut | 57 ++++++++++++++++++++++++---- tests/CustomIterators.agent.test.nut | 31 +++++++-------- tests/CustomType.agent.test.nut | 53 +++++++++++++------------- tests/Raw.agent.test.nut | 26 ++++++------- 5 files changed, 130 insertions(+), 88 deletions(-) diff --git a/tests/Basic.agent.test.nut b/tests/Basic.agent.test.nut index 96cbe96..01e6c78 100644 --- a/tests/Basic.agent.test.nut +++ b/tests/Basic.agent.test.nut @@ -4,37 +4,36 @@ // contains _serialize function to allow custom representation of an instance class A { - _field = 123; + _field = 123; - // returns instance representation as table - function _serialize() { - return { - field = this._field + // returns instance representation as table + function _serialize() { + return { field = this._field } } - } } + class BasicAgentTestCase extends ImpTestCase { - function test01() { - local someblob = blob(); - someblob.writestring("a\ta"); - - local data = { - a = 123, - b = [1, 2, 3, 4], - c = A, - d = 5.125, - e = A(), - f = null, - g = true, - h = "Some\nùnicode\rstring ø∆ø", - i = someblob - }; - - local res = ::JSONEncoder.encode(data); - - this.assertEqual("{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"YQlh\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}", res); - } + function test01() { + local someblob = blob(); + someblob.writestring("a\ta"); + + local data = { + a = 123, + b = [1, 2, 3, 4], + c = A, + d = 5.125, + e = A(), + f = null, + g = true, + h = "Some\nùnicode\rstring ø∆ø", + i = someblob + }; + + local res = ::JSONEncoder.encode(data); + + this.assertEqual("{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"a\\ta\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}", res); + } } diff --git a/tests/Basic.device.test.nut b/tests/Basic.device.test.nut index 9713c0e..3cca931 100644 --- a/tests/Basic.device.test.nut +++ b/tests/Basic.device.test.nut @@ -2,16 +2,57 @@ // This file is licensed under the MIT License // http://opensource.org/licenses/MIT +@include "github:electricimp/JSONEncoder/JSONEncoder.class.nut" + class BasicDeviceTestCase extends ImpTestCase { - function test01() { - local emptyblob = blob(0); - local data = { - a = emptyblob - }; + function test01() { + + // Test: empty blob + local emptyblob = blob(0); + local data = { a = emptyblob }; + local res = ::JSONEncoder.encode(data); + this.assertEqual("{\"a\":\"\"}", res); + } + + function test02() { + + // Test: Binary string -- see https://electricimp.atlassian.net/browse/CSE-702 + local binStr = "\xe8\x03\x00\x00\x01\x00\x00\xc0"; + local jsonStr = ::JSONEncoder.encode({"binary_string":binStr}); + this.assertEqual(jsonStr, "{\"binary_string\":\"\\xe8\\x03\\x00\\x00\\x01\\x00\\x00\\xc0\"}"); + } + + function test02() { + + // Test: Plain Ascii string + local asciiStr = "Electric Imp Developer Center"; + local jsonStr = ::JSONEncoder.encode({"ascii_string":asciiStr}); + this.assertEqual(jsonStr, "{\"ascii_string\":\"Electric Imp Developer Center\"}"); + } + + function test04() { + + // Test: Unicode string 1 + local uniStr = "💾❤️😎🎸"; + local jsonStr = ::JSONEncoder.encode({"uni_string":uniStr}); + this.assertEqual(jsonStr, "{\"uni_string\":\"💾❤️😎🎸\"}"); + } + + function test05() { + + // Test: Unicode string 2 + local uniStr = "\xF0\x9F\x98\x9C"; + local jsonStr = ::JSONEncoder.encode({"uni_string":uniStr}); + this.assertEqual(jsonStr, "{\"uni_string\":\"😜\"}"); + } - local res = ::JSONEncoder.encode(data); + function test06() { - this.assertEqual("{\"a\":\"\"}", res); - } + // Test: Full Blob + local b = blob(144); + b.writestring("Welcome to the Electric Imp Dev Center. We’ve collected everything you’ll need to build great connected products with the Electric Imp Platform."); + local jsonStr = ::JSONEncoder.encode({"blob":b}); + this.assertEqual(jsonStr, "{\"blob\":\"Welcome to the Electric Imp Dev Center. We’ve collected everything you’ll need to build great connected products with the Electric Imp Platform.\"}"); + } } diff --git a/tests/CustomIterators.agent.test.nut b/tests/CustomIterators.agent.test.nut index 2e71861..6e49abd 100644 --- a/tests/CustomIterators.agent.test.nut +++ b/tests/CustomIterators.agent.test.nut @@ -2,28 +2,29 @@ // This file is licensed under the MIT License // http://opensource.org/licenses/MIT -// iterated in a custom way +// Iterated in a custom way class C { - field = 123 + field = 123; - function _nexti(p) { - if (p == null) return 0; - if (p < 1) return p+1; - return null; - } + function _nexti(p) { + if (p == null) return 0; + if (p < 1) return p+1; + return null; + } - function _get(i) { - return "c"; - } + function _get(i) { + return "c"; + } } + class CustomIteratorsTestCase extends ImpTestCase { - function test01() { - local data = {c = C()}; - local res = ::JSONEncoder.encode(data); - this.assertEqual("{\"c\":{\"0\":\"c\",\"1\":\"c\"}}", res); - } + function test01() { + local data = {c = C()}; + local res = ::JSONEncoder.encode(data); + this.assertEqual("{\"c\":{\"0\":\"c\",\"1\":\"c\"}}", res); + } } diff --git a/tests/CustomType.agent.test.nut b/tests/CustomType.agent.test.nut index 41a268f..d3de697 100644 --- a/tests/CustomType.agent.test.nut +++ b/tests/CustomType.agent.test.nut @@ -4,37 +4,38 @@ // contains _serialize function to allow custom representation of an instance class A { - _field = 123; + _field = 123; - // returns instance representation as table - function _serialize() { - return { - field = this._field + // returns instance representation as table + function _serialize() { + return { + field = this._field + } } - } } + class CustomTypeTestCase extends ImpTestCase { - function test01() { - local someblob = blob(); - someblob.writestring("a\ta"); - - local data = { - a = 123, - b = [1, 2, 3, 4], - c = A, - d = 5.125, - e = A(), - f = null, - g = true, - h = "Some\nùnicode\rstring ø∆ø", - i = someblob - }; - - local res = ::JSONEncoder.encode(data); - - this.assertEqual("{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"YQlh\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}", res); - } + function test01() { + local someblob = blob(); + someblob.writestring("a\ta"); + + local data = { + a = 123, + b = [1, 2, 3, 4], + c = A, + d = 5.125, + e = A(), + f = null, + g = true, + h = "Some\nùnicode\rstring ø∆ø", + i = someblob + }; + + local res = ::JSONEncoder.encode(data); + + this.assertEqual("{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"a\\ta\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}", res); + } } diff --git a/tests/Raw.agent.test.nut b/tests/Raw.agent.test.nut index 574d4ab..11a8a9b 100644 --- a/tests/Raw.agent.test.nut +++ b/tests/Raw.agent.test.nut @@ -8,26 +8,26 @@ // value returned by _serialize() is then encoded further class serializeClass { - function _serialize() { - // very long integer - return "12345678901234567890" - } + function _serialize() { + // Very long integer + return "12345678901234567890" + } }; // value returned by _serializeRaw() is then encoded further class serializeRawClass { - function _serializeRaw() { - // very long integer - return "12345678901234567890" - } + function _serializeRaw() { + // Very long integer + return "12345678901234567890" + } }; class RawTestCase extends ImpTestCase { - function test01() { - local data = {i1 = serializeClass(), i2 = serializeRawClass()}; - local res = ::JSONEncoder.encode(data); - this.assertEqual("{\"i1\":\"12345678901234567890\",\"i2\":12345678901234567890}", res); - } + function test01() { + local data = {i1 = serializeClass(), i2 = serializeRawClass()}; + local res = ::JSONEncoder.encode(data); + this.assertEqual("{\"i1\":\"12345678901234567890\",\"i2\":12345678901234567890}", res); + } } From 0ad4a0eaaeb4c17130bf70ba590d3a744a26a495 Mon Sep 17 00:00:00 2001 From: smittytone Date: Tue, 22 Oct 2019 15:06:31 +0100 Subject: [PATCH 20/23] Update Basic.device.test.nut --- tests/Basic.device.test.nut | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Basic.device.test.nut b/tests/Basic.device.test.nut index 3cca931..3515bc4 100644 --- a/tests/Basic.device.test.nut +++ b/tests/Basic.device.test.nut @@ -2,8 +2,6 @@ // This file is licensed under the MIT License // http://opensource.org/licenses/MIT -@include "github:electricimp/JSONEncoder/JSONEncoder.class.nut" - class BasicDeviceTestCase extends ImpTestCase { function test01() { From bbd0a60bd8b73f1880e9a3e2ac12d316caeb0901 Mon Sep 17 00:00:00 2001 From: smittytone Date: Tue, 22 Oct 2019 15:13:29 +0100 Subject: [PATCH 21/23] Update examples --- examples/example-a.nut | 30 +++++++++++++++--------------- examples/example-b.nut | 32 +++++++++++++++----------------- examples/example-c.nut | 6 +++--- examples/example-d.nut | 16 ++++++++-------- examples/example-e.nut | 11 +++++++---- 5 files changed, 48 insertions(+), 47 deletions(-) diff --git a/examples/example-a.nut b/examples/example-a.nut index 5db88f7..b6f5dd1 100644 --- a/examples/example-a.nut +++ b/examples/example-a.nut @@ -1,27 +1,27 @@ -#require "JSONEncoder.class.nut:3.0.0" +#require "JSONEncoder.class.nut:2.1.0" class A { - field = 1 + field = 1 - constructor() { - // when serialized as instance field==2 should be returned - this.field = 2; - } + constructor() { + // when serialized as instance field==2 should be returned + this.field = 2; + } } // iterated in a custom way class C { - field = 123 + field = 123 - function _nexti(p) { - if (p == null) return 0; - if (p < 1) return p+1; - return null; - } + function _nexti(p) { + if (p == null) return 0; + if (p < 1) return p+1; + return null; + } - function _get(i) { - return "c"; - } + function _get(i) { + return "c"; + } } s <- JSONEncoder.encode({a=A(),c=C()}); diff --git a/examples/example-b.nut b/examples/example-b.nut index 6e38ce7..67c77a9 100644 --- a/examples/example-b.nut +++ b/examples/example-b.nut @@ -1,32 +1,30 @@ -#require "JSONEncoder.class.nut:3.0.0" +#require "JSONEncoder.class.nut:2.1.0" // contains _serialize function to allow custom representation of an instance class A { - _field = 123; + _field = 123; - // returns instance representation as table - function _serialize() { - return { - field = this._field + // returns instance representation as table + function _serialize() { + return { field = this._field }; } - } } someblob <- blob(); someblob.writestring("a\ta"); t <- { - a = 123, - b = [1, 2, 3, 4], - c = A, - d = 5.125, - e = A(), - f = null, - g = true, - h = "Some\nùnicode\rstring ø∆ø", - i = someblob + a = 123, + b = [1, 2, 3, 4], + c = A, + d = 5.125, + e = A(), + f = null, + g = true, + h = "Some\nùnicode\rstring ø∆ø", + i = someblob }; s <- JSONEncoder.encode(t); try { server.log(s) } catch (e) { ::print(s) }; -assert(s == "{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"YQlh\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}"); +assert(s == "{\"a\":123,\"c\":{\"_field\":123},\"b\":[1,2,3,4],\"e\":{\"field\":123},\"d\":5.125,\"g\":true,\"f\":null,\"i\":\"a\\ta\",\"h\":\"Some\\nùnicode\\rstring ø∆ø\"}"); diff --git a/examples/example-c.nut b/examples/example-c.nut index 32ac7c1..c79c2ba 100644 --- a/examples/example-c.nut +++ b/examples/example-c.nut @@ -1,8 +1,8 @@ -#require "JSONEncoder.class.nut:3.0.0" +#require "JSONEncoder.class.nut:2.1.0" t <- { - a = 123, - b = [1, 2, 3, 4] + a = 123, + b = [1, 2, 3, 4] }; s <- JSONEncoder.encode(t); diff --git a/examples/example-d.nut b/examples/example-d.nut index 4a2d8c9..011375c 100644 --- a/examples/example-d.nut +++ b/examples/example-d.nut @@ -6,18 +6,18 @@ // value returned by _serialize() is then encoded further class serializeClass { - function _serialize() { - // very long integer - return "12345678901234567890" - } + function _serialize() { + // very long integer + return "12345678901234567890"; + } }; // value returned by _serializeRaw() is then encoded further class serializeRawClass { - function _serializeRaw() { - // very long integer - return "12345678901234567890" - } + function _serializeRaw() { + // very long integer + return "12345678901234567890"; + } }; s <- JSONEncoder.encode({i1 = serializeClass(), i2 = serializeRawClass()}); diff --git a/examples/example-e.nut b/examples/example-e.nut index b3a56e8..c0c04ce 100644 --- a/examples/example-e.nut +++ b/examples/example-e.nut @@ -1,14 +1,17 @@ -#require "JSONEncoder.class.nut:3.0.0" +#require "JSONEncoder.class.nut:2.1.0" /** * Example of blob serialization */ local b = blob(144); -b.writestring("Welcome to the Electric Imp Dev Center. We’ve collected everything you’ll need to build great connected products with the Electric Imp Platform.") +b.writestring("Welcome to the Electric Imp Dev Center. We’ve collected everything you’ll need to build great connected products with the Electric Imp Platform."); -local j = JSONEncoder.encode({"binary_data":b}); +local uniStr1 = "💾❤️😎🎸"; +local uniStr2 = "\xF0\x9F\x98\x9C"; + +local j = JSONEncoder.encode({"binary_data":b, "uni_strings": [{"uni_string_one":uniStr}, {"uni_string_two":uniStr2}]}); server.log(j); // Logs: -// {"binary_data":"V2VsY29tZSB0byB0aGUgRWxlY3RyaWMgSW1wIERldiBDZW50ZXIuIFdl4oCZdmUgY29sbGVjdGVkIGV2ZXJ5dGhpbmcgeW914oCZbGwgbmVlZCB0byBidWlsZCBncmVhdCBjb25uZWN0ZWQgcHJvZHVjdHMgd2l0aCB0aGUgRWxlY3RyaWMgSW1wIFBsYXRmb3JtLg=="} \ No newline at end of file +// {"uni_strings":[{"uni_string_one":"💾❤️😎🎸"},{"uni_string_two":"😜"}],"binary_data":"Welcome to the Electric Imp Dev Center. We’ve collected everything you’ll need to build great connected products with the Electric Imp Platform."} \ No newline at end of file From 66351ccbf4d3438763a427f2ff4791e559b78f70 Mon Sep 17 00:00:00 2001 From: smittytone Date: Tue, 22 Oct 2019 15:18:08 +0100 Subject: [PATCH 22/23] Add comments --- JSONEncoder.class.nut | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/JSONEncoder.class.nut b/JSONEncoder.class.nut index 6bcedac..6c27e9a 100644 --- a/JSONEncoder.class.nut +++ b/JSONEncoder.class.nut @@ -127,8 +127,10 @@ class JSONEncoder { } /** - * Escape strings according to http://www.json.org/ spec - * @param {string} str + * Escape strings according to http://www.json.org/ spec. + * @param {string} str - The input string. + * @returns {string/null} - The rendered string output, or null if the string lacks valid unicode. + * @private */ function _escape(str) { local res = ""; @@ -217,6 +219,12 @@ class JSONEncoder { return format(fs, i); } + /** + * Generate an ascii representation of a hex string of bytes. + * @param {blob/string} b - The input string or blob. + * @returns {string} - The ascii-encoded hex string output. + * @private + */ function _dumpBytes(b) { local rs = ""; for (local i = 0 ; i < b.len() ; i++) { From 9d034e71ce1c242a10a70c2d90a9ba8e13380eed Mon Sep 17 00:00:00 2001 From: smittytone Date: Thu, 23 Nov 2023 12:19:34 +0000 Subject: [PATCH 23/23] Bump to 3.0.0; fix issues --- .impt.test | 3 +-- JSONEncoder.class.nut | 7 +++---- LICENSE | 3 ++- README.md | 2 +- examples/example-a.nut | 5 ++++- examples/example-b.nut | 5 ++++- examples/example-c.nut | 5 ++++- examples/example-d.nut | 3 +++ examples/example-e.nut | 7 +++++-- tests/Basic.agent.test.nut | 3 ++- tests/Basic.device.test.nut | 3 ++- tests/CustomIterators.agent.test.nut | 3 ++- tests/CustomType.agent.test.nut | 3 ++- tests/Raw.agent.test.nut | 3 ++- 14 files changed, 37 insertions(+), 18 deletions(-) diff --git a/.impt.test b/.impt.test index 2f371f6..d97486b 100644 --- a/.impt.test +++ b/.impt.test @@ -1,6 +1,5 @@ { - "deviceGroupId": "d51fc3f4-f818-f7e9-8a93-a7e392a9480d", - "deviceGroupName" : "impFarm P", + "deviceGroupId": "aaf50bad-6522-2e27-4b5b-c40999cddf7a", "timeout": 30, "stopOnFail": false, "allowDisconnect": false, diff --git a/JSONEncoder.class.nut b/JSONEncoder.class.nut index 6c27e9a..cc6f8e5 100644 --- a/JSONEncoder.class.nut +++ b/JSONEncoder.class.nut @@ -1,10 +1,11 @@ // Copyright (c) 2017-19 Electric Imp +// Copyright (c) 2020-23 KORE Wireless // This file is licensed under the MIT License // http://opensource.org/licenses/MIT class JSONEncoder { - static VERSION = "2.1.0"; + static VERSION = "3.0.0"; // max structure depth // anything above probably has a cyclic ref @@ -169,7 +170,6 @@ class JSONEncoder { local ch2 = (str[++i] & 0xFF); res += format("%c%c", ch1, ch2); } else { - //throw "Insufficient data for 2-byte Unicode"; res = null; break; } @@ -180,7 +180,6 @@ class JSONEncoder { local ch3 = (str[++i] & 0xFF); res += format("%c%c%c", ch1, ch2, ch3); } else { - //throw "Insufficient data for 3-byte Unicode"; res = null; break; } @@ -192,7 +191,7 @@ class JSONEncoder { local ch4 = (str[++i] & 0xFF); res += format("%c%c%c%c", ch1, ch2, ch3, ch4); } else { - //throw "Insufficient data for 4-byte Unicode"; + res = null; break; } diff --git a/LICENSE b/LICENSE index 3938369..53a6d03 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) -Copyright (c) 2016-19 Electric Imp +Copyright (c) 2017-19 Electric Imp +Copyright (c) 2020-23 KORE Wireless Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 59268bb..30adaef 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# JSON Encoder 2.1.0 # +# JSON Encoder 3.0.0 # This library can be used to encode Squirrel data structures into JSON. diff --git a/examples/example-a.nut b/examples/example-a.nut index b6f5dd1..1eb2cf0 100644 --- a/examples/example-a.nut +++ b/examples/example-a.nut @@ -1,4 +1,7 @@ -#require "JSONEncoder.class.nut:2.1.0" +// Copyright (c) 2017-19 Electric Imp +// Copyright (c) 2020-23 KORE Wireless + +#require "JSONEncoder.class.nut:3.0.0" class A { field = 1 diff --git a/examples/example-b.nut b/examples/example-b.nut index 67c77a9..b68fe36 100644 --- a/examples/example-b.nut +++ b/examples/example-b.nut @@ -1,4 +1,7 @@ -#require "JSONEncoder.class.nut:2.1.0" +// Copyright (c) 2017-19 Electric Imp +// Copyright (c) 2020-23 KORE Wireless + +#require "JSONEncoder.class.nut:3.0.0" // contains _serialize function to allow custom representation of an instance class A { diff --git a/examples/example-c.nut b/examples/example-c.nut index c79c2ba..ea7f1ce 100644 --- a/examples/example-c.nut +++ b/examples/example-c.nut @@ -1,4 +1,7 @@ -#require "JSONEncoder.class.nut:2.1.0" +// Copyright (c) 2017-19 Electric Imp +// Copyright (c) 2020-23 KORE Wireless + +#require "JSONEncoder.class.nut:3.0.0" t <- { a = 123, diff --git a/examples/example-d.nut b/examples/example-d.nut index 011375c..be7a8a6 100644 --- a/examples/example-d.nut +++ b/examples/example-d.nut @@ -1,3 +1,6 @@ +// Copyright (c) 2017-19 Electric Imp +// Copyright (c) 2020-23 KORE Wireless + #require "JSONEncoder.class.nut:3.0.0" /** diff --git a/examples/example-e.nut b/examples/example-e.nut index c0c04ce..5d43b92 100644 --- a/examples/example-e.nut +++ b/examples/example-e.nut @@ -1,4 +1,7 @@ -#require "JSONEncoder.class.nut:2.1.0" +// Copyright (c) 2017-19 Electric Imp +// Copyright (c) 2020-23 KORE Wireless + +#require "JSONEncoder.class.nut:3.0.0" /** * Example of blob serialization @@ -10,7 +13,7 @@ b.writestring("Welcome to the Electric Imp Dev Center. We’ve collected everyth local uniStr1 = "💾❤️😎🎸"; local uniStr2 = "\xF0\x9F\x98\x9C"; -local j = JSONEncoder.encode({"binary_data":b, "uni_strings": [{"uni_string_one":uniStr}, {"uni_string_two":uniStr2}]}); +local j = JSONEncoder.encode({"binary_data":b, "uni_strings": [{"uni_string_one":uniStr1}, {"uni_string_two":uniStr2}]}); server.log(j); // Logs: diff --git a/tests/Basic.agent.test.nut b/tests/Basic.agent.test.nut index 01e6c78..c19189f 100644 --- a/tests/Basic.agent.test.nut +++ b/tests/Basic.agent.test.nut @@ -1,4 +1,5 @@ -// Copyright (c) 2017 Electric Imp +// Copyright (c) 2017-19 Electric Imp +// Copyright (c) 2020-23 KORE Wireless // This file is licensed under the MIT License // http://opensource.org/licenses/MIT diff --git a/tests/Basic.device.test.nut b/tests/Basic.device.test.nut index 3515bc4..8af9d9d 100644 --- a/tests/Basic.device.test.nut +++ b/tests/Basic.device.test.nut @@ -1,4 +1,5 @@ -// Copyright (c) 2017 Electric Imp +// Copyright (c) 2017-19 Electric Imp +// Copyright (c) 2020-23 KORE Wireless // This file is licensed under the MIT License // http://opensource.org/licenses/MIT diff --git a/tests/CustomIterators.agent.test.nut b/tests/CustomIterators.agent.test.nut index 6e49abd..083956e 100644 --- a/tests/CustomIterators.agent.test.nut +++ b/tests/CustomIterators.agent.test.nut @@ -1,4 +1,5 @@ -// Copyright (c) 2017 Electric Imp +// Copyright (c) 2017-19 Electric Imp +// Copyright (c) 2020-23 KORE Wireless // This file is licensed under the MIT License // http://opensource.org/licenses/MIT diff --git a/tests/CustomType.agent.test.nut b/tests/CustomType.agent.test.nut index d3de697..b724a10 100644 --- a/tests/CustomType.agent.test.nut +++ b/tests/CustomType.agent.test.nut @@ -1,4 +1,5 @@ -// Copyright (c) 2017 Electric Imp +// Copyright (c) 2017-19 Electric Imp +// Copyright (c) 2020-23 KORE Wireless // This file is licensed under the MIT License // http://opensource.org/licenses/MIT diff --git a/tests/Raw.agent.test.nut b/tests/Raw.agent.test.nut index 11a8a9b..c797569 100644 --- a/tests/Raw.agent.test.nut +++ b/tests/Raw.agent.test.nut @@ -1,4 +1,5 @@ -// Copyright (c) 2017 Electric Imp +// Copyright (c) 2017-19 Electric Imp +// Copyright (c) 2020-23 KORE Wireless // This file is licensed under the MIT License // http://opensource.org/licenses/MIT