diff --git a/NEWS b/NEWS index 8927d6e..cd0f389 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +Version 2.1.1 (May 25 2013) +* Added decode_numbers_as_strings() + Version 2.1.0 (Mar 1 2012) * Added cjson.safe module interface which returns nil after an error * Improved Makefile compatibility with Solaris make diff --git a/README.asciidoc b/README.asciidoc new file mode 100644 index 0000000..61810d4 --- /dev/null +++ b/README.asciidoc @@ -0,0 +1,636 @@ += Lua CJSON 2.1devel Manual = +Mark Pulford +:revdate: 1st March 2012 + +Overview +-------- + +The Lua CJSON module provides JSON support for Lua. + +*Features*:: +- Fast, standards compliant encoding/parsing routines +- Full support for JSON with UTF-8, including decoding surrogate pairs +- Optional run-time support for common exceptions to the JSON + specification (infinity, NaN,..) +- No dependencies on other libraries + +*Caveats*:: +- UTF-16 and UTF-32 are not supported + +Lua CJSON is covered by the MIT license. Review the file +LICENSE+ for +details. + +The latest version of this software is available from the +http://www.kyne.com.au/%7Emark/software/lua-cjson.php[Lua CJSON website]. + +Feel free to email me if you have any patches, suggestions, or comments. + + +Installation +------------ + +Lua CJSON requires either http://www.lua.org[Lua] 5.1, Lua 5.2, or +http://www.luajit.org[LuaJIT] to build. + +The build method can be selected from 4 options: + +Make:: Unix (including Linux, BSD, Mac OSX & Solaris), Windows +CMake:: Unix, Windows +RPM:: Linux +LuaRocks:: Unix, Windows + + +Make +~~~~ + +The included +Makefile+ has generic settings. + +First, review and update the included makefile to suit your platform (if +required). + +Next, build and install the module: + +[source,sh] +make install + +Or install manually into your Lua module directory: + +[source,sh] +make +cp cjson.so $LUA_MODULE_DIRECTORY + + +CMake +~~~~~ + +http://www.cmake.org[CMake] can generate build configuration for many +different platforms (including Unix and Windows). + +First, generate the makefile for your platform using CMake. If CMake is +unable to find Lua, manually set the +LUA_DIR+ environment variable to +the base prefix of your Lua 5.1 installation. + +While +cmake+ is used in the example below, +ccmake+ or +cmake-gui+ may +be used to present an interface for changing the default build options. + +[source,sh] +mkdir build +cd build +# Optional: export LUA_DIR=$LUA51_PREFIX +cmake .. + +Next, build and install the module: + +[source,sh] +make install +# Or: +make +cp cjson.so $LUA_MODULE_DIRECTORY + +Review the +http://www.cmake.org/cmake/help/documentation.html[CMake documentation] +for further details. + + +RPM +~~~ + +Linux distributions using http://rpm.org[RPM] can create a package via +the included RPM spec file. Ensure the +rpm-build+ package (or similar) +has been installed. + +Build and install the module via RPM: + +[source,sh] +rpmbuild -tb lua-cjson-2.1devel.tar.gz +rpm -Uvh $LUA_CJSON_RPM + + +LuaRocks +~~~~~~~~ + +http://luarocks.org[LuaRocks] can be used to install and manage Lua +modules on a wide range of platforms (including Windows). + +First, extract the Lua CJSON source package. + +Next, install the module: + +[source,sh] +cd lua-cjson-2.1devel +luarocks make + +[NOTE] +LuaRocks does not support platform specific configuration for Solaris. +On Solaris, you may need to manually uncomment +USE_INTERNAL_ISINF+ in +the rockspec before building this module. + +Review the http://luarocks.org/en/Documentation[LuaRocks documentation] +for further details. + + +[[build_options]] +Build Options (#define) +~~~~~~~~~~~~~~~~~~~~~~~ + +Lua CJSON offers several +#define+ build options to address portability +issues, and enable non-default features. Some build methods may +automatically set platform specific options if required. Other features +should be enabled manually. + +USE_INTERNAL_ISINF:: Workaround for Solaris platforms missing +isinf+. +DISABLE_INVALID_NUMBERS:: Recommended on platforms where +strtod+ / + +sprintf+ are not POSIX compliant (eg, Windows MinGW). Prevents + +cjson.encode_invalid_numbers+ and +cjson.decode_invalid_numbers+ from + being enabled. However, +cjson.encode_invalid_numbers+ may still be + set to +"null"+. When using the Lua CJSON built-in floating point + conversion this option is unnecessary and is ignored. + + +Built-in floating point conversion +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Lua CJSON may be built with David Gay's +http://www.netlib.org/fp/[floating point conversion routines]. This can +increase overall performance by up to 50% on some platforms when +converting a large amount of numeric data. However, this option reduces +portability and is disabled by default. + +USE_INTERNAL_FPCONV:: Enable internal number conversion routines. +IEEE_BIG_ENDIAN:: Must be set on big endian architectures. +MULTIPLE_THREADS:: Must be set if Lua CJSON may be used in a + multi-threaded application. Requires the _pthreads_ library. + + +API (Functions) +--------------- + +Synopsis +~~~~~~~~ + +[source,lua] +------------ +-- Module instantiation +local cjson = require "cjson" +local cjson2 = cjson.new() +local cjson_safe = require "cjson.safe" + +-- Translate Lua value to/from JSON +text = cjson.encode(value) +value = cjson.decode(text) + +-- Get and/or set Lua CJSON configuration +setting = cjson.decode_invalid_numbers([setting]) +setting = cjson.encode_invalid_numbers([setting]) +keep = cjson.encode_keep_buffer([keep]) +depth = cjson.encode_max_depth([depth]) +depth = cjson.decode_max_depth([depth]) +convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]]) +------------ + + +Module Instantiation +~~~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +local cjson = require "cjson" +local cjson2 = cjson.new() +local cjson_safe = require "cjson.safe" +------------ + +Import Lua CJSON via the Lua +require+ function. Lua CJSON does not +register a global module table. + +The +cjson+ module will throw an error during JSON conversion if any +invalid data is encountered. Refer to <> +and <> for details. + +The +cjson.safe+ module behaves identically to the +cjson+ module, +except when errors are encountered during JSON conversion. On error, the ++cjson_safe.encode+ and +cjson_safe.decode+ functions will return ++nil+ followed by the error message. + ++cjson.new+ can be used to instantiate an independent copy of the Lua +CJSON module. The new module has a separate persistent encoding buffer, +and default settings. + +Lua CJSON can support Lua implementations using multiple preemptive +threads within a single Lua state provided the persistent encoding +buffer is not shared. This can be achieved by one of the following +methods: + +- Disabling the persistent encoding buffer with + <> +- Ensuring each thread calls <> separately (ie, + treat +cjson.encode+ as non-reentrant). +- Using a separate +cjson+ module table per preemptive thread + (+cjson.new+) + +[NOTE] +Lua CJSON uses +strtod+ and +snprintf+ to perform numeric conversion as +they are usually well supported, fast and bug free. However, these +functions require a workaround for JSON encoding/parsing under locales +using a comma decimal separator. Lua CJSON detects the current locale +during instantiation to determine and automatically implement the +workaround if required. Lua CJSON should be reinitialised via ++cjson.new+ if the locale of the current process changes. Using a +different locale per thread is not supported. + + +decode +~~~~~~ + +[source,lua] +------------ +value = cjson.decode(json_text) +------------ + ++cjson.decode+ will deserialise any UTF-8 JSON string into a Lua value +or table. + +UTF-16 and UTF-32 JSON strings are not supported. + ++cjson.decode+ requires that any NULL (ASCII 0) and double quote (ASCII +34) characters are escaped within strings. All escape codes will be +decoded and other bytes will be passed transparently. UTF-8 characters +are not validated during decoding and should be checked elsewhere if +required. + +JSON +null+ will be converted to a NULL +lightuserdata+ value. This can +be compared with +cjson.null+ for convenience. + +By default, numbers incompatible with the JSON specification (infinity, +NaN, hexadecimal) can be decoded. This default can be changed with +<>. + +.Example: Decoding +[source,lua] +json_text = '[ true, { "foo": "bar" } ]' +value = cjson.decode(json_text) +-- Returns: { true, { foo = "bar" } } + +[CAUTION] +Care must be taken after decoding JSON objects with numeric keys. Each +numeric key will be stored as a Lua +string+. Any subsequent code +assuming type +number+ may break. + + +[[decode_invalid_numbers]] +decode_invalid_numbers +~~~~~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +setting = cjson.decode_invalid_numbers([setting]) +-- "setting" must be a boolean. Default: true. +------------ + +Lua CJSON may generate an error when trying to decode numbers not +supported by the JSON specification. _Invalid numbers_ are defined as: + +- infinity +- NaN +- hexadecimal + +Available settings: + ++true+:: Accept and decode _invalid numbers_. This is the default + setting. ++false+:: Throw an error when _invalid numbers_ are encountered. + +The current setting is always returned, and is only updated when an +argument is provided. + +[[decode_numbers_as_strings]] +decode_numbers_as_strings +~~~~~~~~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +setting = cjson.decode_numbers_as_strings([setting]) +-- "setting" must be a boolean. Default: false. +------------ + +Lua CJSON will parse numbers as strings and return them as strings if +this setting is true. + +A typical use case may be for JSON with 64bit numbers and you don't want +to loose precision for those fields as they're converted to Lua_number +which is typically a double. + +Available settings: + ++true+:: Decode _numbers_ as a string. ++false+:: Decode _numbers_ as Lua numbers. This is the default setting. + +The current setting is always returned, and is only updated when an +argument is provided. + +[[decode_max_depth]] +decode_max_depth +~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +depth = cjson.decode_max_depth([depth]) +-- "depth" must be a positive integer. Default: 1000. +------------ + +Lua CJSON will generate an error when parsing deeply nested JSON once +the maximum array/object depth has been exceeded. This check prevents +unnecessarily complicated JSON from slowing down the application, or +crashing the application due to lack of process stack space. + +An error may be generated before the depth limit is hit if Lua is unable +to allocate more objects on the Lua stack. + +By default, Lua CJSON will reject JSON with arrays and/or objects nested +more than 1000 levels deep. + +The current setting is always returned, and is only updated when an +argument is provided. + + +[[encode]] +encode +~~~~~~ + +[source,lua] +------------ +json_text = cjson.encode(value) +------------ + ++cjson.encode+ will serialise a Lua value into a string containing the +JSON representation. + ++cjson.encode+ supports the following types: + +- +boolean+ +- +lightuserdata+ (NULL value only) +- +nil+ +- +number+ +- +string+ +- +table+ + +The remaining Lua types will generate an error: + +- +function+ +- +lightuserdata+ (non-NULL values) +- +thread+ +- +userdata+ + +By default, numbers are encoded with 14 significant digits. Refer to +<> for details. + +Lua CJSON will escape the following characters within each UTF-8 string: + +- Control characters (ASCII 0 - 31) +- Double quote (ASCII 34) +- Forward slash (ASCII 47) +- Blackslash (ASCII 92) +- Delete (ASCII 127) + +All other bytes are passed transparently. + +[CAUTION] +========= +Lua CJSON will successfully encode/decode binary strings, but this is +technically not supported by JSON and may not be compatible with other +JSON libraries. To ensure the output is valid JSON, applications should +ensure all Lua strings passed to +cjson.encode+ are UTF-8. + +Base64 is commonly used to encode binary data as the most efficient +encoding under UTF-8 can only reduce the encoded size by a further +~8%. Lua Base64 routines can be found in the +http://w3.impa.br/%7Ediego/software/luasocket/[LuaSocket] and +http://www.tecgraf.puc-rio.br/%7Elhf/ftp/lua/#lbase64[lbase64] packages. +========= + +Lua CJSON uses a heuristic to determine whether to encode a Lua table as +a JSON array or an object. A Lua table with only positive integer keys +of type +number+ will be encoded as a JSON array. All other tables will +be encoded as a JSON object. + +Lua CJSON does not use metamethods when serialising tables. + +- +rawget+ is used to iterate over Lua arrays +- +next+ is used to iterate over Lua objects + +Lua arrays with missing entries (_sparse arrays_) may optionally be +encoded in several different ways. Refer to +<> for details. + +JSON object keys are always strings. Hence +cjson.encode+ only supports +table keys which are type +number+ or +string+. All other types will +generate an error. + +[NOTE] +Standards compliant JSON must be encapsulated in either an object (+{}+) +or an array (+[]+). If strictly standards compliant JSON is desired, a +table must be passed to +cjson.encode+. + +By default, encoding the following Lua values will generate errors: + +- Numbers incompatible with the JSON specification (infinity, NaN) +- Tables nested more than 1000 levels deep +- Excessively sparse Lua arrays + +These defaults can be changed with: + +- <> +- <> +- <> + +.Example: Encoding +[source,lua] +value = { true, { foo = "bar" } } +json_text = cjson.encode(value) +-- Returns: '[true,{"foo":"bar"}]' + + +[[encode_invalid_numbers]] +encode_invalid_numbers +~~~~~~~~~~~~~~~~~~~~~~ +[source,lua] +------------ +setting = cjson.encode_invalid_numbers([setting]) +-- "setting" must a boolean or "null". Default: false. +------------ + +Lua CJSON may generate an error when encoding floating point numbers not +supported by the JSON specification (_invalid numbers_): + +- infinity +- NaN + +Available settings: + ++true+:: Allow _invalid numbers_ to be encoded using the Javascript + compatible values +NaN+ and +Infinity+. This will generate + non-standard JSON, but these values are supported by some libraries. ++"null"+:: Encode _invalid numbers_ as a JSON +null+ value. This allows + infinity and NaN to be encoded into valid JSON. ++false+:: Throw an error when attempting to encode _invalid numbers_. + This is the default setting. + +The current setting is always returned, and is only updated when an +argument is provided. + + +[[encode_keep_buffer]] +encode_keep_buffer +~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +keep = cjson.encode_keep_buffer([keep]) +-- "keep" must be a boolean. Default: true. +------------ + +Lua CJSON can reuse the JSON encoding buffer to improve performance. + +Available settings: + ++true+:: The buffer will grow to the largest size required and is not + freed until the Lua CJSON module is garbage collected. This is the + default setting. ++false+:: Free the encode buffer after each call to +cjson.encode+. + +The current setting is always returned, and is only updated when an +argument is provided. + + +[[encode_max_depth]] +encode_max_depth +~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +depth = cjson.encode_max_depth([depth]) +-- "depth" must be a positive integer. Default: 1000. +------------ + +Once the maximum table depth has been exceeded Lua CJSON will generate +an error. This prevents a deeply nested or recursive data structure from +crashing the application. + +By default, Lua CJSON will generate an error when trying to encode data +structures with more than 1000 nested tables. + +The current setting is always returned, and is only updated when an +argument is provided. + +.Example: Recursive Lua table +[source,lua] +a = {}; a[1] = a + + +[[encode_number_precision]] +encode_number_precision +~~~~~~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +precision = cjson.encode_number_precision([precision]) +-- "precision" must be an integer between 1 and 14. Default: 14. +------------ + +The amount of significant digits returned by Lua CJSON when encoding +numbers can be changed to balance accuracy versus performance. For data +structures containing many numbers, setting ++cjson.encode_number_precision+ to a smaller integer, for example +3+, +can improve encoding performance by up to 50%. + +By default, Lua CJSON will output 14 significant digits when converting +a number to text. + +The current setting is always returned, and is only updated when an +argument is provided. + + +[[encode_sparse_array]] +encode_sparse_array +~~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]]) +-- "convert" must be a boolean. Default: false. +-- "ratio" must be a positive integer. Default: 2. +-- "safe" must be a positive integer. Default: 10. +------------ + +Lua CJSON classifies a Lua table into one of three kinds when encoding a +JSON array. This is determined by the number of values missing from the +Lua array as follows: + +Normal:: All values are available. +Sparse:: At least 1 value is missing. +Excessively sparse:: The number of values missing exceeds the configured + ratio. + +Lua CJSON encodes sparse Lua arrays as JSON arrays using JSON +null+ for +the missing entries. + +An array is excessively sparse when all the following conditions are +met: + +- +ratio+ > +0+ +- _maximum_index_ > +safe+ +- _maximum_index_ > _item_count_ * +ratio+ + +Lua CJSON will never consider an array to be _excessively sparse_ when ++ratio+ = +0+. The +safe+ limit ensures that small Lua arrays are always +encoded as sparse arrays. + +By default, attempting to encode an _excessively sparse_ array will +generate an error. If +convert+ is set to +true+, _excessively sparse_ +arrays will be converted to a JSON object. + +The current settings are always returned. A particular setting is only +changed when the argument is provided (non-++nil++). + +.Example: Encoding a sparse array +[source,lua] +cjson.encode({ [3] = "data" }) +-- Returns: '[null,null,"data"]' + +.Example: Enabling conversion to a JSON object +[source,lua] +cjson.encode_sparse_array(true) +cjson.encode({ [1000] = "excessively sparse" }) +-- Returns: '{"1000":"excessively sparse"}' + + +API (Variables) +--------------- + +_NAME +~~~~~ + +The name of the Lua CJSON module (+"cjson"+). + + +_VERSION +~~~~~~~~ + +The version number of the Lua CJSON module (+"2.1devel"+). + + +null +~~~~ + +Lua CJSON decodes JSON +null+ as a Lua +lightuserdata+ NULL pointer. ++cjson.null+ is provided for comparison. + + +[sect1] +References +---------- + +- http://tools.ietf.org/html/rfc4627[RFC 4627] +- http://www.json.org/[JSON website] + + +// vi:ft=asciidoc tw=72: diff --git a/lua_cjson.c b/lua_cjson.c index c14a1c5..fc2ee33 100644 --- a/lua_cjson.c +++ b/lua_cjson.c @@ -51,7 +51,7 @@ #endif #ifndef CJSON_VERSION -#define CJSON_VERSION "2.1devel" +#define CJSON_VERSION "2.1.1" #endif /* Workaround for Solaris platforms missing isinf() */ @@ -74,6 +74,8 @@ #define DEFAULT_DECODE_INVALID_NUMBERS 0 #endif +#define DEFAULT_DECODE_NUMBERS_AS_STRINGS 0 + typedef enum { T_OBJ_BEGIN, T_OBJ_END, @@ -126,6 +128,7 @@ typedef struct { int encode_keep_buffer; int decode_invalid_numbers; + int decode_numbers_as_strings; int decode_max_depth; } json_config_t; @@ -356,6 +359,15 @@ static int json_cfg_decode_invalid_numbers(lua_State *l) return 1; } +static int json_cfg_decode_numbers_as_strings(lua_State *l) +{ + json_config_t *cfg = json_arg_init(l, 1); + + json_enum_option(l, 1, &cfg->decode_numbers_as_strings, NULL, 1); + + return 1; +} + static int json_destroy_config(lua_State *l) { json_config_t *cfg; @@ -388,6 +400,7 @@ static void json_create_config(lua_State *l) cfg->decode_max_depth = DEFAULT_DECODE_MAX_DEPTH; cfg->encode_invalid_numbers = DEFAULT_ENCODE_INVALID_NUMBERS; cfg->decode_invalid_numbers = DEFAULT_DECODE_INVALID_NUMBERS; + cfg->decode_numbers_as_strings = DEFAULT_DECODE_NUMBERS_AS_STRINGS; cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER; cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION; @@ -592,20 +605,12 @@ static void json_append_number(lua_State *l, json_config_t *cfg, if (cfg->encode_invalid_numbers == 0) { /* Prevent encoding invalid numbers */ if (isinf(num) || isnan(num)) - json_encode_exception(l, cfg, json, lindex, - "must not be NaN or Infinity"); + json_encode_exception(l, cfg, json, lindex, "must not be NaN or Inf"); } else if (cfg->encode_invalid_numbers == 1) { - /* Encode NaN/Infinity separately to ensure Javascript compatible - * values are used. */ + /* Encode invalid numbers, but handle "nan" separately + * since some platforms may encode as "-nan". */ if (isnan(num)) { - strbuf_append_mem(json, "NaN", 3); - return; - } - if (isinf(num)) { - if (num < 0) - strbuf_append_mem(json, "-Infinity", 9); - else - strbuf_append_mem(json, "Infinity", 8); + strbuf_append_mem(json, "nan", 3); return; } } else { @@ -1013,6 +1018,33 @@ static void json_next_number_token(json_parse_t *json, json_token_t *token) return; } +static void json_next_number_as_string_token(json_parse_t *json, json_token_t *token) +{ + char ch; + + /* json->tmp is the temporary strbuf used to accumulate the + * decoded string value. + * json->tmp is sized to handle JSON containing only a string value. + */ + strbuf_reset(json->tmp); + + while (((ch = *json->ptr) >= '0' && ch <= '9') || ch == '-' || ch == '+' || ch == '.') { + if (!ch) { + /* Premature end of the string */ + json_set_token_error(token, json, "unexpected end of string"); + return; + } + + strbuf_append_char_unsafe(json->tmp, ch); + json->ptr++; + } + + strbuf_ensure_null(json->tmp); + + token->type = T_STRING; + token->value.string = strbuf_string(json->tmp, &token->string_len); +} + /* Fills in the token struct. * T_STRING will return a pointer to the json_parse_t temporary string * T_ERROR will leave the json->ptr pointer at the error. @@ -1065,7 +1097,10 @@ static void json_next_token(json_parse_t *json, json_token_t *token) json_set_token_error(token, json, "invalid number"); return; } - json_next_number_token(json, token); + if (json->cfg->decode_numbers_as_strings) + json_next_number_as_string_token(json, token); + else + json_next_number_token(json, token); return; } else if (!strncmp(json->ptr, "true", 4)) { token->type = T_BOOLEAN; @@ -1359,6 +1394,7 @@ static int lua_cjson_new(lua_State *l) { "encode_keep_buffer", json_cfg_encode_keep_buffer }, { "encode_invalid_numbers", json_cfg_encode_invalid_numbers }, { "decode_invalid_numbers", json_cfg_decode_invalid_numbers }, + { "decode_numbers_as_strings", json_cfg_decode_numbers_as_strings }, { "new", lua_cjson_new }, { NULL, NULL } }; diff --git a/manual.txt b/manual.txt index a12e378..61810d4 100644 --- a/manual.txt +++ b/manual.txt @@ -302,6 +302,30 @@ Available settings: The current setting is always returned, and is only updated when an argument is provided. +[[decode_numbers_as_strings]] +decode_numbers_as_strings +~~~~~~~~~~~~~~~~~~~~~~~~~ + +[source,lua] +------------ +setting = cjson.decode_numbers_as_strings([setting]) +-- "setting" must be a boolean. Default: false. +------------ + +Lua CJSON will parse numbers as strings and return them as strings if +this setting is true. + +A typical use case may be for JSON with 64bit numbers and you don't want +to loose precision for those fields as they're converted to Lua_number +which is typically a double. + +Available settings: + ++true+:: Decode _numbers_ as a string. ++false+:: Decode _numbers_ as Lua numbers. This is the default setting. + +The current setting is always returned, and is only updated when an +argument is provided. [[decode_max_depth]] decode_max_depth