# SJSON Module
| Since  | Origin / Contributor  | Maintainer  | Source  |
| :----- | :-------------------- | :---------- | :------ |
| 2017-02-01 | [Philip Gladstone](https://github.com/pjsg) | [Philip Gladstone](https://github.com/pjsg) | [sjson](../../../app/modules/sjson.c) |

The JSON support module. Allows encoding and decoding to/from JSON.

Please note that nested tables can require a lot of memory to encode. To catch out-of-memory errors, use `pcall()`.

This code using the streaming json library [jsonsl](https://github.com/mnunberg/jsonsl) to do the parsing of the string.

This module can be used in two ways. The simpler way is to use it as a direct drop-in for cjson (you can just do `_G.cjson = sjson`). 
The more advanced approach is to use the streaming interface. This allows encoding and decoding of significantly larger objects.

The handling of json null is as follows:

- By default, the decoder represents null as sjson.NULL (which is a userdata object). This is the behavior of cjson.
- The encoder always converts any userdata object into null.
- Optionally, a single string can be specified in both the encoder and decoder. This string will be used in encoding/decoding to represent json null values. This string should not be used
anywhere else in your data structures. A suitable value might be `"\0"`.

When encoding a lua object, if a function is found, then it is invoked (with no arguments) and the (single) returned value is encoded in the place of the function.

## sjson.encoder()

This creates an encoder object that can convert a LUA object into a JSON encoded string.

####Syntax
`sjson.encoder(table [, opts])`

####Parameters
- `table` data to encode
- `opts` an optional table of options. The possible entries are:
   - `depth` the maximum encoding depth needed to encode the table. The default is 20 which should be enough for nearly all situations.
   - `null` the string value to treat as null.

####Returns
A `sjson.encoder` object.

## sjson.encoder:read

This gets a chunk of JSON encoded data.

####Syntax
`encoder:read([size])`

####Parameters
- `size` an optional value for the number of bytes to return. The default is 1024.

####Returns
A string of up to `size` bytes, or `nil` if the encoding is complete and all data has been returned.

#### Example
The following example prints out (in 64 byte chunks) a JSON encoded string containing the first 4k of every file in the file system. The total string
can be bigger than the total amount of memory on the NodeMCU.
```
function files() 
   result = {}
   for k,v in pairs(file.list()) do
     result[k] = function() return file.open(k):read(4096) end
   end
   return result
end

local encoder = sjson.encoder(files())

while true do
   data = encoder:read(64)
   if not data then
      break
   end
   print(data)
end
```

## sjson.encode()

Encode a Lua table to a JSON string. This is a convenience method provided for backwards compatibility with `cjson`.

####Syntax
`sjson.encode(table [, opts])`

####Parameters
- `table` data to encode
- `opts` an optional table of options. The possible entries are:
    - `depth` the maximum encoding depth needed to encode the table. The default is 20 which should be enough for nearly all situations.
    - `null` the string value to treat as null.

####Returns
JSON string

####Example
```lua
ok, json = pcall(sjson.encode, {key="value"})
if ok then
  print(json)
else
  print("failed to encode!")
end
```

## sjson.decoder()

This makes a decoder object that can parse a JSON encoded string into a lua object. A metatable can be specified for all the newly created lua tables. This allows
you to handle each value as it is inserted into each table (by implementing the `__newindex` method).

####Syntax
`sjson.decoder([opts])`

#### Parameters
- `opts` an optional table of options. The possible entries are:
    - `depth` the maximum encoding depth needed to encode the table. The default is 20 which should be enough for nearly all situations.
    - `null` the string value to treat as null.
    - `metatable` a table to use as the metatable for all the new tables in the returned object.

#### Returns
A `sjson.decoder` object

####Metatable

There are two principal methods that are invoked in the metatable (if it is present).

- `__newindex` this is the standard method invoked whenever a new table element is created.
- `checkpath` this is invoked (if defined) whenever a new table is created. It is invoked with two arguments:
    - `table` this is the newly created table
    - `path` this is a list of the keys from the root. 
    It must return `true` if this object is wanted in the result, or `false` otherwise.

For example, when decoding `{ "foo": [1, 2, []] }` the checkpath will be invoked as follows:

- `checkpath({}, {})` the `table` argument is the object that will correspond with the value of the JSON object.
- `checkpath({}, {"foo"})` the `table` argument is the object that will correspond with the value of the outer JSON array.
- `checkpath({}, {"foo", 3})` the `table` argument is the object that will correspond to the empty inner JSON array.

When the `checkpath` method is called, the metatable has already be associated with the new table. Thus the `checkpath` method can replace it
if desired. For example, if you are decoding `{ "foo": { "bar": [1,2,3,4], "cat": [5] } }` and, for some reason, you did not want to capture the
value of the `"bar"` key, then there are various ways to do this:

* In the `__newindex` metamethod, just check for the value of the key and skip the `rawset` if the key is `"bar"`. This only works if you want to skip all the 
`"bar"` keys.

* In the `checkpath` method, if the path is `["foo"]`, then return `false`.

* Use the following `checkpath`:  `checkpath=function(tab, path) tab['__json_path'] = path return true end` This will save the path in each constructed object. Now the `__newindex` method can perform more sophisticated filtering.

The reason for being able to filter is that it enables processing of very large JSON responses on a memory constrained platform. Many APIs return lots of information
which would exceed the memory budget of the platform. For example, `https://api.github.com/repos/nodemcu/nodemcu-firmware/contents` is over 13kB, and yet, if 
you only need the `download_url` keys, then the total size is around 600B. This can be handled with a simple `__newindex` method. 

## sjson.decoder:write

This provides more data to be parsed into the lua object.

####Syntax
`decoder:write(string)`

####Parameters

- `string` the next piece of JSON encoded data

####Returns
The constructed lua object or `nil` if the decode is not yet complete.

####Errors
If a parse error occurrs during this decode, then an error is thrown and the parse is aborted. The object cannot be used again.


## sjson.decoder:result

This gets the decoded lua object, or raises an error if the decode is not yet complete. This can be called multiple times and will return the 
same object each time.

####Syntax
`decoder:result()`

####Errors
If the decode is not complete, then an error is thrown.

####Example
```
local decoder = sjson.decoder()

decoder:write("[10, 1")
decoder:write("1")
decoder:write(", \"foo\"]")

for k,v in pairs(decoder:result()) do
   print (k, v)
end
```

The next example demonstrates the use of the metatable argument. In this case it just prints out the operations, but it could suppress the assignment
altogether if desired.

```
local decoder = sjson.decoder({metatable=
        {__newindex=function(t,k,v) print("Setting '" .. k .. "' = '" .. tostring(v) .."'") 
                                    rawset(t,k,v) end}})

decoder:write('[1, 2, {"foo":"bar"}]')

```


## sjson.decode()

Decode a JSON string to a Lua table. This is a convenience method provided for backwards compatibility with `cjson`.

####Syntax
`sjson.decode(str[, opts])`

####Parameters
- `str` JSON string to decode
- `opts` an optional table of options. The possible entries are:
    - `depth` the maximum encoding depth needed to encode the table. The default is 20 which should be enough for nearly all situations.
    - `null` the string value to treat as null.
    - `metatable` a table to use as the metatable for all the new tables in the returned object. See the metatable section in the description of `sjson.decoder()` above.

####Returns
Lua table representation of the JSON data

####Errors
If the string is not valid JSON, then an error is thrown.

####Example
```lua
t = sjson.decode('{"key":"value"}')
for k,v in pairs(t) do print(k,v) end
```

##Constants

There is one constant -- `sjson.NULL` -- which is used in lua structures to represent the presence of a JSON null.

