JSON (JavaScript? Object Notation) is a format for serializing data based on the format for JavaScript? data structures. For more informations about JSON in general see http://www.json.org/ and [the Wikipedia article on JSON].
A comparison of JSON modules
Since some modules were only called "json" initials were added to create unique names:
- cmj-JSON4Lua (version 0.9.5):
- dkjson (version 2.1):
- Fleece (version 0.3.1):
- jf-JSON (version 20160526.15)
- Lua-Yajl (version 2.0):
- http://github.com/brimworks/lua-yajl
- C-module, requires [Yajl] (used version 2.0.1 at the time of testing).
- Can be used for streaming by writing handlers that deal with JSON on the fly.
- Lua-Yail uses the MIT/X11 license, Yajl itself uses the ISC license.
- mp-CJSON / Lua CJSON (version 1.0.2):
- sb-Json (2007 version):
- th-LuaJSON (version 1.2.1):
- luci.json (version 0.11+)
- lunajson (version 1.1) (tested individually by the author)
This comparison was initially started by DavidKolf, author of dkjson, so it might be biased.
For comparison the default options were used. I (DavidKolf) expect a module to be JSON-compliant with the default settings. An exception was made for Fleece, most of this comparison wouldn't make sense for anything other than the option "E4".
The [feature/compliance tests] and the [speed test] can be found in the repository for dkjson.
Further JSON implementations
JSON implementations that are not included in this comparison (yet):
Major bugs
- Fleece:
- Encoding +/-math.huge (Infinity) freezes the program (endless loop).
- Encoding UTF-8 codes for codepoints >= 128 cancels further output.
- Reference cycles cause segmentation faults.
Escaped characters when encoding
According to [RFC 4627] all characters in the range 0 to 31 plus \\ and \" have to be escaped. However, it is recommended to also escape the Unicode characters U+2028 and U+2029 in order to [generate valid JavaScript as well]. The [original JSON implementation] went even further and escaped most control characters and undefined codepoints in the BMP.
- cmj-JSON4Lua: \\, \", \n, \t plus \' which isn't a valid JSON escape code. JSON files containing this would not load in the original implementation.
- dkjson: All characters in the range 0 to 31, \\, \", 127 and the other Unicode control characters that are escaped by the original implementation.
- Fleece (with the E4 option): All characters in the range 0 to 31, \\, \", \/, 127. All characters above 127 cancel further output.
- jf-JSON: All characters in the range 0 to 31, \\, \". Optionally also escapes U+2028 and U+2029.
- Lua-Yajl: All characters in the range 0 to 31, \\, \".
- mp-CJSON: All characters in the range 0 to 31, \\, \", \/, 127.
- sb-Json: All characters in the range 0 to 31, \\, \", \/, 127.
- th-LuaJSON: All characters in the range 0 to 31, \\, \", \/. \u000b (11) is escaped as \v, which isn't valid JSON code and would not load in the original implementation.
- lunajson: All characters in the range 0 to 31, \\, \".
Support for unicode escape sequences \uXXXX when decoding
- cmj-JSON4Lua: no support
- dkjson: full support from 0 to 0x10ffff, correctly converts [UTF-16] surrogate pairs to [UTF-8].
- jf-JSON: full support from 0 to 0x10ffff, correctly converts UTF-16 surrogate pairs to UTF-8.
- Lua-Yajl: full support from 0 to 0x10ffff, correctly converts UTF-16 surrogate pairs to UTF-8.
- mp-CJSON: full support from 0 to 0x10ffff, correctly converts UTF-16 surrogate pairs to UTF-8.
- sb-Json: support in the range 0 to 0xff, creates 8-bit encoding (latin1 or similar).
- th-LuaJSON: support in the range 0 to 0xffff, but doesn't convert UTF-16 surrogate pairs, so 0x10000 to 0x10ffff would be encoded as [CESU-8] instead of UTF-8.
- lunajson: full support from 0 to 0x10ffff, correctly converts UTF-16 surrogate pairs to UTF-8.
Other bugs
- cmj-JSON4Lua:
- Decoding an empty array raises an error.
- Decoding a number with a plus sign in the exponential part raises an error ("1e+2").
Edge cases for encoding
JSON cannot represent every Lua structure. This section lists the behavior for some cases. You shouldn't feed any data like this to any JSON module but it's still interesting how the modules handle this. A module must not hang or crash and it should not produce invalid JSON output. (Raising Lua errors is a valid way to deal with such data).
Mixed tables
{[1] = 1, a = 2}
Sparse arrays
{[1000] = "test"}
- cmj-JSON4Lua: encoded as a big array.
- dkjson: encoded as an object with string keys.
- Fleece: encoded as an object with integer keys (invalid JSON).
- jf-JSON: encoded as a big array.
- Lua-Yajl: encoded as a big array.
- mp-CJSON: raises an error for excessively sparse arrays and converts other sparse arrays to an object with string keys. Behaviour is runtime configurable.
- sb-Json: encoded as a big array.
- th-LuaJSON: encoded as an object with string keys.
- luci.json: encoded as an object with string keys.
- lunajson: raises an error unless array length is explicitly specified (array length is specified by index 0, e.g. {[0] = 1000, [1000] = "test"}).
Handling of NaN and Inf
nan = math.huge * 0
inf = math.huge
- cmj-JSON4Lua: raw tostring() output (invalid JSON).
- dkjson: 'null' (like in the original JSON-implementation).
- Fleece: NaN is 0.0000, freezes on +/-Inf.
- jf-JSON: NaN is 'null', Inf is 1e+9999.
- Lua-Yajl: NaN is -0, Inf is 1e+666.
- mp-CJSON: raises invalid JSON error by default, but runtime configurable ('null' or Nan/Inf).
- sb-Json: raw tostring() output (invalid JSON).
- th-LuaJSON: JavaScript? constants: NaN is 'NaN', Inf is 'Infinity' (this is valid JavaScript?, but invalid JSON).
- luci.json: NaN is nan, inf is inf.
- lunajson: raises an error (invalid number).
Protection against reference cycles when encoding
a = {}
a.a = a
- cmj-JSON4Lua: no, endless loop.
- dkjson: yes, raises an error.
- Fleece: no, segmentation fault.
- jf-JSON: yes, raises an error.
- Lua-Yajl: yes, raises an error.
- mp-CJSON: yes, raises an error. limit is runtime configurable.
- sb-Json: no, endless loop.
- th-LuaJSON: yes, raises an error.
- luci.json: yes, returns nil, but takes "a while" to do so (spins CPU)
- lunajson: yes, raises an error
Handling of empty arrays
local json_str = '{"items":[],"properties":{}}'
assert(json_str == json.encode(json.decode(json_str)))
- cmj-JSON4Lua: does not work correctly (empty arrays are encoded as simple empty tables)
- dkjson: works correctly (uses a "type field" on the metatable)
- Fleece: not tested
- jf-JSON: optionally works correctly; by default empty tables are always encoded as empty objects
- Lua-Yajl: does not work correctly (empty tables are encoded as empty arrays, always)
- mp-CJSON: does not work correctly (empty tables are encoded as empty objects, always)
- sb-Json: not tested
- th-LuaJSON: (v1.3) works (assigns a special metatable to arrays)
- luci.json: decodes to nil, empty tables are encoded as empty arrays, always
- lunajson: works correctly if the decoder is passed the option to embed array length.
Encoding speed
- cmj-JSON4Lua: 12s
- dkjson: 16s
- Fleece: 0.5s
- jf-JSON: 20s
- Lua-Yajl: 5.9s
- mp-CJSON: 1s
- sb-Json: 22s
- th-LuaJSON: 30s
Decoding speed
- cmj-JSON4Lua: 47s
- dkjson: 28s without LPeg, 8s with LPeg
- jf-JSON: 30s
- Lua-Yajl: 4s
- mp-CJSON: 2s
- sb-Json: 122s
- th-LuaJSON: 9s (version 1.2.2 takes 347s)
For the speed tests a table was encoded or decoded 100000 times. The values are not precise and might differ when using other data, but they should give a general orientation.
FIXME - difficult to run new tests here without knowing what the tables were?
See Also
- [JSONRPC4Lua] (5.0/5.1) - JSON-RPC-over-http client (or server in a CGILua environment) for Lua.
RecentChanges · preferences
edit · history
Last edited August 5, 2018 6:27 am GMT (diff)