Skip to content

Fix binary string issue #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
391 changes: 215 additions & 176 deletions JSONEncoder.class.nut

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions LICENSE.txt → LICENSE
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.

77 changes: 44 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# JSON Encoder
# JSON Encoder 2.1.0 #

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.1.0"` **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 };
Expand All @@ -27,29 +27,40 @@ server.log(jsonString);
// Displays '{"one":1}'
```

## Serialization Details
## Serialization Details ##

### 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.
### Unicode And Binary Strings ###

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.

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 strings which are in turn stored as detailed above. Zero-length blobs are stored as empty strings, eg. `{ "empty_blob" : "" }`.

### Classes ###

### Class Serialization
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.

#### 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() ] );
Expand All @@ -62,36 +73,36 @@ 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));
// Displays '{"a":123,"c":{"_field":123},"b":[1,2,3,4],"e":{"field":123},"d":5.125,"g":true,"f":null,"h":"Some\nùnicode\rstring ø∆ø"}'
// 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).
30 changes: 15 additions & 15 deletions examples/example-a.nut
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
#require "JSONEncoder.class.nut:1.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()});
Expand Down
30 changes: 14 additions & 16 deletions examples/example-b.nut
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
#require "JSONEncoder.class.nut:1.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);
Expand Down
6 changes: 3 additions & 3 deletions examples/example-c.nut
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#require "JSONEncoder.class.nut:1.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);
Expand Down
18 changes: 9 additions & 9 deletions examples/example-d.nut
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
#require "JSONEncoder.class.nut:1.0.0"
#require "JSONEncoder.class.nut:3.0.0"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong version number


/**
* Example of as-is serialization
*/

// 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()});
Expand Down
17 changes: 17 additions & 0 deletions examples/example-e.nut
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#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.");

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}]});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo - ERROR: the index 'uniStr' does not exist

server.log(j);

// Logs:
// {"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."}
61 changes: 25 additions & 36 deletions tests/Basic.agent.test.nut
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +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\":\"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);
}
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);
}

}
Loading