├── CMakeLists.txt ├── LICENSE ├── Makefile ├── NEWS ├── README ├── THANKS ├── TODO ├── dist.cmake ├── dist.info ├── lua-cjson-1.0.3-1.rockspec ├── lua-cjson.spec ├── lua_cjson.c ├── performance.txt ├── rfc4627.txt ├── strbuf.c ├── strbuf.h └── tests ├── README ├── bench.lua ├── common.lua ├── decode.lua ├── encode.lua ├── example1.json ├── example2.json ├── example3.json ├── example4.json ├── example5.json ├── genutf8.pl ├── numbers.json ├── octets-escaped.dat ├── rfc-example1.json ├── rfc-example2.json ├── test.lua └── types.json /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2007-2011 LuaDist. 2 | # Created by Peter Kapec 3 | # Redistribution and use of this file is allowed according to the terms of the MIT license. 4 | # For details see the COPYRIGHT file distributed with LuaDist. 5 | # Please note that the package source code is licensed under its own license. 6 | 7 | project ( lua-cjson C ) 8 | cmake_minimum_required ( VERSION 2.6 ) 9 | include ( dist.cmake ) 10 | 11 | 12 | # lua-cjson modules 13 | add_definitions ( -DVERSION="1.0.3" ) 14 | install_lua_module( cjson lua_cjson.c strbuf.c ) 15 | 16 | 17 | # Install Lua-CJSON Documentation 18 | install_data( README NEWS performance.txt rfc4627.txt ) 19 | 20 | # Install Tests 21 | install_test( tests/ ) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2011 Mark Pulford 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CJSON_VERSION = 1.0.3 2 | LUA_VERSION = 5.1 3 | 4 | # See http://lua-users.org/wiki/BuildingModules for platform specific 5 | # details. 6 | 7 | ## Linux/BSD 8 | PREFIX ?= /usr/local 9 | LDFLAGS += -shared 10 | 11 | ## OSX (Macports) 12 | #PREFIX ?= /opt/local 13 | #LDFLAGS += -bundle -undefined dynamic_lookup 14 | 15 | LUA_INCLUDE_DIR ?= $(PREFIX)/include 16 | LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) 17 | 18 | # Some versions of Solaris are missing isinf(). Add -DMISSING_ISINF to 19 | # CFLAGS to work around this bug. 20 | 21 | #CFLAGS ?= -g -Wall -pedantic -fno-inline 22 | CFLAGS ?= -g -O3 -Wall -pedantic 23 | override CFLAGS += -fpic -I$(LUA_INCLUDE_DIR) -DVERSION=\"$(CJSON_VERSION)\" 24 | 25 | INSTALL ?= install 26 | 27 | .PHONY: all clean install package 28 | 29 | all: cjson.so 30 | 31 | cjson.so: lua_cjson.o strbuf.o 32 | $(CC) $(LDFLAGS) -o $@ $^ 33 | 34 | install: 35 | $(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR) 36 | $(INSTALL) cjson.so $(DESTDIR)/$(LUA_LIB_DIR) 37 | 38 | clean: 39 | rm -f *.o *.so 40 | 41 | package: 42 | git archive --prefix="lua-cjson-$(CJSON_VERSION)/" master | \ 43 | gzip -9 > "lua-cjson-$(CJSON_VERSION).tar.gz" 44 | git archive --prefix="lua-cjson-$(CJSON_VERSION)/" \ 45 | -o "lua-cjson-$(CJSON_VERSION).zip" master 46 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | Version 1.0.3 (Sep 15 2011) 2 | * Fixed detection of objects with numeric string keys 3 | * Provided work around for missing isinf() on Solaris 4 | 5 | Version 1.0.2 (May 30 2011) 6 | * Portability improvements for Windows 7 | - No longer links with -lm 8 | - Use "socket" instead of "posix" for sub-second timing 9 | * Removed UTF-8 test dependency on Perl Text::Iconv 10 | * Added simple CLI commands for testing Lua <-> JSON conversions 11 | * Added cjson.encode_number_precision() 12 | 13 | Version 1.0.1 (May 10 2011) 14 | * Added build support for OSX 15 | * Removed unnecessary whitespace from JSON output 16 | * Added cjson.encode_keep_buffer() 17 | * Fixed memory leak on Lua stack overflow exception 18 | 19 | Version 1.0 (May 9 2011) 20 | * Initial release 21 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Lua CJSON v1.0.3 2 | ================ 3 | 4 | Lua CJSON is covered by the MIT license. See the file "LICENSE" for 5 | details. 6 | 7 | Lua CJSON provides fast JSON parsing and encoding support for Lua. 8 | 9 | Features: 10 | - 10x to 20x quicker (or more) than the fastest pure Lua JSON modules. 11 | - Full support for JSON with UTF-8, including decoding surrogate 12 | pairs. 13 | - Optional run-time support for common exceptions to the JSON 14 | specification (NaN, Inf,..). 15 | 16 | Caveats: 17 | - UTF-16 and UTF-32 are not supported. 18 | - Multiple OS threads within a single Lua state are not currently 19 | supported. However, this is an extremely uncommon configuration due 20 | to performance limitations. 21 | 22 | To obtain the latest version of Lua CJSON visit: 23 | 24 | http://www.kyne.com.au/~mark/software/lua-cjson.php 25 | 26 | Feel free to email me if you have any patches, suggestions, or 27 | comments. 28 | 29 | - Mark Pulford 30 | 31 | 32 | Installing 33 | ========== 34 | 35 | Build requirements: 36 | - Lua (http://www.lua.org/) 37 | Or: 38 | - LuaJIT (http://www.luajit.org/) 39 | 40 | There are 3 build methods available: 41 | - Gmake: POSIX, OSX 42 | - RPM: Various Linux distributions 43 | - LuaRocks (http://www.luarocks.org/): POSIX, OSX, Windows 44 | 45 | 46 | Gmake 47 | ----- 48 | 49 | Review and update the included Makefile to suit your platform. Next, 50 | build and install the module: 51 | 52 | # gmake 53 | # gmake install 54 | OR 55 | # cp cjson.so [your_module_directory] 56 | 57 | Note: Some Solaris platforms are missing isinf(). You can work around 58 | this bug by adding -DMISSING_ISINF to CFLAGS in the Makefile. 59 | 60 | Note: For Lua 5.2. You may got a 'undefined symbol: luaL_register' error. 61 | You can adding -DLUA_COMPAT_MODULE to CFLAGS in the Makefile to fix it. 62 | 63 | RPM 64 | --- 65 | 66 | RPM-based Linux distributions should be able to create a package using 67 | the included RPM spec file. Install the "rpm-build" package, or 68 | similar, then: 69 | 70 | # rpmbuild -tb lua-cjson-1.0.3.tar.gz 71 | 72 | 73 | LuaRocks 74 | -------- 75 | 76 | LuaRocks (http://luarocks.org/) can be used to install and manage Lua 77 | modules on a wide range of platforms (including Windows). 78 | 79 | Extract the Lua CJSON source package into a directory and run: 80 | 81 | # cd lua-cjson-1.0.3; luarocks make 82 | 83 | See the LuaRocks documentation for further details. 84 | 85 | 86 | Lua CJSON API 87 | ============= 88 | 89 | Synopsis 90 | -------- 91 | 92 | require "cjson" 93 | -- Or: 94 | local cjson = require "cjson" 95 | 96 | -- Translate Lua value to/from JSON 97 | text = cjson.encode(value) 98 | value = cjson.decode(text) 99 | 100 | -- Get and/or set CJSON configuration 101 | setting = cjson.refuse_invalid_numbers([setting]) 102 | depth = cjson.encode_max_depth([depth]) 103 | convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]]) 104 | keep = cjson.encode_keep_buffer([keep]) 105 | 106 | 107 | Encoding 108 | -------- 109 | 110 | json_text = cjson.encode(value) 111 | 112 | cjson.encode() will serialise the following types: 113 | * number, string, table, boolean, lightuserdata (NULL) or nil 114 | 115 | The remaining Lua types cannot be serialised: 116 | * thread, userdata, lightuserdata (non-NULL), function 117 | 118 | Numbers are encoded using the standard Lua number format. 119 | 120 | ASCII 0 - 31, double-quote, forward-slash, black-slash and ASCII 127 121 | are escaped when encoding strings. Other octets are passed 122 | transparently. It is expected the application will perform UTF-8 error 123 | checking if required. 124 | 125 | A Lua table will only be recognised as an array if all keys are type 126 | "number" and are positive integers (>0). Otherwise CJSON will encode 127 | the table as a JSON object. 128 | 129 | CJSON will also recognise and handle sparse arrays. Missing entries 130 | will be encoded as "null". Eg: 131 | { [3] = "data" } 132 | becomes: 133 | [null,null,"data"] 134 | 135 | Note: standards compliant JSON must be encapsulated in either an 136 | object ({}) or an array ([]). You must pass a table to cjson.encode() 137 | if you want to generate standards compliant JSON output. 138 | 139 | By default, errors will be raised for: 140 | - Excessively sparse arrays (see below) 141 | - More than 20 nested tables 142 | - Invalid numbers (NaN, Infinity) 143 | 144 | These defaults can be changed with: 145 | - cjson.encode_sparse_array() 146 | - cjson.encode_max_depth() 147 | - cjson.refuse_invalid_numbers() 148 | 149 | Example: 150 | data_obj = { true, { foo = "bar" } } 151 | data_json = cjson.encode(data_obj) 152 | 153 | 154 | Decoding 155 | -------- 156 | 157 | value = cjson.decode(json_text) 158 | 159 | cjson.decode() will deserialise any UTF-8 JSON string into a Lua data 160 | structure. It can return any of the types that cjson.encode() 161 | supports. 162 | 163 | UTF-16 and UTF-32 JSON strings are not supported. 164 | 165 | CJSON requires that NULL (\0) and double quote (\") are escaped within 166 | strings. All escape codes will be decoded and other characters will be 167 | passed transparently. UTF-8 characters are not validated during 168 | decoding and should be checked elsewhere if required. 169 | 170 | JSON "null" will be converted to a NULL lightuserdata value. This can 171 | be compared with cjson.null for convenience. 172 | 173 | By default, invalid numbers (NaN, Infinity, Hexidecimal) will be 174 | decoded. 175 | 176 | Example: 177 | data_json = '[ true, { "foo": "bar" } ]' 178 | data_obj = cjson.decode(data_json) 179 | 180 | 181 | Invalid numbers 182 | --------------- 183 | 184 | setting = cjson.refuse_invalid_numbers([setting]) 185 | -- "setting" must be on of: 186 | -- false, "encode", "decode", "both", true 187 | 188 | CJSON considers numbers which are outside the JSON specification to be 189 | "invalid". Eg: 190 | - Infinity 191 | - NaN 192 | - Hexadecimal numbers 193 | 194 | By default CJSON will decode "invalid" numbers, but will refuse to 195 | encode them. 196 | 197 | This setting can be configured separately for encoding and/or 198 | decoding: 199 | - Enabled: an error will be generated if an invalid number is found. 200 | - Disabled (encoding): NaN and Infinity can be encoded. 201 | - Disabled (decoding): All numbers supported by strtod(3) will be 202 | parsed. 203 | 204 | 205 | Sparse arrays 206 | ------------- 207 | 208 | convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]]) 209 | -- "convert" must be a boolean. Default: false. 210 | -- "ratio" must be a positive integer (>0). Default: 2 211 | -- "safe" must be a positive integer (>0). Default: 10 212 | 213 | A Lua array is sparse if it is missing a value for at least 1 index. 214 | Lua CJSON encodes missing values as "null". Eg: 215 | Lua array: { [3] = "sparse" } 216 | JSON array: [null,null,"sparse"] 217 | 218 | CJSON detects excessively sparse arrays by comparing the number of 219 | items in a Lua array with the maximum index. In particular: 220 | 221 | maximum index > safe AND maximum index > array_items * ratio 222 | 223 | By default, attempting to encode excessively sparse arrays will 224 | generate an error. 225 | 226 | If "convert" is set to "true", excessively sparse arrays will be 227 | encoded as a JSON object: 228 | Lua array: { [1000] = "excessively sparse" } 229 | JSON array: {"1000":"excessively sparse"} 230 | 231 | Setting "ratio" to 0 disables checking for excessively sparse arrays. 232 | 233 | 234 | Nested tables 235 | ------------- 236 | 237 | depth = cjson.encode_max_depth([depth]) 238 | -- "depth" must be a positive integer (>0). 239 | 240 | By default, CJSON will reject data structure with more than 20 nested 241 | tables. 242 | 243 | This check is used to prevent a nested data structure from crashing 244 | the application. Eg: 245 | a = {}; b = { a }; a[1] = b 246 | 247 | 248 | Number precision 249 | ---------------- 250 | 251 | precision = cjson.encode_number_precision([precision]) 252 | -- "precision" must be between 1 and 14 (inclusive) 253 | 254 | By default CJSON will output 14 significant digits when converting a 255 | number to text. 256 | 257 | Reducing number precision to 3 can improve performance of number 258 | heavy conversions by up to 50%. 259 | 260 | 261 | Persistent encoding buffer 262 | -------------------------- 263 | 264 | keep = cjson.keep_encode_buffer([keep]) 265 | -- "keep" must be a boolean 266 | 267 | By default, CJSON will reuse the JSON encoding buffer to improve 268 | performance. The buffer will grow to the largest size required and is 269 | not freed until CJSON is garbage collected. Setting this option to 270 | "false" will cause the buffer to be freed after each call to 271 | cjson.encode(). 272 | 273 | 274 | Lua / JSON limitations and CJSON 275 | ================================ 276 | 277 | Null handling 278 | ------------- 279 | 280 | Lua CJSON decodes JSON "null" as a Lua lightuserdata NULL pointer. 281 | 282 | CJSON provides "cjson.null" as a convenience for comparison. 283 | 284 | 285 | Table keys 286 | ---------- 287 | 288 | JSON object keys must be strings - other types are not supported. Lua 289 | CJSON will convert numeric keys to a string, and other non-string 290 | types will generate an error. 291 | 292 | JSON object keys are always be decoded as Lua strings. 293 | 294 | If all Lua table keys are numbers (not strings), Lua CJSON will 295 | encode the table as a JSON array. See "Sparse arrays" above for 296 | more details. 297 | 298 | 299 | Metamethods 300 | ----------- 301 | 302 | Lua CJSON does not use metamethods when serialising tables. 303 | - next() is used to iterate over tables. 304 | - rawget() is used when iterating over arrays. 305 | 306 | 307 | Functions, Userdata, Threads 308 | ---------------------------- 309 | 310 | Lua CJSON will generate an error if asked to serialise Lua functions, 311 | userdata, lightuserdata or threads. 312 | 313 | 314 | References 315 | ========== 316 | 317 | - http://tools.ietf.org/html/rfc4627 318 | - http://www.json.org/ 319 | -------------------------------------------------------------------------------- /THANKS: -------------------------------------------------------------------------------- 1 | The following people have helped with bug reports, testing and/or 2 | suggestions: 3 | 4 | - Louis-Philippe Perron (@loopole) 5 | - Steve Donovan 6 | - Zhang "agentzh" Yichun 7 | 8 | Thanks! 9 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - Make encode/decode routines OS thread safe (within the same lua_State) 2 | - Optionally create an object for settings. Clone function. 3 | 4 | - Convert documentation into structured source format 5 | -------------------------------------------------------------------------------- /dist.cmake: -------------------------------------------------------------------------------- 1 | # LuaDist CMake utility library. 2 | # Provides variables and utility functions common to LuaDist CMake builds. 3 | # 4 | # Copyright (C) 2007-2011 LuaDist. 5 | # by David Manura, Peter Drahos 6 | # Redistribution and use of this file is allowed according to the terms of the MIT license. 7 | # For details see the COPYRIGHT file distributed with LuaDist. 8 | # Please note that the package source code is licensed under its own license. 9 | 10 | ## INSTALL DEFAULTS (Relative to CMAKE_INSTALL_PREFIX) 11 | # Primary paths 12 | set ( INSTALL_BIN bin CACHE PATH "Where to install binaries to." ) 13 | set ( INSTALL_LIB lib CACHE PATH "Where to install libraries to." ) 14 | set ( INSTALL_INC include CACHE PATH "Where to install headers to." ) 15 | set ( INSTALL_ETC etc CACHE PATH "Where to store configuration files" ) 16 | set ( INSTALL_LMOD ${INSTALL_LIB}/lua CACHE PATH "Directory to install Lua modules." ) 17 | set ( INSTALL_CMOD ${INSTALL_LIB}/lua CACHE PATH "Directory to install Lua binary modules." ) 18 | set ( INSTALL_SHARE share CACHE PATH "Directory for shared data." ) 19 | 20 | # Secondary paths 21 | set ( INSTALL_DATA ${INSTALL_SHARE}/${PROJECT_NAME} CACHE PATH "Directory the package can store documentation, tests or other data in.") 22 | set ( INSTALL_DOC ${INSTALL_DATA}/doc CACHE PATH "Recommended directory to install documentation into.") 23 | set ( INSTALL_EXAMPLE ${INSTALL_DATA}/example CACHE PATH "Recommended directory to install examples into.") 24 | set ( INSTALL_TEST ${INSTALL_DATA}/test CACHE PATH "Recommended directory to install tests into.") 25 | set ( INSTALL_FOO ${INSTALL_DATA}/etc CACHE PATH "Where to install additional files") 26 | 27 | # Skipable content, headers, binaries and libraries are always required 28 | option ( SKIP_TESTING "Do not add tests." OFF) 29 | option ( SKIP_LUA_WRAPPER "Do not build and install Lua executable wrappers." OFF) 30 | option ( SKIP_INSTALL_DATA "Skip installing all data." OFF ) 31 | if ( NOT SKIP_INSTALL_DATA ) 32 | option ( SKIP_INSTALL_DOC "Skip installation of documentation." OFF ) 33 | option ( SKIP_INSTALL_EXAMPLE "Skip installation of documentation." OFF ) 34 | option ( SKIP_INSTALL_TEST "Skip installation of tests." OFF) 35 | option ( SKIP_INSTALL_FOO "Skip installation of optional package content." OFF) 36 | endif () 37 | 38 | # TWEAKS 39 | # Setting CMAKE to use loose block and search for find modules in source directory 40 | set ( CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true ) 41 | set ( CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_MODULE_PATH} ) 42 | 43 | # In MSVC, prevent warnings that can occur when using standard libraries. 44 | if ( MSVC ) 45 | add_definitions ( -D_CRT_SECURE_NO_WARNINGS ) 46 | endif () 47 | 48 | ## MACROS 49 | # Parser macro 50 | macro ( parse_arguments prefix arg_names option_names) 51 | set ( DEFAULT_ARGS ) 52 | foreach ( arg_name ${arg_names} ) 53 | set ( ${prefix}_${arg_name} ) 54 | endforeach () 55 | foreach ( option ${option_names} ) 56 | set ( ${prefix}_${option} FALSE ) 57 | endforeach () 58 | 59 | set ( current_arg_name DEFAULT_ARGS ) 60 | set ( current_arg_list ) 61 | foreach ( arg ${ARGN} ) 62 | set ( larg_names ${arg_names} ) 63 | list ( FIND larg_names "${arg}" is_arg_name ) 64 | if ( is_arg_name GREATER -1 ) 65 | set ( ${prefix}_${current_arg_name} ${current_arg_list} ) 66 | set ( current_arg_name ${arg} ) 67 | set ( current_arg_list ) 68 | else () 69 | set ( loption_names ${option_names} ) 70 | list ( FIND loption_names "${arg}" is_option ) 71 | if ( is_option GREATER -1 ) 72 | set ( ${prefix}_${arg} TRUE ) 73 | else () 74 | set ( current_arg_list ${current_arg_list} ${arg} ) 75 | endif () 76 | endif () 77 | endforeach () 78 | set ( ${prefix}_${current_arg_name} ${current_arg_list} ) 79 | endmacro () 80 | 81 | # INSTALL_LUA_EXECUTABLE ( target source ) 82 | # Automatically generate a binary wrapper for lua application and install it 83 | # The wrapper and the source of the application will be placed into /bin 84 | # If the application source did not have .lua suffix then it will be added 85 | # USE: lua_executable ( sputnik src/sputnik.lua ) 86 | macro ( install_lua_executable _name _source ) 87 | get_filename_component ( _source_name ${_source} NAME_WE ) 88 | if ( NOT SKIP_LUA_WRAPPER ) 89 | enable_language ( C ) 90 | 91 | find_package ( Lua51 REQUIRED ) 92 | include_directories ( ${LUA_INCLUDE_DIR} ) 93 | 94 | set ( _wrapper ${CMAKE_CURRENT_BINARY_DIR}/${_name}.c ) 95 | set ( _code 96 | "// Not so simple executable wrapper for Lua apps 97 | #include 98 | #include 99 | #include 100 | #include 101 | #include 102 | 103 | lua_State *L\; 104 | 105 | static int getargs (lua_State *L, char **argv, int n) { 106 | int narg\; 107 | int i\; 108 | int argc = 0\; 109 | while (argv[argc]) argc++\; 110 | narg = argc - (n + 1)\; 111 | luaL_checkstack(L, narg + 3, \"too many arguments to script\")\; 112 | for (i=n+1\; i < argc\; i++) 113 | lua_pushstring(L, argv[i])\; 114 | lua_createtable(L, narg, n + 1)\; 115 | for (i=0\; i < argc\; i++) { 116 | lua_pushstring(L, argv[i])\; 117 | lua_rawseti(L, -2, i - n)\; 118 | } 119 | return narg\; 120 | } 121 | 122 | static void lstop (lua_State *L, lua_Debug *ar) { 123 | (void)ar\; 124 | lua_sethook(L, NULL, 0, 0)\; 125 | luaL_error(L, \"interrupted!\")\; 126 | } 127 | 128 | static void laction (int i) { 129 | signal(i, SIG_DFL)\; 130 | lua_sethook(L, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1)\; 131 | } 132 | 133 | static void l_message (const char *pname, const char *msg) { 134 | if (pname) fprintf(stderr, \"%s: \", pname)\; 135 | fprintf(stderr, \"%s\\n\", msg)\; 136 | fflush(stderr)\; 137 | } 138 | 139 | static int report (lua_State *L, int status) { 140 | if (status && !lua_isnil(L, -1)) { 141 | const char *msg = lua_tostring(L, -1)\; 142 | if (msg == NULL) msg = \"(error object is not a string)\"\; 143 | l_message(\"${_source_name}\", msg)\; 144 | lua_pop(L, 1)\; 145 | } 146 | return status\; 147 | } 148 | 149 | static int traceback (lua_State *L) { 150 | if (!lua_isstring(L, 1)) 151 | return 1\; 152 | lua_getfield(L, LUA_GLOBALSINDEX, \"debug\")\; 153 | if (!lua_istable(L, -1)) { 154 | lua_pop(L, 1)\; 155 | return 1\; 156 | } 157 | lua_getfield(L, -1, \"traceback\")\; 158 | if (!lua_isfunction(L, -1)) { 159 | lua_pop(L, 2)\; 160 | return 1\; 161 | } 162 | lua_pushvalue(L, 1)\; 163 | lua_pushinteger(L, 2)\; 164 | lua_call(L, 2, 1)\; 165 | return 1\; 166 | } 167 | 168 | static int docall (lua_State *L, int narg, int clear) { 169 | int status\; 170 | int base = lua_gettop(L) - narg\; 171 | lua_pushcfunction(L, traceback)\; 172 | lua_insert(L, base)\; 173 | signal(SIGINT, laction)\; 174 | status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base)\; 175 | signal(SIGINT, SIG_DFL)\; 176 | lua_remove(L, base)\; 177 | if (status != 0) lua_gc(L, LUA_GCCOLLECT, 0)\; 178 | return status\; 179 | } 180 | 181 | int main (int argc, char **argv) { 182 | L=lua_open()\; 183 | lua_gc(L, LUA_GCSTOP, 0)\; 184 | luaL_openlibs(L)\; 185 | lua_gc(L, LUA_GCRESTART, 0)\; 186 | int narg = getargs(L, argv, 0)\; 187 | lua_setglobal(L, \"arg\")\; 188 | 189 | // Script 190 | char script[500] = \"./${_source_name}.lua\"\; 191 | lua_getglobal(L, \"_PROGDIR\")\; 192 | if (lua_isstring(L, -1)) { 193 | sprintf( script, \"%s/${_source_name}.lua\", lua_tostring(L, -1))\; 194 | } 195 | lua_pop(L, 1)\; 196 | 197 | // Run 198 | int status = luaL_loadfile(L, script)\; 199 | lua_insert(L, -(narg+1))\; 200 | if (status == 0) 201 | status = docall(L, narg, 0)\; 202 | else 203 | lua_pop(L, narg)\; 204 | 205 | report(L, status)\; 206 | lua_close(L)\; 207 | return status\; 208 | }; 209 | ") 210 | file ( WRITE ${_wrapper} ${_code} ) 211 | add_executable ( ${_name} ${_wrapper} ) 212 | target_link_libraries ( ${_name} ${LUA_LIBRARY} ) 213 | install ( TARGETS ${_name} DESTINATION ${INSTALL_BIN} ) 214 | endif() 215 | install ( PROGRAMS ${_source} DESTINATION ${INSTALL_BIN} RENAME ${_source_name}.lua ) 216 | endmacro () 217 | 218 | # INSTALL_LIBRARY 219 | # Installs any libraries generated using "add_library" into apropriate places. 220 | # USE: install_library ( libexpat ) 221 | macro ( install_library ) 222 | foreach ( _file ${ARGN} ) 223 | install ( TARGETS ${_file} RUNTIME DESTINATION ${INSTALL_BIN} LIBRARY DESTINATION ${INSTALL_LIB} ARCHIVE DESTINATION ${INSTALL_LIB} ) 224 | endforeach() 225 | endmacro () 226 | 227 | # INSTALL_EXECUTABLE 228 | # Installs any executables generated using "add_executable". 229 | # USE: install_executable ( lua ) 230 | macro ( install_executable ) 231 | foreach ( _file ${ARGN} ) 232 | install ( TARGETS ${_file} RUNTIME DESTINATION ${INSTALL_BIN} ) 233 | endforeach() 234 | endmacro () 235 | 236 | # INSTALL_HEADER 237 | # Install a directories or files into header destination. 238 | # USE: install_header ( lua.h luaconf.h ) or install_header ( GL ) 239 | # NOTE: If headers need to be installed into subdirectories use the INSTALL command directly 240 | macro ( install_header ) 241 | parse_arguments ( _ARG "INTO" "" ${ARGN} ) 242 | foreach ( _file ${_ARG_DEFAULT_ARGS} ) 243 | if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" ) 244 | install ( DIRECTORY ${_file} DESTINATION ${INSTALL_INC}/${_ARG_INTO} ) 245 | else () 246 | install ( FILES ${_file} DESTINATION ${INSTALL_INC}/${_ARG_INTO} ) 247 | endif () 248 | endforeach() 249 | endmacro () 250 | 251 | # INSTALL_DATA ( files/directories ) 252 | # This installs additional data files or directories. 253 | # USE: install_data ( extra data.dat ) 254 | macro ( install_data ) 255 | if ( NOT SKIP_INSTALL_DATA ) 256 | parse_arguments ( _ARG "INTO" "" ${ARGN} ) 257 | foreach ( _file ${_ARG_DEFAULT_ARGS} ) 258 | if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" ) 259 | install ( DIRECTORY ${_file} DESTINATION ${INSTALL_DATA}/${_ARG_INTO} ) 260 | else () 261 | install ( FILES ${_file} DESTINATION ${INSTALL_DATA}/${_ARG_INTO} ) 262 | endif () 263 | endforeach() 264 | endif() 265 | endmacro () 266 | 267 | # INSTALL_DOC ( files/directories ) 268 | # This installs documentation content 269 | # USE: install_doc ( doc/ ) 270 | macro ( install_doc ) 271 | if ( NOT SKIP_INSTALL_DATA AND NOT SKIP_INSTALL_DOC ) 272 | parse_arguments ( _ARG "INTO" "" ${ARGN} ) 273 | foreach ( _file ${_ARG_DEFAULT_ARGS} ) 274 | if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" ) 275 | install ( DIRECTORY ${_file} DESTINATION ${INSTALL_DOC}/${_ARG_INTO} ) 276 | else () 277 | install ( FILES ${_file} DESTINATION ${INSTALL_DOC}/${_ARG_INTO} ) 278 | endif () 279 | endforeach() 280 | endif() 281 | endmacro () 282 | 283 | # INSTALL_EXAMPLE ( files/directories ) 284 | # This installs additional data 285 | # USE: install_example ( examples/ exampleA.lua ) 286 | macro ( install_example ) 287 | if ( NOT SKIP_INSTALL_DATA AND NOT SKIP_INSTALL_EXAMPLE ) 288 | parse_arguments ( _ARG "INTO" "" ${ARGN} ) 289 | foreach ( _file ${_ARG_DEFAULT_ARGS} ) 290 | if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" ) 291 | install ( DIRECTORY ${_file} DESTINATION ${INSTALL_EXAMPLE}/${_ARG_INTO} ) 292 | else () 293 | install ( FILES ${_file} DESTINATION ${INSTALL_EXAMPLE}/${_ARG_INTO} ) 294 | endif () 295 | endforeach() 296 | endif() 297 | endmacro () 298 | 299 | # INSTALL_TEST ( files/directories ) 300 | # This installs tests 301 | # USE: install_example ( examples/ exampleA.lua ) 302 | macro ( install_test ) 303 | if ( NOT SKIP_INSTALL_DATA AND NOT SKIP_INSTALL_TEST ) 304 | parse_arguments ( _ARG "INTO" "" ${ARGN} ) 305 | foreach ( _file ${_ARG_DEFAULT_ARGS} ) 306 | if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" ) 307 | install ( DIRECTORY ${_file} DESTINATION ${INSTALL_TEST}/${_ARG_INTO} ) 308 | else () 309 | install ( FILES ${_file} DESTINATION ${INSTALL_TEST}/${_ARG_INTO} ) 310 | endif () 311 | endforeach() 312 | endif() 313 | endmacro () 314 | 315 | # INSTALL_FOO ( files/directories ) 316 | # This installs optional content 317 | # USE: install_foo ( examples/ exampleA.lua ) 318 | macro ( install_foo ) 319 | if ( NOT SKIP_INSTALL_DATA AND NOT SKIP_INSTALL_FOO ) 320 | parse_arguments ( _ARG "INTO" "" ${ARGN} ) 321 | foreach ( _file ${_ARG_DEFAULT_ARGS} ) 322 | if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" ) 323 | install ( DIRECTORY ${_file} DESTINATION ${INSTALL_FOO}/${_ARG_INTO} ) 324 | else () 325 | install ( FILES ${_file} DESTINATION ${INSTALL_FOO}/${_ARG_INTO} ) 326 | endif () 327 | endforeach() 328 | endif() 329 | endmacro () 330 | 331 | # INSTALL_LUA_MODULE 332 | # This macro installs a lua source module into destination given by lua require syntax. 333 | # Binary modules are also supported where this funcion takes sources and libraries to compile separated by LINK keyword 334 | # USE: install_lua_module ( socket.http src/http.lua ) 335 | # USE2: install_lua_module ( mime.core src/mime.c ) 336 | # USE3: install_lua_module ( socket.core ${SRC_SOCKET} LINK ${LIB_SOCKET} ) 337 | macro (install_lua_module _name ) 338 | string ( REPLACE "." "/" _module "${_name}" ) 339 | string ( REPLACE "." "_" _target "${_name}" ) 340 | 341 | set ( _lua_module "${_module}.lua" ) 342 | set ( _bin_module "${_module}${CMAKE_SHARED_MODULE_SUFFIX}" ) 343 | 344 | parse_arguments ( _MODULE "LINK" "" ${ARGN} ) 345 | get_filename_component ( _ext ${ARGV1} EXT ) 346 | if ( _ext STREQUAL ".lua" ) 347 | get_filename_component ( _path ${_lua_module} PATH ) 348 | get_filename_component ( _filename ${_lua_module} NAME ) 349 | install ( FILES ${ARGV1} DESTINATION ${INSTALL_LMOD}/${_path} RENAME ${_filename} ) 350 | else () 351 | enable_language ( C ) 352 | get_filename_component ( _module_name ${_bin_module} NAME_WE ) 353 | get_filename_component ( _module_path ${_bin_module} PATH ) 354 | 355 | find_package ( Lua51 REQUIRED ) 356 | include_directories ( ${LUA_INCLUDE_DIR} ) 357 | 358 | add_library( ${_target} MODULE ${_MODULE_DEFAULT_ARGS}) 359 | target_link_libraries ( ${_target} ${LUA_LIBRARY} ${_MODULE_LINK} ) 360 | set_target_properties ( ${_target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${_module_path}" PREFIX "" OUTPUT_NAME "${_module_name}" ) 361 | 362 | install ( TARGETS ${_target} DESTINATION ${INSTALL_CMOD}/${_module_path}) 363 | endif () 364 | endmacro () 365 | 366 | # ADD_LUA_TEST 367 | # Runs Lua script `_testfile` under CTest tester. 368 | # Optional named argument `WORKING_DIRECTORY` is current working directory to run test under 369 | # (defaults to ${CMAKE_CURRENT_BINARY_DIR}). 370 | # Both paths, if relative, are relative to ${CMAKE_CURRENT_SOURCE_DIR}. 371 | # Under LuaDist, set test=true in config.lua to enable testing. 372 | # USE: add_lua_test ( test/test1.lua [args...] [WORKING_DIRECTORY dir]) 373 | macro ( add_lua_test _testfile ) 374 | if ( NOT SKIP_TESTING ) 375 | parse_arguments ( _ARG "WORKING_DIRECTORY" "" ${ARGN} ) 376 | include ( CTest ) 377 | find_program ( LUA NAMES lua lua.bat ) 378 | get_filename_component ( TESTFILEABS ${_testfile} ABSOLUTE ) 379 | get_filename_component ( TESTFILENAME ${_testfile} NAME ) 380 | get_filename_component ( TESTFILEBASE ${_testfile} NAME_WE ) 381 | 382 | # Write wrapper script. 383 | set ( TESTWRAPPER ${CMAKE_CURRENT_BINARY_DIR}/${TESTFILENAME} ) 384 | set ( TESTWRAPPERSOURCE 385 | "local configuration = ... 386 | local sodir = '${CMAKE_CURRENT_BINARY_DIR}' .. (configuration == '' and '' or '/' .. configuration) 387 | package.path = sodir .. '/?.lua\;' .. sodir .. '/?.lua\;' .. package.path 388 | package.cpath = sodir .. '/?.so\;' .. sodir .. '/?.dll\;' .. package.cpath 389 | arg[0] = '${TESTFILEABS}' 390 | table.remove(arg, 1) 391 | return assert(loadfile '${TESTFILEABS}')(unpack(arg)) 392 | " ) 393 | if ( _ARG_WORKING_DIRECTORY ) 394 | get_filename_component ( TESTCURRENTDIRABS ${_ARG_WORKING_DIRECTORY} ABSOLUTE ) 395 | # note: CMake 2.6 (unlike 2.8) lacks WORKING_DIRECTORY parameter. 396 | #old: set ( TESTWRAPPERSOURCE "require 'lfs'; lfs.chdir('${TESTCURRENTDIRABS}' ) ${TESTWRAPPERSOURCE}" ) 397 | set ( _pre ${CMAKE_COMMAND} -E chdir "${TESTCURRENTDIRABS}" ) 398 | endif () 399 | file ( WRITE ${TESTWRAPPER} ${TESTWRAPPERSOURCE}) 400 | add_test ( NAME ${TESTFILEBASE} COMMAND ${_pre} ${LUA} ${TESTWRAPPER} $ ${_ARG_DEFAULT_ARGS} ) 401 | endif () 402 | # see also http://gdcm.svn.sourceforge.net/viewvc/gdcm/Sandbox/CMakeModules/UsePythonTest.cmake 403 | endmacro () 404 | 405 | # Converts Lua source file `_source` to binary string embedded in C source 406 | # file `_target`. Optionally compiles Lua source to byte code (not available 407 | # under LuaJIT2, which doesn't have a bytecode loader). Additionally, Lua 408 | # versions of bin2c [1] and luac [2] may be passed respectively as additional 409 | # arguments. 410 | # 411 | # [1] http://lua-users.org/wiki/BinToCee 412 | # [2] http://lua-users.org/wiki/LuaCompilerInLua 413 | function ( add_lua_bin2c _target _source ) 414 | find_program ( LUA NAMES lua lua.bat ) 415 | execute_process ( COMMAND ${LUA} -e "string.dump(function()end)" RESULT_VARIABLE _LUA_DUMP_RESULT ERROR_QUIET ) 416 | if ( NOT ${_LUA_DUMP_RESULT} ) 417 | SET ( HAVE_LUA_DUMP true ) 418 | endif () 419 | message ( "-- string.dump=${HAVE_LUA_DUMP}" ) 420 | 421 | if ( ARGV2 ) 422 | get_filename_component ( BIN2C ${ARGV2} ABSOLUTE ) 423 | set ( BIN2C ${LUA} ${BIN2C} ) 424 | else () 425 | find_program ( BIN2C NAMES bin2c bin2c.bat ) 426 | endif () 427 | if ( HAVE_LUA_DUMP ) 428 | if ( ARGV3 ) 429 | get_filename_component ( LUAC ${ARGV3} ABSOLUTE ) 430 | set ( LUAC ${LUA} ${LUAC} ) 431 | else () 432 | find_program ( LUAC NAMES luac luac.bat ) 433 | endif () 434 | endif ( HAVE_LUA_DUMP ) 435 | message ( "-- bin2c=${BIN2C}" ) 436 | message ( "-- luac=${LUAC}" ) 437 | 438 | get_filename_component ( SOURCEABS ${_source} ABSOLUTE ) 439 | if ( HAVE_LUA_DUMP ) 440 | get_filename_component ( SOURCEBASE ${_source} NAME_WE ) 441 | add_custom_command ( 442 | OUTPUT ${_target} DEPENDS ${_source} 443 | COMMAND ${LUAC} -o ${CMAKE_CURRENT_BINARY_DIR}/${SOURCEBASE}.lo ${SOURCEABS} 444 | COMMAND ${BIN2C} ${CMAKE_CURRENT_BINARY_DIR}/${SOURCEBASE}.lo ">${_target}" ) 445 | else () 446 | add_custom_command ( 447 | OUTPUT ${_target} DEPENDS ${SOURCEABS} 448 | COMMAND ${BIN2C} ${_source} ">${_target}" ) 449 | endif () 450 | endfunction() 451 | -------------------------------------------------------------------------------- /dist.info: -------------------------------------------------------------------------------- 1 | --- This file is part of LuaDist project 2 | 3 | name = "lua-cjson" 4 | version = "1.0.3" 5 | 6 | desc = "Lua CJSON provides fast JSON parsing and encoding support for Lua." 7 | author = "Mark Pulford" 8 | license = "MIT" 9 | url = "http://www.kyne.com.au/~mark/software/lua-cjson.php" 10 | maintainer = "Félix A.A." 11 | 12 | depends = { 13 | "lua ~>5.1" 14 | } 15 | -------------------------------------------------------------------------------- /lua-cjson-1.0.3-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-cjson" 2 | version = "1.0.3-1" 3 | 4 | source = { 5 | url = "http://www.kyne.com.au/~mark/software/lua-cjson-1.0.3.zip", 6 | } 7 | 8 | description = { 9 | summary = "Fast JSON encoding/parsing support for Lua", 10 | detailed = [[ 11 | Lua CJSON provides fast UTF-8 JSON parsing/encoding support for Lua, 12 | and has no external dependencies. 13 | ]], 14 | homepage = "http://www.kyne.com.au/~mark/software/lua-cjson.php", 15 | license = "MIT" 16 | } 17 | 18 | dependencies = { 19 | "lua >= 5.1" 20 | } 21 | 22 | build = { 23 | type = "builtin", 24 | modules = { 25 | cjson = { 26 | sources = { "lua_cjson.c", "strbuf.c" }, 27 | defines = { "VERSION=\"1.0.3\"" } 28 | } 29 | }, 30 | copy_directories = { "tests" } 31 | } 32 | -------------------------------------------------------------------------------- /lua-cjson.spec: -------------------------------------------------------------------------------- 1 | %define luaver 5.1 2 | %define lualibdir %{_libdir}/lua/%{luaver} 3 | 4 | Name: lua-cjson 5 | Version: 1.0.3 6 | Release: 1%{?dist} 7 | Summary: JSON support for the Lua language 8 | 9 | Group: Development/Libraries 10 | License: MIT 11 | URL: http://www.kyne.com.au/~mark/software/lua-cjson/ 12 | Source0: http://www.kyne.com.au/~mark/software/lua-cjson/lua-cjson-%{version}.tar.gz 13 | BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) 14 | 15 | BuildRequires: lua >= %{luaver}, lua-devel >= %{luaver} 16 | Requires: lua >= %{luaver} 17 | 18 | %description 19 | Lua CJSON provides fast, standards compliant JSON support for Lua. 20 | 21 | 22 | %prep 23 | %setup -q 24 | 25 | 26 | %build 27 | make %{?_smp_mflags} CFLAGS="%{optflags}" LUA_INCLUDE_DIR="%{_includedir}" 28 | 29 | 30 | %install 31 | rm -rf "$RPM_BUILD_ROOT" 32 | make install DESTDIR="$RPM_BUILD_ROOT" LUA_LIB_DIR="%{lualibdir}" 33 | 34 | 35 | %clean 36 | rm -rf "$RPM_BUILD_ROOT" 37 | 38 | 39 | %files 40 | %defattr(-,root,root,-) 41 | %doc LICENSE NEWS performance.txt README rfc4627.txt tests THANKS TODO 42 | %{lualibdir}/* 43 | 44 | 45 | %changelog 46 | * Wed Sep 15 2011 Mark Pulford - 1.0.3-1 47 | - Updated for 1.0.3 48 | 49 | * Sun May 29 2011 Mark Pulford - 1.0.2-1 50 | - Updated for 1.0.2 51 | 52 | * Sun May 10 2011 Mark Pulford - 1.0.1-1 53 | - Updated for 1.0.1 54 | 55 | * Sun May 1 2011 Mark Pulford - 1.0-1 56 | - Initial package 57 | -------------------------------------------------------------------------------- /lua_cjson.c: -------------------------------------------------------------------------------- 1 | /* CJSON - JSON support for Lua 2 | * 3 | * Copyright (c) 2010-2011 Mark Pulford 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining 6 | * a copy of this software and associated documentation files (the 7 | * "Software"), to deal in the Software without restriction, including 8 | * without limitation the rights to use, copy, modify, merge, publish, 9 | * distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to 11 | * the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | /* Caveats: 26 | * - JSON "null" values are represented as lightuserdata since Lua 27 | * tables cannot contain "nil". Compare with cjson.null. 28 | * - Invalid UTF-8 characters are not detected and will be passed 29 | * untouched. If required, UTF-8 error checking should be done 30 | * outside this library. 31 | * - Javascript comments are not part of the JSON spec, and are not 32 | * currently supported. 33 | * 34 | * Note: Decoding is slower than encoding. Lua spends significant 35 | * time (30%) managing tables when parsing JSON since it is 36 | * difficult to know object/array sizes ahead of time. 37 | */ 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #include "strbuf.h" 46 | 47 | #ifdef MISSING_ISINF 48 | #define isinf(x) (!isnan(x) && isnan((x) - (x))) 49 | #endif 50 | 51 | #define DEFAULT_SPARSE_CONVERT 0 52 | #define DEFAULT_SPARSE_RATIO 2 53 | #define DEFAULT_SPARSE_SAFE 10 54 | #define DEFAULT_MAX_DEPTH 20 55 | #define DEFAULT_ENCODE_REFUSE_BADNUM 1 56 | #define DEFAULT_DECODE_REFUSE_BADNUM 0 57 | #define DEFAULT_ENCODE_KEEP_BUFFER 1 58 | 59 | typedef enum { 60 | T_OBJ_BEGIN, 61 | T_OBJ_END, 62 | T_ARR_BEGIN, 63 | T_ARR_END, 64 | T_STRING, 65 | T_NUMBER, 66 | T_BOOLEAN, 67 | T_NULL, 68 | T_COLON, 69 | T_COMMA, 70 | T_END, 71 | T_WHITESPACE, 72 | T_ERROR, 73 | T_UNKNOWN 74 | } json_token_type_t; 75 | 76 | static const char *json_token_type_name[] = { 77 | "T_OBJ_BEGIN", 78 | "T_OBJ_END", 79 | "T_ARR_BEGIN", 80 | "T_ARR_END", 81 | "T_STRING", 82 | "T_NUMBER", 83 | "T_BOOLEAN", 84 | "T_NULL", 85 | "T_COLON", 86 | "T_COMMA", 87 | "T_END", 88 | "T_WHITESPACE", 89 | "T_ERROR", 90 | "T_UNKNOWN", 91 | NULL 92 | }; 93 | 94 | typedef struct { 95 | json_token_type_t ch2token[256]; 96 | char escape2char[256]; /* Decoding */ 97 | #if 0 98 | char escapes[35][8]; /* Pre-generated escape string buffer */ 99 | char *char2escape[256]; /* Encoding */ 100 | #endif 101 | strbuf_t encode_buf; 102 | char number_fmt[8]; /* "%.XXg\0" */ 103 | int current_depth; 104 | 105 | int encode_sparse_convert; 106 | int encode_sparse_ratio; 107 | int encode_sparse_safe; 108 | int encode_max_depth; 109 | int encode_refuse_badnum; 110 | int decode_refuse_badnum; 111 | int encode_keep_buffer; 112 | int encode_number_precision; 113 | } json_config_t; 114 | 115 | typedef struct { 116 | const char *data; 117 | int index; 118 | strbuf_t *tmp; /* Temporary storage for strings */ 119 | json_config_t *cfg; 120 | } json_parse_t; 121 | 122 | typedef struct { 123 | json_token_type_t type; 124 | int index; 125 | union { 126 | const char *string; 127 | double number; 128 | int boolean; 129 | } value; 130 | int string_len; 131 | } json_token_t; 132 | 133 | static const char *char2escape[256] = { 134 | "\\u0000", "\\u0001", "\\u0002", "\\u0003", 135 | "\\u0004", "\\u0005", "\\u0006", "\\u0007", 136 | "\\b", "\\t", "\\n", "\\u000b", 137 | "\\f", "\\r", "\\u000e", "\\u000f", 138 | "\\u0010", "\\u0011", "\\u0012", "\\u0013", 139 | "\\u0014", "\\u0015", "\\u0016", "\\u0017", 140 | "\\u0018", "\\u0019", "\\u001a", "\\u001b", 141 | "\\u001c", "\\u001d", "\\u001e", "\\u001f", 142 | NULL, NULL, "\\\"", NULL, NULL, NULL, NULL, NULL, 143 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\/", 144 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 145 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 146 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 147 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 148 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 149 | NULL, NULL, NULL, NULL, "\\\\", NULL, NULL, NULL, 150 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 151 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 152 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 153 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\u007f", 154 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 155 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 156 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 157 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 158 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 159 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 160 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 161 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 162 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 163 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 164 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 165 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 166 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 167 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 168 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 169 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 170 | }; 171 | 172 | static int json_config_key; 173 | 174 | /* ===== CONFIGURATION ===== */ 175 | 176 | static json_config_t *json_fetch_config(lua_State *l) 177 | { 178 | json_config_t *cfg; 179 | 180 | lua_pushlightuserdata(l, &json_config_key); 181 | lua_gettable(l, LUA_REGISTRYINDEX); 182 | cfg = lua_touserdata(l, -1); 183 | if (!cfg) 184 | luaL_error(l, "BUG: Unable to fetch CJSON configuration"); 185 | 186 | lua_pop(l, 1); 187 | 188 | return cfg; 189 | } 190 | 191 | static void json_verify_arg_count(lua_State *l, int args) 192 | { 193 | luaL_argcheck(l, lua_gettop(l) <= args, args + 1, 194 | "found too many arguments"); 195 | } 196 | 197 | /* Configures handling of extremely sparse arrays: 198 | * convert: Convert extremely sparse arrays into objects? Otherwise error. 199 | * ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio 200 | * safe: Always use an array when the max index <= safe */ 201 | static int json_cfg_encode_sparse_array(lua_State *l) 202 | { 203 | json_config_t *cfg; 204 | int val; 205 | 206 | json_verify_arg_count(l, 3); 207 | cfg = json_fetch_config(l); 208 | 209 | switch (lua_gettop(l)) { 210 | case 3: 211 | val = luaL_checkinteger(l, 3); 212 | luaL_argcheck(l, val >= 0, 3, "expected integer >= 0"); 213 | cfg->encode_sparse_safe = val; 214 | case 2: 215 | val = luaL_checkinteger(l, 2); 216 | luaL_argcheck(l, val >= 0, 2, "expected integer >= 0"); 217 | cfg->encode_sparse_ratio = val; 218 | case 1: 219 | luaL_argcheck(l, lua_isboolean(l, 1), 1, "expected boolean"); 220 | cfg->encode_sparse_convert = lua_toboolean(l, 1); 221 | } 222 | 223 | lua_pushboolean(l, cfg->encode_sparse_convert); 224 | lua_pushinteger(l, cfg->encode_sparse_ratio); 225 | lua_pushinteger(l, cfg->encode_sparse_safe); 226 | 227 | return 3; 228 | } 229 | 230 | /* Configures the maximum number of nested arrays/objects allowed when 231 | * encoding */ 232 | static int json_cfg_encode_max_depth(lua_State *l) 233 | { 234 | json_config_t *cfg; 235 | int depth; 236 | 237 | json_verify_arg_count(l, 1); 238 | cfg = json_fetch_config(l); 239 | 240 | if (lua_gettop(l)) { 241 | depth = luaL_checkinteger(l, 1); 242 | luaL_argcheck(l, depth > 0, 1, "expected positive integer"); 243 | cfg->encode_max_depth = depth; 244 | } 245 | 246 | lua_pushinteger(l, cfg->encode_max_depth); 247 | 248 | return 1; 249 | } 250 | 251 | static void json_set_number_precision(json_config_t *cfg, int prec) 252 | { 253 | cfg->encode_number_precision = prec; 254 | sprintf(cfg->number_fmt, "%%.%dg", prec); 255 | } 256 | 257 | /* Configures number precision when converting doubles to text */ 258 | static int json_cfg_encode_number_precision(lua_State *l) 259 | { 260 | json_config_t *cfg; 261 | int precision; 262 | 263 | json_verify_arg_count(l, 1); 264 | cfg = json_fetch_config(l); 265 | 266 | if (lua_gettop(l)) { 267 | precision = luaL_checkinteger(l, 1); 268 | luaL_argcheck(l, 1 <= precision && precision <= 14, 1, 269 | "expected integer between 1 and 14"); 270 | json_set_number_precision(cfg, precision); 271 | } 272 | 273 | lua_pushinteger(l, cfg->encode_number_precision); 274 | 275 | return 1; 276 | } 277 | 278 | /* Configures JSON encoding buffer persistence */ 279 | static int json_cfg_encode_keep_buffer(lua_State *l) 280 | { 281 | json_config_t *cfg; 282 | 283 | json_verify_arg_count(l, 1); 284 | cfg = json_fetch_config(l); 285 | 286 | if (lua_gettop(l)) { 287 | luaL_checktype(l, 1, LUA_TBOOLEAN); 288 | cfg->encode_keep_buffer = lua_toboolean(l, 1); 289 | } 290 | 291 | lua_pushboolean(l, cfg->encode_keep_buffer); 292 | 293 | return 1; 294 | } 295 | 296 | /* On argument: decode enum and set config variables 297 | * **options must point to a NULL terminated array of 4 enums 298 | * Returns: current enum value */ 299 | static void json_enum_option(lua_State *l, const char **options, 300 | int *opt1, int *opt2) 301 | { 302 | int setting; 303 | 304 | if (lua_gettop(l)) { 305 | if (lua_isboolean(l, 1)) 306 | setting = lua_toboolean(l, 1) * 3; 307 | else 308 | setting = luaL_checkoption(l, 1, NULL, options); 309 | 310 | *opt1 = setting & 1 ? 1 : 0; 311 | *opt2 = setting & 2 ? 1 : 0; 312 | } else { 313 | setting = *opt1 | (*opt2 << 1); 314 | } 315 | 316 | if (setting) 317 | lua_pushstring(l, options[setting]); 318 | else 319 | lua_pushboolean(l, 0); 320 | } 321 | 322 | 323 | /* When enabled, rejects: NaN, Infinity, hexidecimal numbers */ 324 | static int json_cfg_refuse_invalid_numbers(lua_State *l) 325 | { 326 | static const char *options_enc_dec[] = { "none", "encode", "decode", 327 | "both", NULL }; 328 | json_config_t *cfg; 329 | 330 | json_verify_arg_count(l, 1); 331 | cfg = json_fetch_config(l); 332 | 333 | json_enum_option(l, options_enc_dec, 334 | &cfg->encode_refuse_badnum, 335 | &cfg->decode_refuse_badnum); 336 | 337 | return 1; 338 | } 339 | 340 | static int json_destroy_config(lua_State *l) 341 | { 342 | json_config_t *cfg; 343 | 344 | cfg = lua_touserdata(l, 1); 345 | if (cfg) 346 | strbuf_free(&cfg->encode_buf); 347 | cfg = NULL; 348 | 349 | return 0; 350 | } 351 | 352 | static void json_create_config(lua_State *l) 353 | { 354 | json_config_t *cfg; 355 | int i; 356 | 357 | cfg = lua_newuserdata(l, sizeof(*cfg)); 358 | 359 | /* Create GC method to clean up strbuf */ 360 | lua_newtable(l); 361 | lua_pushcfunction(l, json_destroy_config); 362 | lua_setfield(l, -2, "__gc"); 363 | lua_setmetatable(l, -2); 364 | 365 | strbuf_init(&cfg->encode_buf, 0); 366 | 367 | cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT; 368 | cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO; 369 | cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE; 370 | cfg->encode_max_depth = DEFAULT_MAX_DEPTH; 371 | cfg->encode_refuse_badnum = DEFAULT_ENCODE_REFUSE_BADNUM; 372 | cfg->decode_refuse_badnum = DEFAULT_DECODE_REFUSE_BADNUM; 373 | cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER; 374 | json_set_number_precision(cfg, 14); 375 | 376 | /* Decoding init */ 377 | 378 | /* Tag all characters as an error */ 379 | for (i = 0; i < 256; i++) 380 | cfg->ch2token[i] = T_ERROR; 381 | 382 | /* Set tokens that require no further processing */ 383 | cfg->ch2token['{'] = T_OBJ_BEGIN; 384 | cfg->ch2token['}'] = T_OBJ_END; 385 | cfg->ch2token['['] = T_ARR_BEGIN; 386 | cfg->ch2token[']'] = T_ARR_END; 387 | cfg->ch2token[','] = T_COMMA; 388 | cfg->ch2token[':'] = T_COLON; 389 | cfg->ch2token['\0'] = T_END; 390 | cfg->ch2token[' '] = T_WHITESPACE; 391 | cfg->ch2token['\t'] = T_WHITESPACE; 392 | cfg->ch2token['\n'] = T_WHITESPACE; 393 | cfg->ch2token['\r'] = T_WHITESPACE; 394 | 395 | /* Update characters that require further processing */ 396 | cfg->ch2token['f'] = T_UNKNOWN; /* false? */ 397 | cfg->ch2token['i'] = T_UNKNOWN; /* inf, ininity? */ 398 | cfg->ch2token['I'] = T_UNKNOWN; 399 | cfg->ch2token['n'] = T_UNKNOWN; /* null, nan? */ 400 | cfg->ch2token['N'] = T_UNKNOWN; 401 | cfg->ch2token['t'] = T_UNKNOWN; /* true? */ 402 | cfg->ch2token['"'] = T_UNKNOWN; /* string? */ 403 | cfg->ch2token['+'] = T_UNKNOWN; /* number? */ 404 | cfg->ch2token['-'] = T_UNKNOWN; 405 | for (i = 0; i < 10; i++) 406 | cfg->ch2token['0' + i] = T_UNKNOWN; 407 | 408 | /* Lookup table for parsing escape characters */ 409 | for (i = 0; i < 256; i++) 410 | cfg->escape2char[i] = 0; /* String error */ 411 | cfg->escape2char['"'] = '"'; 412 | cfg->escape2char['\\'] = '\\'; 413 | cfg->escape2char['/'] = '/'; 414 | cfg->escape2char['b'] = '\b'; 415 | cfg->escape2char['t'] = '\t'; 416 | cfg->escape2char['n'] = '\n'; 417 | cfg->escape2char['f'] = '\f'; 418 | cfg->escape2char['r'] = '\r'; 419 | cfg->escape2char['u'] = 'u'; /* Unicode parsing required */ 420 | 421 | 422 | #if 0 423 | /* Initialise separate storage for pre-generated escape codes. 424 | * Escapes 0-31 map directly, 34, 92, 127 follow afterwards to 425 | * save memory. */ 426 | for (i = 0 ; i < 32; i++) 427 | sprintf(cfg->escapes[i], "\\u%04x", i); 428 | strcpy(cfg->escapes[8], "\b"); /* Override simpler escapes */ 429 | strcpy(cfg->escapes[9], "\t"); 430 | strcpy(cfg->escapes[10], "\n"); 431 | strcpy(cfg->escapes[12], "\f"); 432 | strcpy(cfg->escapes[13], "\r"); 433 | strcpy(cfg->escapes[32], "\\\""); /* chr(34) */ 434 | strcpy(cfg->escapes[33], "\\\\"); /* chr(92) */ 435 | sprintf(cfg->escapes[34], "\\u%04x", 127); /* char(127) */ 436 | 437 | /* Initialise encoding escape lookup table */ 438 | for (i = 0; i < 32; i++) 439 | cfg->char2escape[i] = cfg->escapes[i]; 440 | for (i = 32; i < 256; i++) 441 | cfg->char2escape[i] = NULL; 442 | cfg->char2escape[34] = cfg->escapes[32]; 443 | cfg->char2escape[92] = cfg->escapes[33]; 444 | cfg->char2escape[127] = cfg->escapes[34]; 445 | #endif 446 | } 447 | 448 | /* ===== ENCODING ===== */ 449 | 450 | static void json_encode_exception(lua_State *l, json_config_t *cfg, int lindex, 451 | const char *reason) 452 | { 453 | if (!cfg->encode_keep_buffer) 454 | strbuf_free(&cfg->encode_buf); 455 | luaL_error(l, "Cannot serialise %s: %s", 456 | lua_typename(l, lua_type(l, lindex)), reason); 457 | } 458 | 459 | /* json_append_string args: 460 | * - lua_State 461 | * - JSON strbuf 462 | * - String (Lua stack index) 463 | * 464 | * Returns nothing. Doesn't remove string from Lua stack */ 465 | static void json_append_string(lua_State *l, strbuf_t *json, int lindex) 466 | { 467 | const char *escstr; 468 | int i; 469 | const char *str; 470 | size_t len; 471 | 472 | str = lua_tolstring(l, lindex, &len); 473 | 474 | /* Worst case is len * 6 (all unicode escapes). 475 | * This buffer is reused constantly for small strings 476 | * If there are any excess pages, they won't be hit anyway. 477 | * This gains ~5% speedup. */ 478 | strbuf_ensure_empty_length(json, len * 6 + 2); 479 | 480 | strbuf_append_char_unsafe(json, '\"'); 481 | for (i = 0; i < len; i++) { 482 | escstr = char2escape[(unsigned char)str[i]]; 483 | if (escstr) 484 | strbuf_append_string(json, escstr); 485 | else 486 | strbuf_append_char_unsafe(json, str[i]); 487 | } 488 | strbuf_append_char_unsafe(json, '\"'); 489 | } 490 | 491 | /* Find the size of the array on the top of the Lua stack 492 | * -1 object (not a pure array) 493 | * >=0 elements in array 494 | */ 495 | static int lua_array_length(lua_State *l, json_config_t *cfg) 496 | { 497 | double k; 498 | int max; 499 | int items; 500 | 501 | max = 0; 502 | items = 0; 503 | 504 | lua_pushnil(l); 505 | /* table, startkey */ 506 | while (lua_next(l, -2) != 0) { 507 | /* table, key, value */ 508 | if (lua_type(l, -2) == LUA_TNUMBER && 509 | (k = lua_tonumber(l, -2))) { 510 | /* Integer >= 1 ? */ 511 | if (floor(k) == k && k >= 1) { 512 | if (k > max) 513 | max = k; 514 | items++; 515 | lua_pop(l, 1); 516 | continue; 517 | } 518 | } 519 | 520 | /* Must not be an array (non integer key) */ 521 | lua_pop(l, 2); 522 | return -1; 523 | } 524 | 525 | /* Encode excessively sparse arrays as objects (if enabled) */ 526 | if (cfg->encode_sparse_ratio > 0 && 527 | max > items * cfg->encode_sparse_ratio && 528 | max > cfg->encode_sparse_safe) { 529 | if (!cfg->encode_sparse_convert) 530 | json_encode_exception(l, cfg, -1, "excessively sparse array"); 531 | 532 | return -1; 533 | } 534 | 535 | return max; 536 | } 537 | 538 | static void json_encode_descend(lua_State *l, json_config_t *cfg) 539 | { 540 | cfg->current_depth++; 541 | 542 | if (cfg->current_depth > cfg->encode_max_depth) { 543 | if (!cfg->encode_keep_buffer) 544 | strbuf_free(&cfg->encode_buf); 545 | luaL_error(l, "Cannot serialise, excessive nesting (%d)", 546 | cfg->current_depth); 547 | } 548 | } 549 | 550 | static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json); 551 | 552 | /* json_append_array args: 553 | * - lua_State 554 | * - JSON strbuf 555 | * - Size of passwd Lua array (top of stack) */ 556 | static void json_append_array(lua_State *l, json_config_t *cfg, strbuf_t *json, 557 | int array_length) 558 | { 559 | int comma, i; 560 | 561 | json_encode_descend(l, cfg); 562 | 563 | strbuf_append_char(json, '['); 564 | 565 | comma = 0; 566 | for (i = 1; i <= array_length; i++) { 567 | if (comma) 568 | strbuf_append_char(json, ','); 569 | else 570 | comma = 1; 571 | 572 | lua_rawgeti(l, -1, i); 573 | json_append_data(l, cfg, json); 574 | lua_pop(l, 1); 575 | } 576 | 577 | strbuf_append_char(json, ']'); 578 | 579 | cfg->current_depth--; 580 | } 581 | 582 | static void json_append_number(lua_State *l, strbuf_t *json, int index, 583 | json_config_t *cfg) 584 | { 585 | double num = lua_tonumber(l, index); 586 | 587 | if (cfg->encode_refuse_badnum && (isinf(num) || isnan(num))) 588 | json_encode_exception(l, cfg, index, "must not be NaN or Inf"); 589 | 590 | /* Lowest double printed with %.14g is 21 characters long: 591 | * -1.7976931348623e+308 592 | * 593 | * Use 32 to include the \0, and a few extra just in case.. 594 | */ 595 | strbuf_append_fmt(json, 32, cfg->number_fmt, num); 596 | } 597 | 598 | static void json_append_object(lua_State *l, json_config_t *cfg, 599 | strbuf_t *json) 600 | { 601 | int comma, keytype; 602 | 603 | json_encode_descend(l, cfg); 604 | 605 | /* Object */ 606 | strbuf_append_char(json, '{'); 607 | 608 | lua_pushnil(l); 609 | /* table, startkey */ 610 | comma = 0; 611 | while (lua_next(l, -2) != 0) { 612 | if (comma) 613 | strbuf_append_char(json, ','); 614 | else 615 | comma = 1; 616 | 617 | /* table, key, value */ 618 | keytype = lua_type(l, -2); 619 | if (keytype == LUA_TNUMBER) { 620 | strbuf_append_char(json, '"'); 621 | json_append_number(l, json, -2, cfg); 622 | strbuf_append_mem(json, "\":", 2); 623 | } else if (keytype == LUA_TSTRING) { 624 | json_append_string(l, json, -2); 625 | strbuf_append_char(json, ':'); 626 | } else { 627 | json_encode_exception(l, cfg, -2, 628 | "table key must be a number or string"); 629 | /* never returns */ 630 | } 631 | 632 | /* table, key, value */ 633 | json_append_data(l, cfg, json); 634 | lua_pop(l, 1); 635 | /* table, key */ 636 | } 637 | 638 | strbuf_append_char(json, '}'); 639 | 640 | cfg->current_depth--; 641 | } 642 | 643 | /* Serialise Lua data into JSON string. */ 644 | static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json) 645 | { 646 | int len; 647 | 648 | switch (lua_type(l, -1)) { 649 | case LUA_TSTRING: 650 | json_append_string(l, json, -1); 651 | break; 652 | case LUA_TNUMBER: 653 | json_append_number(l, json, -1, cfg); 654 | break; 655 | case LUA_TBOOLEAN: 656 | if (lua_toboolean(l, -1)) 657 | strbuf_append_mem(json, "true", 4); 658 | else 659 | strbuf_append_mem(json, "false", 5); 660 | break; 661 | case LUA_TTABLE: 662 | len = lua_array_length(l, cfg); 663 | if (len > 0) 664 | json_append_array(l, cfg, json, len); 665 | else 666 | json_append_object(l, cfg, json); 667 | break; 668 | case LUA_TNIL: 669 | strbuf_append_mem(json, "null", 4); 670 | break; 671 | case LUA_TLIGHTUSERDATA: 672 | if (lua_touserdata(l, -1) == NULL) { 673 | strbuf_append_mem(json, "null", 4); 674 | break; 675 | } 676 | default: 677 | /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD, 678 | * and LUA_TLIGHTUSERDATA) cannot be serialised */ 679 | json_encode_exception(l, cfg, -1, "type not supported"); 680 | /* never returns */ 681 | } 682 | } 683 | 684 | static int json_encode(lua_State *l) 685 | { 686 | json_config_t *cfg; 687 | char *json; 688 | int len; 689 | 690 | /* Can't use json_verify_arg_count() since we need to ensure 691 | * there is only 1 argument */ 692 | luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); 693 | 694 | cfg = json_fetch_config(l); 695 | cfg->current_depth = 0; 696 | 697 | /* Reset the persistent buffer if it exists. 698 | * Otherwise allocate a new buffer. */ 699 | if (strbuf_allocated(&cfg->encode_buf)) 700 | strbuf_reset(&cfg->encode_buf); 701 | else 702 | strbuf_init(&cfg->encode_buf, 0); 703 | 704 | json_append_data(l, cfg, &cfg->encode_buf); 705 | json = strbuf_string(&cfg->encode_buf, &len); 706 | 707 | lua_pushlstring(l, json, len); 708 | 709 | if (!cfg->encode_keep_buffer) 710 | strbuf_free(&cfg->encode_buf); 711 | 712 | return 1; 713 | } 714 | 715 | /* ===== DECODING ===== */ 716 | 717 | static void json_process_value(lua_State *l, json_parse_t *json, 718 | json_token_t *token); 719 | 720 | static int hexdigit2int(char hex) 721 | { 722 | if ('0' <= hex && hex <= '9') 723 | return hex - '0'; 724 | 725 | /* Force lowercase */ 726 | hex |= 0x20; 727 | if ('a' <= hex && hex <= 'f') 728 | return 10 + hex - 'a'; 729 | 730 | return -1; 731 | } 732 | 733 | static int decode_hex4(const char *hex) 734 | { 735 | int digit[4]; 736 | int i; 737 | 738 | /* Convert ASCII hex digit to numeric digit 739 | * Note: this returns an error for invalid hex digits, including 740 | * NULL */ 741 | for (i = 0; i < 4; i++) { 742 | digit[i] = hexdigit2int(hex[i]); 743 | if (digit[i] < 0) { 744 | return -1; 745 | } 746 | } 747 | 748 | return (digit[0] << 12) + 749 | (digit[1] << 8) + 750 | (digit[2] << 4) + 751 | digit[3]; 752 | } 753 | 754 | /* Converts a Unicode codepoint to UTF-8. 755 | * Returns UTF-8 string length, and up to 4 bytes in *utf8 */ 756 | static int codepoint_to_utf8(char *utf8, int codepoint) 757 | { 758 | /* 0xxxxxxx */ 759 | if (codepoint <= 0x7F) { 760 | utf8[0] = codepoint; 761 | return 1; 762 | } 763 | 764 | /* 110xxxxx 10xxxxxx */ 765 | if (codepoint <= 0x7FF) { 766 | utf8[0] = (codepoint >> 6) | 0xC0; 767 | utf8[1] = (codepoint & 0x3F) | 0x80; 768 | return 2; 769 | } 770 | 771 | /* 1110xxxx 10xxxxxx 10xxxxxx */ 772 | if (codepoint <= 0xFFFF) { 773 | utf8[0] = (codepoint >> 12) | 0xE0; 774 | utf8[1] = ((codepoint >> 6) & 0x3F) | 0x80; 775 | utf8[2] = (codepoint & 0x3F) | 0x80; 776 | return 3; 777 | } 778 | 779 | /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ 780 | if (codepoint <= 0x1FFFFF) { 781 | utf8[0] = (codepoint >> 18) | 0xF0; 782 | utf8[1] = ((codepoint >> 12) & 0x3F) | 0x80; 783 | utf8[2] = ((codepoint >> 6) & 0x3F) | 0x80; 784 | utf8[3] = (codepoint & 0x3F) | 0x80; 785 | return 4; 786 | } 787 | 788 | return 0; 789 | } 790 | 791 | 792 | /* Called when index pointing to beginning of UTF-16 code escape: \uXXXX 793 | * \u is guaranteed to exist, but the remaining hex characters may be 794 | * missing. 795 | * Translate to UTF-8 and append to temporary token string. 796 | * Must advance index to the next character to be processed. 797 | * Returns: 0 success 798 | * -1 error 799 | */ 800 | static int json_append_unicode_escape(json_parse_t *json) 801 | { 802 | char utf8[4]; /* Surrogate pairs require 4 UTF-8 bytes */ 803 | int codepoint; 804 | int surrogate_low; 805 | int len; 806 | int escape_len = 6; 807 | 808 | /* Fetch UTF-16 code unit */ 809 | codepoint = decode_hex4(&json->data[json->index + 2]); 810 | if (codepoint < 0) 811 | return -1; 812 | 813 | /* UTF-16 surrogate pairs take the following 2 byte form: 814 | * 11011 x yyyyyyyyyy 815 | * When x = 0: y is the high 10 bits of the codepoint 816 | * x = 1: y is the low 10 bits of the codepoint 817 | * 818 | * Check for a surrogate pair (high or low) */ 819 | if ((codepoint & 0xF800) == 0xD800) { 820 | /* Error if the 1st surrogate is not high */ 821 | if (codepoint & 0x400) 822 | return -1; 823 | 824 | /* Ensure the next code is a unicode escape */ 825 | if (json->data[json->index + escape_len] != '\\' || 826 | json->data[json->index + escape_len + 1] != 'u') { 827 | return -1; 828 | } 829 | 830 | /* Fetch the next codepoint */ 831 | surrogate_low = decode_hex4(&json->data[json->index + 2 + escape_len]); 832 | if (surrogate_low < 0) 833 | return -1; 834 | 835 | /* Error if the 2nd code is not a low surrogate */ 836 | if ((surrogate_low & 0xFC00) != 0xDC00) 837 | return -1; 838 | 839 | /* Calculate Unicode codepoint */ 840 | codepoint = (codepoint & 0x3FF) << 10; 841 | surrogate_low &= 0x3FF; 842 | codepoint = (codepoint | surrogate_low) + 0x10000; 843 | escape_len = 12; 844 | } 845 | 846 | /* Convert codepoint to UTF-8 */ 847 | len = codepoint_to_utf8(utf8, codepoint); 848 | if (!len) 849 | return -1; 850 | 851 | /* Append bytes and advance parse index */ 852 | strbuf_append_mem_unsafe(json->tmp, utf8, len); 853 | json->index += escape_len; 854 | 855 | return 0; 856 | } 857 | 858 | static void json_set_token_error(json_token_t *token, json_parse_t *json, 859 | const char *errtype) 860 | { 861 | token->type = T_ERROR; 862 | token->index = json->index; 863 | token->value.string = errtype; 864 | } 865 | 866 | static void json_next_string_token(json_parse_t *json, json_token_t *token) 867 | { 868 | char *escape2char = json->cfg->escape2char; 869 | char ch; 870 | 871 | /* Caller must ensure a string is next */ 872 | assert(json->data[json->index] == '"'); 873 | 874 | /* Skip " */ 875 | json->index++; 876 | 877 | /* json->tmp is the temporary strbuf used to accumulate the 878 | * decoded string value. */ 879 | strbuf_reset(json->tmp); 880 | while ((ch = json->data[json->index]) != '"') { 881 | if (!ch) { 882 | /* Premature end of the string */ 883 | json_set_token_error(token, json, "unexpected end of string"); 884 | return; 885 | } 886 | 887 | /* Handle escapes */ 888 | if (ch == '\\') { 889 | /* Fetch escape character */ 890 | ch = json->data[json->index + 1]; 891 | 892 | /* Translate escape code and append to tmp string */ 893 | ch = escape2char[(unsigned char)ch]; 894 | if (ch == 'u') { 895 | if (json_append_unicode_escape(json) == 0) 896 | continue; 897 | 898 | json_set_token_error(token, json, 899 | "invalid unicode escape code"); 900 | return; 901 | } 902 | if (!ch) { 903 | json_set_token_error(token, json, "invalid escape code"); 904 | return; 905 | } 906 | 907 | /* Skip '\' */ 908 | json->index++; 909 | } 910 | /* Append normal character or translated single character 911 | * Unicode escapes are handled above */ 912 | strbuf_append_char_unsafe(json->tmp, ch); 913 | json->index++; 914 | } 915 | json->index++; /* Eat final quote (") */ 916 | 917 | strbuf_ensure_null(json->tmp); 918 | 919 | token->type = T_STRING; 920 | token->value.string = strbuf_string(json->tmp, &token->string_len); 921 | } 922 | 923 | /* JSON numbers should take the following form: 924 | * -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)? 925 | * 926 | * json_next_number_token() uses strtod() which allows other forms: 927 | * - numbers starting with '+' 928 | * - NaN, -NaN, infinity, -infinity 929 | * - hexidecimal numbers 930 | * - numbers with leading zeros 931 | * 932 | * json_is_invalid_number() detects "numbers" which may pass strtod()'s 933 | * error checking, but should not be allowed with strict JSON. 934 | * 935 | * json_is_invalid_number() may pass numbers which cause strtod() 936 | * to generate an error. 937 | */ 938 | static int json_is_invalid_number(json_parse_t *json) 939 | { 940 | int i = json->index; 941 | 942 | /* Reject numbers starting with + */ 943 | if (json->data[i] == '+') 944 | return 1; 945 | 946 | /* Skip minus sign if it exists */ 947 | if (json->data[i] == '-') 948 | i++; 949 | 950 | /* Reject numbers starting with 0x, or leading zeros */ 951 | if (json->data[i] == '0') { 952 | int ch2 = json->data[i + 1]; 953 | 954 | if ((ch2 | 0x20) == 'x' || /* Hex */ 955 | ('0' <= ch2 && ch2 <= '9')) /* Leading zero */ 956 | return 1; 957 | 958 | return 0; 959 | } else if (json->data[i] <= '9') { 960 | return 0; /* Ordinary number */ 961 | } 962 | 963 | 964 | /* Reject inf/nan */ 965 | if (!strncasecmp(&json->data[i], "inf", 3)) 966 | return 1; 967 | if (!strncasecmp(&json->data[i], "nan", 3)) 968 | return 1; 969 | 970 | /* Pass all other numbers which may still be invalid, but 971 | * strtod() will catch them. */ 972 | return 0; 973 | } 974 | 975 | static void json_next_number_token(json_parse_t *json, json_token_t *token) 976 | { 977 | const char *startptr; 978 | char *endptr; 979 | 980 | token->type = T_NUMBER; 981 | startptr = &json->data[json->index]; 982 | token->value.number = strtod(&json->data[json->index], &endptr); 983 | if (startptr == endptr) 984 | json_set_token_error(token, json, "invalid number"); 985 | else 986 | json->index += endptr - startptr; /* Skip the processed number */ 987 | 988 | return; 989 | } 990 | 991 | /* Fills in the token struct. 992 | * T_STRING will return a pointer to the json_parse_t temporary string 993 | * T_ERROR will leave the json->index pointer at the error. 994 | */ 995 | static void json_next_token(json_parse_t *json, json_token_t *token) 996 | { 997 | json_token_type_t *ch2token = json->cfg->ch2token; 998 | int ch; 999 | 1000 | /* Eat whitespace. FIXME: UGLY */ 1001 | token->type = ch2token[(unsigned char)json->data[json->index]]; 1002 | while (token->type == T_WHITESPACE) 1003 | token->type = ch2token[(unsigned char)json->data[++json->index]]; 1004 | 1005 | token->index = json->index; 1006 | 1007 | /* Don't advance the pointer for an error or the end */ 1008 | if (token->type == T_ERROR) { 1009 | json_set_token_error(token, json, "invalid token"); 1010 | return; 1011 | } 1012 | 1013 | if (token->type == T_END) { 1014 | return; 1015 | } 1016 | 1017 | /* Found a known single character token, advance index and return */ 1018 | if (token->type != T_UNKNOWN) { 1019 | json->index++; 1020 | return; 1021 | } 1022 | 1023 | /* Process characters which triggered T_UNKNOWN */ 1024 | ch = json->data[json->index]; 1025 | 1026 | /* Must use strncmp() to match the front of the JSON string. 1027 | * JSON identifier must be lowercase. 1028 | * When strict_numbers if disabled, either case is allowed for 1029 | * Infinity/NaN (since we are no longer following the spec..) */ 1030 | if (ch == '"') { 1031 | json_next_string_token(json, token); 1032 | return; 1033 | } else if (ch == '-' || ('0' <= ch && ch <= '9')) { 1034 | if (json->cfg->decode_refuse_badnum && json_is_invalid_number(json)) { 1035 | json_set_token_error(token, json, "invalid number"); 1036 | return; 1037 | } 1038 | json_next_number_token(json, token); 1039 | return; 1040 | } else if (!strncmp(&json->data[json->index], "true", 4)) { 1041 | token->type = T_BOOLEAN; 1042 | token->value.boolean = 1; 1043 | json->index += 4; 1044 | return; 1045 | } else if (!strncmp(&json->data[json->index], "false", 5)) { 1046 | token->type = T_BOOLEAN; 1047 | token->value.boolean = 0; 1048 | json->index += 5; 1049 | return; 1050 | } else if (!strncmp(&json->data[json->index], "null", 4)) { 1051 | token->type = T_NULL; 1052 | json->index += 4; 1053 | return; 1054 | } else if (!json->cfg->decode_refuse_badnum && 1055 | json_is_invalid_number(json)) { 1056 | /* When refuse_badnum is disabled, only attempt to process 1057 | * numbers we know are invalid JSON (Inf, NaN, hex) 1058 | * This is required to generate an appropriate token error, 1059 | * otherwise all bad tokens will register as "invalid number" 1060 | */ 1061 | json_next_number_token(json, token); 1062 | return; 1063 | } 1064 | 1065 | /* Token starts with t/f/n but isn't recognised above. */ 1066 | json_set_token_error(token, json, "invalid token"); 1067 | } 1068 | 1069 | /* This function does not return. 1070 | * DO NOT CALL WITH DYNAMIC MEMORY ALLOCATED. 1071 | * The only supported exception is the temporary parser string 1072 | * json->tmp struct. 1073 | * json and token should exist on the stack somewhere. 1074 | * luaL_error() will long_jmp and release the stack */ 1075 | static void json_throw_parse_error(lua_State *l, json_parse_t *json, 1076 | const char *exp, json_token_t *token) 1077 | { 1078 | const char *found; 1079 | 1080 | strbuf_free(json->tmp); 1081 | 1082 | if (token->type == T_ERROR) 1083 | found = token->value.string; 1084 | else 1085 | found = json_token_type_name[token->type]; 1086 | 1087 | /* Note: token->index is 0 based, display starting from 1 */ 1088 | luaL_error(l, "Expected %s but found %s at character %d", 1089 | exp, found, token->index + 1); 1090 | } 1091 | 1092 | static void json_decode_checkstack(lua_State *l, json_parse_t *json, int n) 1093 | { 1094 | if (lua_checkstack(l, n)) 1095 | return; 1096 | 1097 | strbuf_free(json->tmp); 1098 | luaL_error(l, "Too many nested data structures"); 1099 | } 1100 | 1101 | static void json_parse_object_context(lua_State *l, json_parse_t *json) 1102 | { 1103 | json_token_t token; 1104 | 1105 | /* 3 slots required: 1106 | * .., table, key, value */ 1107 | json_decode_checkstack(l, json, 3); 1108 | 1109 | lua_newtable(l); 1110 | 1111 | json_next_token(json, &token); 1112 | 1113 | /* Handle empty objects */ 1114 | if (token.type == T_OBJ_END) { 1115 | return; 1116 | } 1117 | 1118 | while (1) { 1119 | if (token.type != T_STRING) 1120 | json_throw_parse_error(l, json, "object key string", &token); 1121 | 1122 | /* Push key */ 1123 | lua_pushlstring(l, token.value.string, token.string_len); 1124 | 1125 | json_next_token(json, &token); 1126 | if (token.type != T_COLON) 1127 | json_throw_parse_error(l, json, "colon", &token); 1128 | 1129 | /* Fetch value */ 1130 | json_next_token(json, &token); 1131 | json_process_value(l, json, &token); 1132 | 1133 | /* Set key = value */ 1134 | lua_rawset(l, -3); 1135 | 1136 | json_next_token(json, &token); 1137 | 1138 | if (token.type == T_OBJ_END) 1139 | return; 1140 | 1141 | if (token.type != T_COMMA) 1142 | json_throw_parse_error(l, json, "comma or object end", &token); 1143 | 1144 | json_next_token(json, &token); 1145 | } 1146 | } 1147 | 1148 | /* Handle the array context */ 1149 | static void json_parse_array_context(lua_State *l, json_parse_t *json) 1150 | { 1151 | json_token_t token; 1152 | int i; 1153 | 1154 | /* 2 slots required: 1155 | * .., table, value */ 1156 | json_decode_checkstack(l, json, 2); 1157 | 1158 | lua_newtable(l); 1159 | 1160 | json_next_token(json, &token); 1161 | 1162 | /* Handle empty arrays */ 1163 | if (token.type == T_ARR_END) 1164 | return; 1165 | 1166 | for (i = 1; ; i++) { 1167 | json_process_value(l, json, &token); 1168 | lua_rawseti(l, -2, i); /* arr[i] = value */ 1169 | 1170 | json_next_token(json, &token); 1171 | 1172 | if (token.type == T_ARR_END) 1173 | return; 1174 | 1175 | if (token.type != T_COMMA) 1176 | json_throw_parse_error(l, json, "comma or array end", &token); 1177 | 1178 | json_next_token(json, &token); 1179 | } 1180 | } 1181 | 1182 | /* Handle the "value" context */ 1183 | static void json_process_value(lua_State *l, json_parse_t *json, 1184 | json_token_t *token) 1185 | { 1186 | switch (token->type) { 1187 | case T_STRING: 1188 | lua_pushlstring(l, token->value.string, token->string_len); 1189 | break;; 1190 | case T_NUMBER: 1191 | lua_pushnumber(l, token->value.number); 1192 | break;; 1193 | case T_BOOLEAN: 1194 | lua_pushboolean(l, token->value.boolean); 1195 | break;; 1196 | case T_OBJ_BEGIN: 1197 | json_parse_object_context(l, json); 1198 | break;; 1199 | case T_ARR_BEGIN: 1200 | json_parse_array_context(l, json); 1201 | break;; 1202 | case T_NULL: 1203 | /* In Lua, setting "t[k] = nil" will delete k from the table. 1204 | * Hence a NULL pointer lightuserdata object is used instead */ 1205 | lua_pushlightuserdata(l, NULL); 1206 | break;; 1207 | default: 1208 | json_throw_parse_error(l, json, "value", token); 1209 | } 1210 | } 1211 | 1212 | /* json_text must be null terminated string */ 1213 | static void lua_json_decode(lua_State *l, const char *json_text, int json_len) 1214 | { 1215 | json_parse_t json; 1216 | json_token_t token; 1217 | 1218 | json.cfg = json_fetch_config(l); 1219 | json.data = json_text; 1220 | json.index = 0; 1221 | 1222 | /* Ensure the temporary buffer can hold the entire string. 1223 | * This means we no longer need to do length checks since the decoded 1224 | * string must be smaller than the entire json string */ 1225 | json.tmp = strbuf_new(json_len); 1226 | 1227 | json_next_token(&json, &token); 1228 | json_process_value(l, &json, &token); 1229 | 1230 | /* Ensure there is no more input left */ 1231 | json_next_token(&json, &token); 1232 | 1233 | if (token.type != T_END) 1234 | json_throw_parse_error(l, &json, "the end", &token); 1235 | 1236 | strbuf_free(json.tmp); 1237 | } 1238 | 1239 | static int json_decode(lua_State *l) 1240 | { 1241 | const char *json; 1242 | size_t len; 1243 | 1244 | json_verify_arg_count(l, 1); 1245 | 1246 | json = luaL_checklstring(l, 1, &len); 1247 | 1248 | /* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3) 1249 | * 1250 | * CJSON can support any simple data type, hence only the first 1251 | * character is guaranteed to be ASCII (at worst: '"'). This is 1252 | * still enough to detect whether the wrong encoding is in use. */ 1253 | if (len >= 2 && (!json[0] || !json[1])) 1254 | luaL_error(l, "JSON parser does not support UTF-16 or UTF-32"); 1255 | 1256 | lua_json_decode(l, json, len); 1257 | 1258 | return 1; 1259 | } 1260 | 1261 | /* ===== INITIALISATION ===== */ 1262 | 1263 | int luaopen_cjson(lua_State *l) 1264 | { 1265 | luaL_Reg reg[] = { 1266 | { "encode", json_encode }, 1267 | { "decode", json_decode }, 1268 | { "encode_sparse_array", json_cfg_encode_sparse_array }, 1269 | { "encode_max_depth", json_cfg_encode_max_depth }, 1270 | { "encode_number_precision", json_cfg_encode_number_precision }, 1271 | { "encode_keep_buffer", json_cfg_encode_keep_buffer }, 1272 | { "refuse_invalid_numbers", json_cfg_refuse_invalid_numbers }, 1273 | { NULL, NULL } 1274 | }; 1275 | 1276 | /* Use json_fetch_config as a pointer. 1277 | * It's faster than using a config string, and more unique */ 1278 | lua_pushlightuserdata(l, &json_config_key); 1279 | json_create_config(l); 1280 | lua_settable(l, LUA_REGISTRYINDEX); 1281 | 1282 | luaL_register(l, "cjson", reg); 1283 | 1284 | /* Set cjson.null */ 1285 | lua_pushlightuserdata(l, NULL); 1286 | lua_setfield(l, -2, "null"); 1287 | 1288 | /* Set cjson.version */ 1289 | lua_pushliteral(l, VERSION); 1290 | lua_setfield(l, -2, "version"); 1291 | 1292 | /* Return cjson table */ 1293 | return 1; 1294 | } 1295 | 1296 | /* vi:ai et sw=4 ts=4: 1297 | */ 1298 | -------------------------------------------------------------------------------- /performance.txt: -------------------------------------------------------------------------------- 1 | JSON Performance Comparison under Lua 2 | ------------------------------------- 3 | 4 | The following JSON packages for Lua were tested: 5 | 6 | - DKJSON 1.0: One of the fastest pure Lua JSON implementations. 7 | - LuaJSON 1.0: A mixed Lua/C JSON implementation using LPeg. 8 | - Lua YAJL 2.0: A Lua wrapper for the YAJL JSON library. 9 | - CJSON 1.0.2: Pure C. 10 | 11 | LuaJSON 1.2.2 appeared to be slower during initial testing, so 1.0 was 12 | used instead. 13 | 14 | The following Lua implementations were used for this comparison: 15 | - Lua 5.1.4 16 | - LuaJIT 2.0.0-beta7 17 | 18 | The example JSON files used were taken from http://json.org/ and 19 | RFC 4627. 20 | 21 | DKJSON 1.0 LuaJSON 1.0 LuaYAJL 2.0 CJSON 1.0.2 22 | == Decoding == Lua LuaJIT Lua LuaJIT Lua LuaJIT Lua LuaJIT 23 | example1 1.0x 2.0x 3.4x 4.0x 7.1x 10.1x 13.2x 19.4x 24 | example2 1.0x 2.1x 3.5x 4.5x 6.6x 9.8x 12.7x 20.0x 25 | example3 1.0x 2.0x 3.9x 4.7x 7.0x 9.4x 13.2x 19.3x 26 | example4 1.0x 1.9x 3.7x 4.4x 7.4x 10.6x 11.8x 18.0x 27 | example5 1.0x 2.1x 4.0x 4.7x 7.7x 11.4x 14.7x 22.3x 28 | numbers 1.0x 2.1x 2.1x 3.4x 4.6x 5.7x 8.6x 10.4x 29 | rfc-example1 1.0x 2.0x 3.2x 4.2x 5.8x 8.2x 11.8x 17.7x 30 | rfc-example2 1.0x 2.0x 3.6x 4.5x 7.0x 9.3x 14.5x 20.5x 31 | types 1.0x 2.1x 2.3x 3.5x 4.9x 7.6x 10.7x 17.2x 32 | == Average ==> 1.0x 2.0x 3.3x 4.2x 6.4x 9.1x 12.4x 18.3x 33 | 34 | == Encoding == 35 | example1 1.0x 1.9x 0.6x 1.4x 3.5x 5.6x 23.1x 29.1x 36 | example2 1.0x 2.0x 0.5x 1.2x 3.0x 4.9x 23.4x 28.5x 37 | example3 1.0x 1.8x 0.6x 1.3x 3.0x 4.7x 13.3x 14.9x 38 | example4 1.0x 1.7x 0.7x 1.5x 4.2x 6.6x 15.4x 18.5x 39 | example5 1.0x 2.0x 0.6x 1.4x 3.4x 5.5x 22.7x 25.5x 40 | numbers 1.0x 2.4x 0.4x 0.9x 1.4x 2.1x 4.3x 4.6x 41 | rfc-example1 1.0x 1.9x 0.5x 1.2x 2.3x 3.6x 8.8x 9.6x 42 | rfc-example2 1.0x 1.9x 0.6x 1.3x 2.8x 4.3x 10.7x 10.7x 43 | types 1.0x 2.4x 0.3x 0.7x 1.4x 2.3x 11.7x 11.3x 44 | == Average ==> 1.0x 2.0x 0.6x 1.2x 2.8x 4.4x 14.8x 17.0x 45 | 46 | 47 | Number conversion is a relatively expensive operation. Number heavy 48 | JSON will show less performance difference between libraries. 49 | 50 | Performance can vary widely between platforms and data sets. These 51 | results should only be considered as a rough guide. 52 | -------------------------------------------------------------------------------- /rfc4627.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Network Working Group D. Crockford 8 | Request for Comments: 4627 JSON.org 9 | Category: Informational July 2006 10 | 11 | 12 | The application/json Media Type for JavaScript Object Notation (JSON) 13 | 14 | Status of This Memo 15 | 16 | This memo provides information for the Internet community. It does 17 | not specify an Internet standard of any kind. Distribution of this 18 | memo is unlimited. 19 | 20 | Copyright Notice 21 | 22 | Copyright (C) The Internet Society (2006). 23 | 24 | Abstract 25 | 26 | JavaScript Object Notation (JSON) is a lightweight, text-based, 27 | language-independent data interchange format. It was derived from 28 | the ECMAScript Programming Language Standard. JSON defines a small 29 | set of formatting rules for the portable representation of structured 30 | data. 31 | 32 | 1. Introduction 33 | 34 | JavaScript Object Notation (JSON) is a text format for the 35 | serialization of structured data. It is derived from the object 36 | literals of JavaScript, as defined in the ECMAScript Programming 37 | Language Standard, Third Edition [ECMA]. 38 | 39 | JSON can represent four primitive types (strings, numbers, booleans, 40 | and null) and two structured types (objects and arrays). 41 | 42 | A string is a sequence of zero or more Unicode characters [UNICODE]. 43 | 44 | An object is an unordered collection of zero or more name/value 45 | pairs, where a name is a string and a value is a string, number, 46 | boolean, null, object, or array. 47 | 48 | An array is an ordered sequence of zero or more values. 49 | 50 | The terms "object" and "array" come from the conventions of 51 | JavaScript. 52 | 53 | JSON's design goals were for it to be minimal, portable, textual, and 54 | a subset of JavaScript. 55 | 56 | 57 | 58 | Crockford Informational [Page 1] 59 | 60 | RFC 4627 JSON July 2006 61 | 62 | 63 | 1.1. Conventions Used in This Document 64 | 65 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 66 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 67 | document are to be interpreted as described in [RFC2119]. 68 | 69 | The grammatical rules in this document are to be interpreted as 70 | described in [RFC4234]. 71 | 72 | 2. JSON Grammar 73 | 74 | A JSON text is a sequence of tokens. The set of tokens includes six 75 | structural characters, strings, numbers, and three literal names. 76 | 77 | A JSON text is a serialized object or array. 78 | 79 | JSON-text = object / array 80 | 81 | These are the six structural characters: 82 | 83 | begin-array = ws %x5B ws ; [ left square bracket 84 | 85 | begin-object = ws %x7B ws ; { left curly bracket 86 | 87 | end-array = ws %x5D ws ; ] right square bracket 88 | 89 | end-object = ws %x7D ws ; } right curly bracket 90 | 91 | name-separator = ws %x3A ws ; : colon 92 | 93 | value-separator = ws %x2C ws ; , comma 94 | 95 | Insignificant whitespace is allowed before or after any of the six 96 | structural characters. 97 | 98 | ws = *( 99 | %x20 / ; Space 100 | %x09 / ; Horizontal tab 101 | %x0A / ; Line feed or New line 102 | %x0D ; Carriage return 103 | ) 104 | 105 | 2.1. Values 106 | 107 | A JSON value MUST be an object, array, number, or string, or one of 108 | the following three literal names: 109 | 110 | false null true 111 | 112 | 113 | 114 | Crockford Informational [Page 2] 115 | 116 | RFC 4627 JSON July 2006 117 | 118 | 119 | The literal names MUST be lowercase. No other literal names are 120 | allowed. 121 | 122 | value = false / null / true / object / array / number / string 123 | 124 | false = %x66.61.6c.73.65 ; false 125 | 126 | null = %x6e.75.6c.6c ; null 127 | 128 | true = %x74.72.75.65 ; true 129 | 130 | 2.2. Objects 131 | 132 | An object structure is represented as a pair of curly brackets 133 | surrounding zero or more name/value pairs (or members). A name is a 134 | string. A single colon comes after each name, separating the name 135 | from the value. A single comma separates a value from a following 136 | name. The names within an object SHOULD be unique. 137 | 138 | object = begin-object [ member *( value-separator member ) ] 139 | end-object 140 | 141 | member = string name-separator value 142 | 143 | 2.3. Arrays 144 | 145 | An array structure is represented as square brackets surrounding zero 146 | or more values (or elements). Elements are separated by commas. 147 | 148 | array = begin-array [ value *( value-separator value ) ] end-array 149 | 150 | 2.4. Numbers 151 | 152 | The representation of numbers is similar to that used in most 153 | programming languages. A number contains an integer component that 154 | may be prefixed with an optional minus sign, which may be followed by 155 | a fraction part and/or an exponent part. 156 | 157 | Octal and hex forms are not allowed. Leading zeros are not allowed. 158 | 159 | A fraction part is a decimal point followed by one or more digits. 160 | 161 | An exponent part begins with the letter E in upper or lowercase, 162 | which may be followed by a plus or minus sign. The E and optional 163 | sign are followed by one or more digits. 164 | 165 | Numeric values that cannot be represented as sequences of digits 166 | (such as Infinity and NaN) are not permitted. 167 | 168 | 169 | 170 | Crockford Informational [Page 3] 171 | 172 | RFC 4627 JSON July 2006 173 | 174 | 175 | number = [ minus ] int [ frac ] [ exp ] 176 | 177 | decimal-point = %x2E ; . 178 | 179 | digit1-9 = %x31-39 ; 1-9 180 | 181 | e = %x65 / %x45 ; e E 182 | 183 | exp = e [ minus / plus ] 1*DIGIT 184 | 185 | frac = decimal-point 1*DIGIT 186 | 187 | int = zero / ( digit1-9 *DIGIT ) 188 | 189 | minus = %x2D ; - 190 | 191 | plus = %x2B ; + 192 | 193 | zero = %x30 ; 0 194 | 195 | 2.5. Strings 196 | 197 | The representation of strings is similar to conventions used in the C 198 | family of programming languages. A string begins and ends with 199 | quotation marks. All Unicode characters may be placed within the 200 | quotation marks except for the characters that must be escaped: 201 | quotation mark, reverse solidus, and the control characters (U+0000 202 | through U+001F). 203 | 204 | Any character may be escaped. If the character is in the Basic 205 | Multilingual Plane (U+0000 through U+FFFF), then it may be 206 | represented as a six-character sequence: a reverse solidus, followed 207 | by the lowercase letter u, followed by four hexadecimal digits that 208 | encode the character's code point. The hexadecimal letters A though 209 | F can be upper or lowercase. So, for example, a string containing 210 | only a single reverse solidus character may be represented as 211 | "\u005C". 212 | 213 | Alternatively, there are two-character sequence escape 214 | representations of some popular characters. So, for example, a 215 | string containing only a single reverse solidus character may be 216 | represented more compactly as "\\". 217 | 218 | To escape an extended character that is not in the Basic Multilingual 219 | Plane, the character is represented as a twelve-character sequence, 220 | encoding the UTF-16 surrogate pair. So, for example, a string 221 | containing only the G clef character (U+1D11E) may be represented as 222 | "\uD834\uDD1E". 223 | 224 | 225 | 226 | Crockford Informational [Page 4] 227 | 228 | RFC 4627 JSON July 2006 229 | 230 | 231 | string = quotation-mark *char quotation-mark 232 | 233 | char = unescaped / 234 | escape ( 235 | %x22 / ; " quotation mark U+0022 236 | %x5C / ; \ reverse solidus U+005C 237 | %x2F / ; / solidus U+002F 238 | %x62 / ; b backspace U+0008 239 | %x66 / ; f form feed U+000C 240 | %x6E / ; n line feed U+000A 241 | %x72 / ; r carriage return U+000D 242 | %x74 / ; t tab U+0009 243 | %x75 4HEXDIG ) ; uXXXX U+XXXX 244 | 245 | escape = %x5C ; \ 246 | 247 | quotation-mark = %x22 ; " 248 | 249 | unescaped = %x20-21 / %x23-5B / %x5D-10FFFF 250 | 251 | 3. Encoding 252 | 253 | JSON text SHALL be encoded in Unicode. The default encoding is 254 | UTF-8. 255 | 256 | Since the first two characters of a JSON text will always be ASCII 257 | characters [RFC0020], it is possible to determine whether an octet 258 | stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking 259 | at the pattern of nulls in the first four octets. 260 | 261 | 00 00 00 xx UTF-32BE 262 | 00 xx 00 xx UTF-16BE 263 | xx 00 00 00 UTF-32LE 264 | xx 00 xx 00 UTF-16LE 265 | xx xx xx xx UTF-8 266 | 267 | 4. Parsers 268 | 269 | A JSON parser transforms a JSON text into another representation. A 270 | JSON parser MUST accept all texts that conform to the JSON grammar. 271 | A JSON parser MAY accept non-JSON forms or extensions. 272 | 273 | An implementation may set limits on the size of texts that it 274 | accepts. An implementation may set limits on the maximum depth of 275 | nesting. An implementation may set limits on the range of numbers. 276 | An implementation may set limits on the length and character contents 277 | of strings. 278 | 279 | 280 | 281 | 282 | Crockford Informational [Page 5] 283 | 284 | RFC 4627 JSON July 2006 285 | 286 | 287 | 5. Generators 288 | 289 | A JSON generator produces JSON text. The resulting text MUST 290 | strictly conform to the JSON grammar. 291 | 292 | 6. IANA Considerations 293 | 294 | The MIME media type for JSON text is application/json. 295 | 296 | Type name: application 297 | 298 | Subtype name: json 299 | 300 | Required parameters: n/a 301 | 302 | Optional parameters: n/a 303 | 304 | Encoding considerations: 8bit if UTF-8; binary if UTF-16 or UTF-32 305 | 306 | JSON may be represented using UTF-8, UTF-16, or UTF-32. When JSON 307 | is written in UTF-8, JSON is 8bit compatible. When JSON is 308 | written in UTF-16 or UTF-32, the binary content-transfer-encoding 309 | must be used. 310 | 311 | Security considerations: 312 | 313 | Generally there are security issues with scripting languages. JSON 314 | is a subset of JavaScript, but it is a safe subset that excludes 315 | assignment and invocation. 316 | 317 | A JSON text can be safely passed into JavaScript's eval() function 318 | (which compiles and executes a string) if all the characters not 319 | enclosed in strings are in the set of characters that form JSON 320 | tokens. This can be quickly determined in JavaScript with two 321 | regular expressions and calls to the test and replace methods. 322 | 323 | var my_JSON_object = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test( 324 | text.replace(/"(\\.|[^"\\])*"/g, ''))) && 325 | eval('(' + text + ')'); 326 | 327 | Interoperability considerations: n/a 328 | 329 | Published specification: RFC 4627 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | Crockford Informational [Page 6] 339 | 340 | RFC 4627 JSON July 2006 341 | 342 | 343 | Applications that use this media type: 344 | 345 | JSON has been used to exchange data between applications written 346 | in all of these programming languages: ActionScript, C, C#, 347 | ColdFusion, Common Lisp, E, Erlang, Java, JavaScript, Lua, 348 | Objective CAML, Perl, PHP, Python, Rebol, Ruby, and Scheme. 349 | 350 | Additional information: 351 | 352 | Magic number(s): n/a 353 | File extension(s): .json 354 | Macintosh file type code(s): TEXT 355 | 356 | Person & email address to contact for further information: 357 | Douglas Crockford 358 | douglas@crockford.com 359 | 360 | Intended usage: COMMON 361 | 362 | Restrictions on usage: none 363 | 364 | Author: 365 | Douglas Crockford 366 | douglas@crockford.com 367 | 368 | Change controller: 369 | Douglas Crockford 370 | douglas@crockford.com 371 | 372 | 7. Security Considerations 373 | 374 | See Security Considerations in Section 6. 375 | 376 | 8. Examples 377 | 378 | This is a JSON object: 379 | 380 | { 381 | "Image": { 382 | "Width": 800, 383 | "Height": 600, 384 | "Title": "View from 15th Floor", 385 | "Thumbnail": { 386 | "Url": "http://www.example.com/image/481989943", 387 | "Height": 125, 388 | "Width": "100" 389 | }, 390 | "IDs": [116, 943, 234, 38793] 391 | 392 | 393 | 394 | Crockford Informational [Page 7] 395 | 396 | RFC 4627 JSON July 2006 397 | 398 | 399 | } 400 | } 401 | 402 | Its Image member is an object whose Thumbnail member is an object 403 | and whose IDs member is an array of numbers. 404 | 405 | This is a JSON array containing two objects: 406 | 407 | [ 408 | { 409 | "precision": "zip", 410 | "Latitude": 37.7668, 411 | "Longitude": -122.3959, 412 | "Address": "", 413 | "City": "SAN FRANCISCO", 414 | "State": "CA", 415 | "Zip": "94107", 416 | "Country": "US" 417 | }, 418 | { 419 | "precision": "zip", 420 | "Latitude": 37.371991, 421 | "Longitude": -122.026020, 422 | "Address": "", 423 | "City": "SUNNYVALE", 424 | "State": "CA", 425 | "Zip": "94085", 426 | "Country": "US" 427 | } 428 | ] 429 | 430 | 9. References 431 | 432 | 9.1. Normative References 433 | 434 | [ECMA] European Computer Manufacturers Association, "ECMAScript 435 | Language Specification 3rd Edition", December 1999, 436 | . 438 | 439 | [RFC0020] Cerf, V., "ASCII format for network interchange", RFC 20, 440 | October 1969. 441 | 442 | [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate 443 | Requirement Levels", BCP 14, RFC 2119, March 1997. 444 | 445 | [RFC4234] Crocker, D. and P. Overell, "Augmented BNF for Syntax 446 | Specifications: ABNF", RFC 4234, October 2005. 447 | 448 | 449 | 450 | Crockford Informational [Page 8] 451 | 452 | RFC 4627 JSON July 2006 453 | 454 | 455 | [UNICODE] The Unicode Consortium, "The Unicode Standard Version 4.0", 456 | 2003, . 457 | 458 | Author's Address 459 | 460 | Douglas Crockford 461 | JSON.org 462 | EMail: douglas@crockford.com 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | Crockford Informational [Page 9] 507 | 508 | RFC 4627 JSON July 2006 509 | 510 | 511 | Full Copyright Statement 512 | 513 | Copyright (C) The Internet Society (2006). 514 | 515 | This document is subject to the rights, licenses and restrictions 516 | contained in BCP 78, and except as set forth therein, the authors 517 | retain all their rights. 518 | 519 | This document and the information contained herein are provided on an 520 | "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS 521 | OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET 522 | ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, 523 | INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE 524 | INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED 525 | WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. 526 | 527 | Intellectual Property 528 | 529 | The IETF takes no position regarding the validity or scope of any 530 | Intellectual Property Rights or other rights that might be claimed to 531 | pertain to the implementation or use of the technology described in 532 | this document or the extent to which any license under such rights 533 | might or might not be available; nor does it represent that it has 534 | made any independent effort to identify any such rights. Information 535 | on the procedures with respect to rights in RFC documents can be 536 | found in BCP 78 and BCP 79. 537 | 538 | Copies of IPR disclosures made to the IETF Secretariat and any 539 | assurances of licenses to be made available, or the result of an 540 | attempt made to obtain a general license or permission for the use of 541 | such proprietary rights by implementers or users of this 542 | specification can be obtained from the IETF on-line IPR repository at 543 | http://www.ietf.org/ipr. 544 | 545 | The IETF invites any interested party to bring to its attention any 546 | copyrights, patents or patent applications, or other proprietary 547 | rights that may cover technology that may be required to implement 548 | this standard. Please address the information to the IETF at 549 | ietf-ipr@ietf.org. 550 | 551 | Acknowledgement 552 | 553 | Funding for the RFC Editor function is provided by the IETF 554 | Administrative Support Activity (IASA). 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | Crockford Informational [Page 10] 563 | 564 | -------------------------------------------------------------------------------- /strbuf.c: -------------------------------------------------------------------------------- 1 | /* strbuf - string buffer routines 2 | * 3 | * Copyright (c) 2010-2011 Mark Pulford 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining 6 | * a copy of this software and associated documentation files (the 7 | * "Software"), to deal in the Software without restriction, including 8 | * without limitation the rights to use, copy, modify, merge, publish, 9 | * distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to 11 | * the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "strbuf.h" 31 | 32 | void die(const char *fmt, ...) 33 | { 34 | va_list arg; 35 | 36 | va_start(arg, fmt); 37 | vfprintf(stderr, fmt, arg); 38 | va_end(arg); 39 | fprintf(stderr, "\n"); 40 | 41 | exit(-1); 42 | } 43 | 44 | void strbuf_init(strbuf_t *s, int len) 45 | { 46 | int size; 47 | 48 | if (len <= 0) 49 | size = STRBUF_DEFAULT_SIZE; 50 | else 51 | size = len + 1; /* \0 terminator */ 52 | 53 | s->buf = NULL; 54 | s->size = size; 55 | s->length = 0; 56 | s->increment = STRBUF_DEFAULT_INCREMENT; 57 | s->dynamic = 0; 58 | s->reallocs = 0; 59 | s->debug = 0; 60 | 61 | s->buf = malloc(size); 62 | if (!s->buf) 63 | die("Out of memory"); 64 | 65 | strbuf_ensure_null(s); 66 | } 67 | 68 | strbuf_t *strbuf_new(int len) 69 | { 70 | strbuf_t *s; 71 | 72 | s = malloc(sizeof(strbuf_t)); 73 | if (!s) 74 | die("Out of memory"); 75 | 76 | strbuf_init(s, len); 77 | 78 | /* Dynamic strbuf allocation / deallocation */ 79 | s->dynamic = 1; 80 | 81 | return s; 82 | } 83 | 84 | void strbuf_set_increment(strbuf_t *s, int increment) 85 | { 86 | /* Increment > 0: Linear buffer growth rate 87 | * Increment < -1: Exponential buffer growth rate */ 88 | if (increment == 0 || increment == -1) 89 | die("BUG: Invalid string increment"); 90 | 91 | s->increment = increment; 92 | } 93 | 94 | static inline void debug_stats(strbuf_t *s) 95 | { 96 | if (s->debug) { 97 | fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n", 98 | (long)s, s->reallocs, s->length, s->size); 99 | } 100 | } 101 | 102 | /* If strbuf_t has not been dynamically allocated, strbuf_free() can 103 | * be called any number of times strbuf_init() */ 104 | void strbuf_free(strbuf_t *s) 105 | { 106 | debug_stats(s); 107 | 108 | if (s->buf) { 109 | free(s->buf); 110 | s->buf = NULL; 111 | } 112 | if (s->dynamic) 113 | free(s); 114 | } 115 | 116 | char *strbuf_free_to_string(strbuf_t *s, int *len) 117 | { 118 | char *buf; 119 | 120 | debug_stats(s); 121 | 122 | strbuf_ensure_null(s); 123 | 124 | buf = s->buf; 125 | if (len) 126 | *len = s->length; 127 | 128 | if (s->dynamic) 129 | free(s); 130 | 131 | return buf; 132 | } 133 | 134 | static int calculate_new_size(strbuf_t *s, int len) 135 | { 136 | int reqsize, newsize; 137 | 138 | if (len <= 0) 139 | die("BUG: Invalid strbuf length requested"); 140 | 141 | /* Ensure there is room for optional NULL termination */ 142 | reqsize = len + 1; 143 | 144 | /* If the user has requested to shrink the buffer, do it exactly */ 145 | if (s->size > reqsize) 146 | return reqsize; 147 | 148 | newsize = s->size; 149 | if (s->increment < 0) { 150 | /* Exponential sizing */ 151 | while (newsize < reqsize) 152 | newsize *= -s->increment; 153 | } else { 154 | /* Linear sizing */ 155 | newsize = ((newsize + s->increment - 1) / s->increment) * s->increment; 156 | } 157 | 158 | return newsize; 159 | } 160 | 161 | 162 | /* Ensure strbuf can handle a string length bytes long (ignoring NULL 163 | * optional termination). */ 164 | void strbuf_resize(strbuf_t *s, int len) 165 | { 166 | int newsize; 167 | 168 | newsize = calculate_new_size(s, len); 169 | 170 | if (s->debug > 1) { 171 | fprintf(stderr, "strbuf(%lx) resize: %d => %d\n", 172 | (long)s, s->size, newsize); 173 | } 174 | 175 | s->size = newsize; 176 | s->buf = realloc(s->buf, s->size); 177 | if (!s->buf) 178 | die("Out of memory"); 179 | s->reallocs++; 180 | } 181 | 182 | void strbuf_append_string(strbuf_t *s, const char *str) 183 | { 184 | int space, i; 185 | 186 | space = strbuf_empty_length(s); 187 | 188 | for (i = 0; str[i]; i++) { 189 | if (space < 1) { 190 | strbuf_resize(s, s->length + 1); 191 | space = strbuf_empty_length(s); 192 | } 193 | 194 | s->buf[s->length] = str[i]; 195 | s->length++; 196 | space--; 197 | } 198 | } 199 | 200 | /* strbuf_append_fmt() should only be used when an upper bound 201 | * is known for the output string. */ 202 | void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...) 203 | { 204 | va_list arg; 205 | int fmt_len; 206 | 207 | strbuf_ensure_empty_length(s, len); 208 | 209 | va_start(arg, fmt); 210 | fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg); 211 | va_end(arg); 212 | 213 | if (fmt_len < 0) 214 | die("BUG: Unable to convert number"); /* This should never happen.. */ 215 | 216 | s->length += fmt_len; 217 | } 218 | 219 | /* strbuf_append_fmt_retry() can be used when the there is no known 220 | * upper bound for the output string. */ 221 | void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...) 222 | { 223 | va_list arg; 224 | int fmt_len, try; 225 | int empty_len; 226 | 227 | /* If the first attempt to append fails, resize the buffer appropriately 228 | * and try again */ 229 | for (try = 0; ; try++) { 230 | va_start(arg, fmt); 231 | /* Append the new formatted string */ 232 | /* fmt_len is the length of the string required, excluding the 233 | * trailing NULL */ 234 | empty_len = strbuf_empty_length(s); 235 | /* Add 1 since there is also space to store the terminating NULL. */ 236 | fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg); 237 | va_end(arg); 238 | 239 | if (fmt_len <= empty_len) 240 | break; /* SUCCESS */ 241 | if (try > 0) 242 | die("BUG: length of formatted string changed"); 243 | 244 | strbuf_resize(s, s->length + fmt_len); 245 | } 246 | 247 | s->length += fmt_len; 248 | } 249 | 250 | /* vi:ai et sw=4 ts=4: 251 | */ 252 | -------------------------------------------------------------------------------- /strbuf.h: -------------------------------------------------------------------------------- 1 | /* strbuf - String buffer routines 2 | * 3 | * Copyright (c) 2010-2011 Mark Pulford 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining 6 | * a copy of this software and associated documentation files (the 7 | * "Software"), to deal in the Software without restriction, including 8 | * without limitation the rights to use, copy, modify, merge, publish, 9 | * distribute, sublicense, and/or sell copies of the Software, and to 10 | * permit persons to whom the Software is furnished to do so, subject to 11 | * the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | /* Size: Total bytes allocated to *buf 29 | * Length: String length, excluding optional NULL terminator. 30 | * Increment: Allocation increments when resizing the string buffer. 31 | * Dynamic: True if created via strbuf_new() 32 | */ 33 | 34 | typedef struct { 35 | char *buf; 36 | int size; 37 | int length; 38 | int increment; 39 | int dynamic; 40 | int reallocs; 41 | int debug; 42 | } strbuf_t; 43 | 44 | #ifndef STRBUF_DEFAULT_SIZE 45 | #define STRBUF_DEFAULT_SIZE 1023 46 | #endif 47 | #ifndef STRBUF_DEFAULT_INCREMENT 48 | #define STRBUF_DEFAULT_INCREMENT -2 49 | #endif 50 | 51 | /* Initialise */ 52 | extern strbuf_t *strbuf_new(int len); 53 | extern void strbuf_init(strbuf_t *s, int len); 54 | extern void strbuf_set_increment(strbuf_t *s, int increment); 55 | 56 | /* Release */ 57 | extern void strbuf_free(strbuf_t *s); 58 | extern char *strbuf_free_to_string(strbuf_t *s, int *len); 59 | 60 | /* Management */ 61 | extern void strbuf_resize(strbuf_t *s, int len); 62 | static int strbuf_empty_length(strbuf_t *s); 63 | static int strbuf_length(strbuf_t *s); 64 | static char *strbuf_string(strbuf_t *s, int *len); 65 | static void strbuf_ensure_empty_length(strbuf_t *s, int len); 66 | 67 | /* Update */ 68 | extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...); 69 | extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...); 70 | static void strbuf_append_mem(strbuf_t *s, const char *c, int len); 71 | extern void strbuf_append_string(strbuf_t *s, const char *str); 72 | static void strbuf_append_char(strbuf_t *s, const char c); 73 | static void strbuf_ensure_null(strbuf_t *s); 74 | 75 | /* Reset string for before use */ 76 | static inline void strbuf_reset(strbuf_t *s) 77 | { 78 | s->length = 0; 79 | } 80 | 81 | static inline int strbuf_allocated(strbuf_t *s) 82 | { 83 | return s->buf != NULL; 84 | } 85 | 86 | /* Return bytes remaining in the string buffer 87 | * Ensure there is space for a NULL terminator. */ 88 | static inline int strbuf_empty_length(strbuf_t *s) 89 | { 90 | return s->size - s->length - 1; 91 | } 92 | 93 | static inline void strbuf_ensure_empty_length(strbuf_t *s, int len) 94 | { 95 | if (len > strbuf_empty_length(s)) 96 | strbuf_resize(s, s->length + len); 97 | } 98 | 99 | static inline int strbuf_length(strbuf_t *s) 100 | { 101 | return s->length; 102 | } 103 | 104 | static inline void strbuf_append_char(strbuf_t *s, const char c) 105 | { 106 | strbuf_ensure_empty_length(s, 1); 107 | s->buf[s->length++] = c; 108 | } 109 | 110 | static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c) 111 | { 112 | s->buf[s->length++] = c; 113 | } 114 | 115 | static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len) 116 | { 117 | strbuf_ensure_empty_length(s, len); 118 | memcpy(s->buf + s->length, c, len); 119 | s->length += len; 120 | } 121 | 122 | static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len) 123 | { 124 | memcpy(s->buf + s->length, c, len); 125 | s->length += len; 126 | } 127 | 128 | static inline void strbuf_ensure_null(strbuf_t *s) 129 | { 130 | s->buf[s->length] = 0; 131 | } 132 | 133 | static inline char *strbuf_string(strbuf_t *s, int *len) 134 | { 135 | if (len) 136 | *len = s->length; 137 | 138 | return s->buf; 139 | } 140 | 141 | /* vi:ai et sw=4 ts=4: 142 | */ 143 | -------------------------------------------------------------------------------- /tests/README: -------------------------------------------------------------------------------- 1 | These JSON examples were taken from the JSON website 2 | (http://json.org/example.html) and RFC 4627. 3 | 4 | Used with permission. 5 | -------------------------------------------------------------------------------- /tests/bench.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- Simple JSON benchmark. 4 | -- 5 | -- Your Mileage May Vary. 6 | -- 7 | -- Mark Pulford 8 | 9 | require "common" 10 | require "socket" 11 | 12 | local json = require "cjson" 13 | 14 | function benchmark(tests, seconds, rep) 15 | local function bench(func, iter) 16 | -- collectgarbage("stop") 17 | collectgarbage("collect") 18 | local t = socket.gettime() 19 | for i = 1, iter do 20 | func(i) 21 | end 22 | t = socket.gettime() - t 23 | -- collectgarbage("restart") 24 | return (iter / t) 25 | end 26 | 27 | -- Roughly calculate the number of interations required 28 | -- to obtain a particular time period. 29 | local function calc_iter(func, seconds) 30 | local base_iter = 10 31 | local rate = (bench(func, base_iter) + bench(func, base_iter)) / 2 32 | return math.ceil(seconds * rate) 33 | end 34 | 35 | local test_results = {} 36 | for name, func in pairs(tests) do 37 | -- k(number), v(string) 38 | -- k(string), v(function) 39 | -- k(number), v(function) 40 | if type(func) == "string" then 41 | name = func 42 | func = _G[name] 43 | end 44 | local iter = calc_iter(func, seconds) 45 | local result = {} 46 | for i = 1, rep do 47 | result[i] = bench(func, iter) 48 | end 49 | table.sort(result) 50 | test_results[name] = result[rep] 51 | end 52 | 53 | return test_results 54 | end 55 | 56 | function bench_file(filename) 57 | local data_json = file_load(filename) 58 | local data_obj = json.decode(data_json) 59 | 60 | local function test_encode () 61 | json.encode(data_obj) 62 | end 63 | local function test_decode () 64 | json.decode(data_json) 65 | end 66 | 67 | local tests = { 68 | encode = test_encode, 69 | decode = test_decode 70 | } 71 | 72 | return benchmark(tests, 0.1, 5) 73 | end 74 | 75 | cjson.encode_keep_buffer(true) 76 | 77 | for i = 1, #arg do 78 | local results = bench_file(arg[i]) 79 | for k, v in pairs(results) do 80 | print(string.format("%s: %s: %d", arg[i], k, v)) 81 | end 82 | end 83 | 84 | -- vi:ai et sw=4 ts=4: 85 | -------------------------------------------------------------------------------- /tests/common.lua: -------------------------------------------------------------------------------- 1 | require "cjson" 2 | 3 | -- Misc routines to assist with CJSON testing 4 | -- 5 | -- Mark Pulford 6 | 7 | -- Determine with a Lua table can be treated as an array. 8 | -- Explicitly returns "not an array" for very sparse arrays. 9 | -- Returns: 10 | -- -1 Not an array 11 | -- 0 Empty table 12 | -- >0 Highest index in the array 13 | function is_array(table) 14 | local max = 0 15 | local count = 0 16 | for k, v in pairs(table) do 17 | if type(k) == "number" then 18 | if k > max then max = k end 19 | count = count + 1 20 | else 21 | return -1 22 | end 23 | end 24 | if max > count * 2 then 25 | return -1 26 | end 27 | 28 | return max 29 | end 30 | 31 | function serialise_table(value, indent, depth) 32 | local spacing, spacing2, indent2 33 | if indent then 34 | spacing = "\n" .. indent 35 | spacing2 = spacing .. " " 36 | indent2 = indent .. " " 37 | else 38 | spacing, spacing2, indent2 = " ", " ", false 39 | end 40 | depth = depth + 1 41 | if depth > 50 then 42 | return "Cannot serialise any further: too many nested tables" 43 | end 44 | 45 | local max = is_array(value) 46 | 47 | local comma = false 48 | local fragment = { "{" .. spacing2 } 49 | if max > 0 then 50 | -- Serialise array 51 | for i = 1, max do 52 | if comma then 53 | table.insert(fragment, "," .. spacing2) 54 | end 55 | table.insert(fragment, serialise_value(value[i], indent2, depth)) 56 | comma = true 57 | end 58 | elseif max < 0 then 59 | -- Serialise table 60 | for k, v in pairs(value) do 61 | if comma then 62 | table.insert(fragment, "," .. spacing2) 63 | end 64 | table.insert(fragment, string.format( 65 | "[%s] = %s", serialise_value(k, indent2, depth), 66 | serialise_value(v, indent2, depth)) 67 | ) 68 | comma = true 69 | end 70 | end 71 | table.insert(fragment, spacing .. "}") 72 | 73 | return table.concat(fragment) 74 | end 75 | 76 | function serialise_value(value, indent, depth) 77 | if indent == nil then indent = "" end 78 | if depth == nil then depth = 0 end 79 | 80 | if value == cjson.null then 81 | return "cjson.null" 82 | elseif type(value) == "string" then 83 | return string.format("%q", value) 84 | elseif type(value) == "nil" or type(value) == "number" or 85 | type(value) == "boolean" then 86 | return tostring(value) 87 | elseif type(value) == "table" then 88 | return serialise_table(value, indent, depth) 89 | else 90 | return "\"<" .. type(value) .. ">\"" 91 | end 92 | end 93 | 94 | function dump_value(value) 95 | print(serialise_value(value)) 96 | end 97 | 98 | function file_load(filename) 99 | local file 100 | if filename == nil then 101 | file = io.stdin 102 | else 103 | local err 104 | file, err = io.open(filename) 105 | if file == nil then 106 | error(string.format("Unable to read '%s': %s", filename, err)) 107 | end 108 | end 109 | local data = file:read("*a") 110 | 111 | if filename ~= nil then 112 | file:close() 113 | end 114 | 115 | if data == nil then 116 | error("Failed to read " .. filename) 117 | end 118 | 119 | return data 120 | end 121 | 122 | function file_save(filename, data) 123 | local file 124 | if filename == nil then 125 | file = io.stdout 126 | else 127 | local err 128 | file, err = io.open(filename, "w") 129 | if file == nil then 130 | error(string.format("Unable to write '%s': %s", filename, err)) 131 | end 132 | end 133 | file:write(data) 134 | if filename ~= nil then 135 | file:close() 136 | end 137 | end 138 | 139 | function compare_values(val1, val2) 140 | local type1 = type(val1) 141 | local type2 = type(val2) 142 | if type1 ~= type2 then 143 | return false 144 | end 145 | 146 | -- Check for NaN 147 | if type1 == "number" and val1 ~= val1 and val2 ~= val2 then 148 | return true 149 | end 150 | 151 | if type1 ~= "table" then 152 | return val1 == val2 153 | end 154 | 155 | -- check_keys stores all the keys that must be checked in val2 156 | local check_keys = {} 157 | for k, _ in pairs(val1) do 158 | check_keys[k] = true 159 | end 160 | 161 | for k, v in pairs(val2) do 162 | if not check_keys[k] then 163 | return false 164 | end 165 | 166 | if not compare_values(val1[k], val2[k]) then 167 | return false 168 | end 169 | 170 | check_keys[k] = nil 171 | end 172 | for k, _ in pairs(check_keys) do 173 | -- Not the same if any keys from val1 were not found in val2 174 | return false 175 | end 176 | return true 177 | end 178 | 179 | function run_test(testname, func, input, should_work, output) 180 | local function status_line(name, status, value) 181 | local statusmap = { [true] = ":success", [false] = ":error" } 182 | if status ~= nil then 183 | name = name .. statusmap[status] 184 | end 185 | print(string.format("[%s] %s", name, serialise_value(value, false))) 186 | end 187 | 188 | local result = { pcall(func, unpack(input)) } 189 | local success = table.remove(result, 1) 190 | 191 | local correct = false 192 | if success == should_work and compare_values(result, output) then 193 | correct = true 194 | end 195 | 196 | local teststatus = { [true] = "PASS", [false] = "FAIL" } 197 | print("==> Test " .. testname .. ": " .. teststatus[correct]) 198 | 199 | status_line("Input", nil, input) 200 | if not correct then 201 | status_line("Expected", should_work, output) 202 | end 203 | status_line("Received", success, result) 204 | print() 205 | 206 | return correct, result 207 | end 208 | 209 | function run_test_group(testgroup, tests) 210 | local function run_config(configname, func) 211 | local success, msg = pcall(func) 212 | if msg then 213 | print(string.format("==> Config %s: %s", configname, msg)) 214 | end 215 | print() 216 | end 217 | 218 | local function test_id(group, id) 219 | return string.format("%s [%d]", group, id) 220 | end 221 | 222 | for k, v in ipairs(tests) do 223 | if type(v) == "function" then 224 | -- Useful for changing configuration during a batch 225 | run_config(test_id(testgroup, k), v) 226 | elseif type(v) == "table" then 227 | run_test(test_id(testgroup, k), unpack(v)) 228 | else 229 | error("Testgroup can only contain functions and tables") 230 | end 231 | end 232 | end 233 | 234 | -- vi:ai et sw=4 ts=4: 235 | -------------------------------------------------------------------------------- /tests/decode.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- usage: decode.lua [json_file] 4 | -- 5 | -- Eg: 6 | -- echo '[ "testing" ]' | ./decode.lua 7 | -- ./decode.lua test.json 8 | 9 | require "common" 10 | require "cjson" 11 | 12 | local json_text = file_load(arg[1]) 13 | local t = cjson.decode(json_text) 14 | print(serialise_value(t)) 15 | -------------------------------------------------------------------------------- /tests/encode.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- usage: encode.lua [lua_file] 4 | -- 5 | -- Eg: 6 | -- echo '{ "testing" }' | ./encode.lua 7 | -- ./encode.lua lua_data.lua 8 | 9 | require "common" 10 | require "cjson" 11 | 12 | function get_lua_table(file) 13 | local func = loadstring("data = " .. file_load(file)) 14 | if func == nil then 15 | error("Invalid syntax? Lua table required.") 16 | end 17 | 18 | local env = {} 19 | func = setfenv(func, env) 20 | func() 21 | 22 | return env.data 23 | end 24 | 25 | local t = get_lua_table(arg[1]) 26 | print(cjson.encode(t)) 27 | 28 | -- vi:ai et sw=4 ts=4: 29 | -------------------------------------------------------------------------------- /tests/example1.json: -------------------------------------------------------------------------------- 1 | { 2 | "glossary": { 3 | "title": "example glossary", 4 | "GlossDiv": { 5 | "title": "S", 6 | "GlossList": { 7 | "GlossEntry": { 8 | "ID": "SGML", 9 | "SortAs": "SGML", 10 | "GlossTerm": "Standard Generalized Mark up Language", 11 | "Acronym": "SGML", 12 | "Abbrev": "ISO 8879:1986", 13 | "GlossDef": { 14 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 15 | "GlossSeeAlso": ["GML", "XML"] 16 | }, 17 | "GlossSee": "markup" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/example2.json: -------------------------------------------------------------------------------- 1 | {"menu": { 2 | "id": "file", 3 | "value": "File", 4 | "popup": { 5 | "menuitem": [ 6 | {"value": "New", "onclick": "CreateNewDoc()"}, 7 | {"value": "Open", "onclick": "OpenDoc()"}, 8 | {"value": "Close", "onclick": "CloseDoc()"} 9 | ] 10 | } 11 | }} 12 | -------------------------------------------------------------------------------- /tests/example3.json: -------------------------------------------------------------------------------- 1 | {"widget": { 2 | "debug": "on", 3 | "window": { 4 | "title": "Sample Konfabulator Widget", 5 | "name": "main_window", 6 | "width": 500, 7 | "height": 500 8 | }, 9 | "image": { 10 | "src": "Images/Sun.png", 11 | "name": "sun1", 12 | "hOffset": 250, 13 | "vOffset": 250, 14 | "alignment": "center" 15 | }, 16 | "text": { 17 | "data": "Click Here", 18 | "size": 36, 19 | "style": "bold", 20 | "name": "text1", 21 | "hOffset": 250, 22 | "vOffset": 100, 23 | "alignment": "center", 24 | "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" 25 | } 26 | }} 27 | -------------------------------------------------------------------------------- /tests/example4.json: -------------------------------------------------------------------------------- 1 | {"web-app": { 2 | "servlet": [ 3 | { 4 | "servlet-name": "cofaxCDS", 5 | "servlet-class": "org.cofax.cds.CDSServlet", 6 | "init-param": { 7 | "configGlossary:installationAt": "Philadelphia, PA", 8 | "configGlossary:adminEmail": "ksm@pobox.com", 9 | "configGlossary:poweredBy": "Cofax", 10 | "configGlossary:poweredByIcon": "/images/cofax.gif", 11 | "configGlossary:staticPath": "/content/static", 12 | "templateProcessorClass": "org.cofax.WysiwygTemplate", 13 | "templateLoaderClass": "org.cofax.FilesTemplateLoader", 14 | "templatePath": "templates", 15 | "templateOverridePath": "", 16 | "defaultListTemplate": "listTemplate.htm", 17 | "defaultFileTemplate": "articleTemplate.htm", 18 | "useJSP": false, 19 | "jspListTemplate": "listTemplate.jsp", 20 | "jspFileTemplate": "articleTemplate.jsp", 21 | "cachePackageTagsTrack": 200, 22 | "cachePackageTagsStore": 200, 23 | "cachePackageTagsRefresh": 60, 24 | "cacheTemplatesTrack": 100, 25 | "cacheTemplatesStore": 50, 26 | "cacheTemplatesRefresh": 15, 27 | "cachePagesTrack": 200, 28 | "cachePagesStore": 100, 29 | "cachePagesRefresh": 10, 30 | "cachePagesDirtyRead": 10, 31 | "searchEngineListTemplate": "forSearchEnginesList.htm", 32 | "searchEngineFileTemplate": "forSearchEngines.htm", 33 | "searchEngineRobotsDb": "WEB-INF/robots.db", 34 | "useDataStore": true, 35 | "dataStoreClass": "org.cofax.SqlDataStore", 36 | "redirectionClass": "org.cofax.SqlRedirection", 37 | "dataStoreName": "cofax", 38 | "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", 39 | "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", 40 | "dataStoreUser": "sa", 41 | "dataStorePassword": "dataStoreTestQuery", 42 | "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", 43 | "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", 44 | "dataStoreInitConns": 10, 45 | "dataStoreMaxConns": 100, 46 | "dataStoreConnUsageLimit": 100, 47 | "dataStoreLogLevel": "debug", 48 | "maxUrlLength": 500}}, 49 | { 50 | "servlet-name": "cofaxEmail", 51 | "servlet-class": "org.cofax.cds.EmailServlet", 52 | "init-param": { 53 | "mailHost": "mail1", 54 | "mailHostOverride": "mail2"}}, 55 | { 56 | "servlet-name": "cofaxAdmin", 57 | "servlet-class": "org.cofax.cds.AdminServlet"}, 58 | 59 | { 60 | "servlet-name": "fileServlet", 61 | "servlet-class": "org.cofax.cds.FileServlet"}, 62 | { 63 | "servlet-name": "cofaxTools", 64 | "servlet-class": "org.cofax.cms.CofaxToolsServlet", 65 | "init-param": { 66 | "templatePath": "toolstemplates/", 67 | "log": 1, 68 | "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", 69 | "logMaxSize": "", 70 | "dataLog": 1, 71 | "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", 72 | "dataLogMaxSize": "", 73 | "removePageCache": "/content/admin/remove?cache=pages&id=", 74 | "removeTemplateCache": "/content/admin/remove?cache=templates&id=", 75 | "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", 76 | "lookInContext": 1, 77 | "adminGroupID": 4, 78 | "betaServer": true}}], 79 | "servlet-mapping": { 80 | "cofaxCDS": "/", 81 | "cofaxEmail": "/cofaxutil/aemail/*", 82 | "cofaxAdmin": "/admin/*", 83 | "fileServlet": "/static/*", 84 | "cofaxTools": "/tools/*"}, 85 | 86 | "taglib": { 87 | "taglib-uri": "cofax.tld", 88 | "taglib-location": "/WEB-INF/tlds/cofax.tld"}}} 89 | -------------------------------------------------------------------------------- /tests/example5.json: -------------------------------------------------------------------------------- 1 | {"menu": { 2 | "header": "SVG Viewer", 3 | "items": [ 4 | {"id": "Open"}, 5 | {"id": "OpenNew", "label": "Open New"}, 6 | null, 7 | {"id": "ZoomIn", "label": "Zoom In"}, 8 | {"id": "ZoomOut", "label": "Zoom Out"}, 9 | {"id": "OriginalView", "label": "Original View"}, 10 | null, 11 | {"id": "Quality"}, 12 | {"id": "Pause"}, 13 | {"id": "Mute"}, 14 | null, 15 | {"id": "Find", "label": "Find..."}, 16 | {"id": "FindAgain", "label": "Find Again"}, 17 | {"id": "Copy"}, 18 | {"id": "CopyAgain", "label": "Copy Again"}, 19 | {"id": "CopySVG", "label": "Copy SVG"}, 20 | {"id": "ViewSVG", "label": "View SVG"}, 21 | {"id": "ViewSource", "label": "View Source"}, 22 | {"id": "SaveAs", "label": "Save As"}, 23 | null, 24 | {"id": "Help"}, 25 | {"id": "About", "label": "About Adobe CVG Viewer..."} 26 | ] 27 | }} 28 | -------------------------------------------------------------------------------- /tests/genutf8.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # Create test comparison data using a different UTF-8 implementation. 4 | 5 | # The generation utf8.dat file must have the following MD5 sum: 6 | # cff03b039d850f370a7362f3313e5268 7 | 8 | use strict; 9 | use warnings; 10 | use FileHandle; 11 | 12 | # 0xD800 - 0xDFFF are used to encode supplementary codepoints 13 | # 0x10000 - 0x10FFFF are supplementary codepoints 14 | my (@codepoints) = (0 .. 0xD7FF, 0xE000 .. 0x10FFFF); 15 | 16 | my ($utf8); 17 | { 18 | # Hide "Unicode character X is illegal" warnings. 19 | # We want all the codes to test the UTF-8 escape decoder. 20 | no warnings; 21 | $utf8 = pack("U*", @codepoints); 22 | } 23 | defined($utf8) or die "Unable create UTF-8 string\n"; 24 | 25 | my $fh = FileHandle->new(); 26 | $fh->open("utf8.dat", ">:utf8") 27 | or die "Unable to open utf8.dat: $!\n"; 28 | $fh->write($utf8) 29 | or die "Unable to write utf8.dat\n"; 30 | $fh->close(); 31 | 32 | # vi:ai et sw=4 ts=4: 33 | -------------------------------------------------------------------------------- /tests/numbers.json: -------------------------------------------------------------------------------- 1 | [ 0.110001000000000000000001, 2 | 0.12345678910111213141516, 3 | 0.412454033640, 4 | 2.6651441426902251886502972498731, 5 | 2.718281828459045235360287471352, 6 | 3.141592653589793238462643383279, 7 | 2.14069263277926 ] 8 | -------------------------------------------------------------------------------- /tests/octets-escaped.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efelix/lua-cjson/4f27182acabc435fcc220fdb710ddfaf4e648c86/tests/octets-escaped.dat -------------------------------------------------------------------------------- /tests/rfc-example1.json: -------------------------------------------------------------------------------- 1 | { 2 | "Image": { 3 | "Width": 800, 4 | "Height": 600, 5 | "Title": "View from 15th Floor", 6 | "Thumbnail": { 7 | "Url": "http://www.example.com/image/481989943", 8 | "Height": 125, 9 | "Width": "100" 10 | }, 11 | "IDs": [116, 943, 234, 38793] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/rfc-example2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "precision": "zip", 4 | "Latitude": 37.7668, 5 | "Longitude": -122.3959, 6 | "Address": "", 7 | "City": "SAN FRANCISCO", 8 | "State": "CA", 9 | "Zip": "94107", 10 | "Country": "US" 11 | }, 12 | { 13 | "precision": "zip", 14 | "Latitude": 37.371991, 15 | "Longitude": -122.026020, 16 | "Address": "", 17 | "City": "SUNNYVALE", 18 | "State": "CA", 19 | "Zip": "94085", 20 | "Country": "US" 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /tests/test.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- CJSON tests 4 | -- 5 | -- Mark Pulford 6 | -- 7 | -- Note: The output of this script is easier to read with "less -S" 8 | 9 | require "common" 10 | local json = require "cjson" 11 | 12 | local function gen_ascii() 13 | local chars = {} 14 | for i = 0, 255 do chars[i + 1] = string.char(i) end 15 | return table.concat(chars) 16 | end 17 | 18 | -- Generate every UTF-16 codepoint, including supplementary codes 19 | local function gen_utf16_escaped() 20 | -- Create raw table escapes 21 | local utf16_escaped = {} 22 | local count = 0 23 | 24 | local function append_escape(code) 25 | local esc = string.format('\\u%04X', code) 26 | table.insert(utf16_escaped, esc) 27 | end 28 | 29 | table.insert(utf16_escaped, '"') 30 | for i = 0, 0xD7FF do 31 | append_escape(i) 32 | end 33 | -- Skip 0xD800 - 0xDFFF since they are used to encode supplementary 34 | -- codepoints 35 | for i = 0xE000, 0xFFFF do 36 | append_escape(i) 37 | end 38 | -- Append surrogate pair for each supplementary codepoint 39 | for high = 0xD800, 0xDBFF do 40 | for low = 0xDC00, 0xDFFF do 41 | append_escape(high) 42 | append_escape(low) 43 | end 44 | end 45 | table.insert(utf16_escaped, '"') 46 | 47 | return table.concat(utf16_escaped) 48 | end 49 | 50 | function test_decode_cycle(filename) 51 | local obj1 = json.decode(file_load(filename)) 52 | local obj2 = json.decode(json.encode(obj1)) 53 | return compare_values(obj1, obj2) 54 | end 55 | 56 | local Inf = math.huge; 57 | local NaN = math.huge * 0; 58 | local octets_raw = gen_ascii() 59 | local octets_escaped = file_load("octets-escaped.dat") 60 | local utf8_loaded, utf8_raw = pcall(file_load, "utf8.dat") 61 | if not utf8_loaded then 62 | utf8_raw = "Failed to load utf8.dat" 63 | end 64 | local utf16_escaped = gen_utf16_escaped() 65 | local nested5 = {{{{{ "nested" }}}}} 66 | local table_cycle = {} 67 | local table_cycle2 = { table_cycle } 68 | table_cycle[1] = table_cycle2 69 | 70 | local decode_simple_tests = { 71 | { json.decode, { '"test string"' }, true, { "test string" } }, 72 | { json.decode, { '-5e3' }, true, { -5000 } }, 73 | { json.decode, { 'null' }, true, { json.null } }, 74 | { json.decode, { 'true' }, true, { true } }, 75 | { json.decode, { 'false' }, true, { false } }, 76 | { json.decode, { '{ "1": "one", "3": "three" }' }, 77 | true, { { ["1"] = "one", ["3"] = "three" } } }, 78 | { json.decode, { '[ "one", null, "three" ]' }, 79 | true, { { "one", json.null, "three" } } } 80 | } 81 | 82 | local encode_simple_tests = { 83 | { json.encode, { json.null }, true, { 'null' } }, 84 | { json.encode, { true }, true, { 'true' } }, 85 | { json.encode, { false }, true, { 'false' } }, 86 | { json.encode, { { } }, true, { '{}' } }, 87 | { json.encode, { 10 }, true, { '10' } }, 88 | { json.encode, { NaN }, 89 | false, { "Cannot serialise number: must not be NaN or Inf" } }, 90 | { json.encode, { Inf }, 91 | false, { "Cannot serialise number: must not be NaN or Inf" } }, 92 | { json.encode, { "hello" }, true, { '"hello"' } }, 93 | } 94 | 95 | 96 | local decode_numeric_tests = { 97 | { json.decode, { '[ 0.0, -1, 0.3e-3, 1023.2 ]' }, 98 | true, { { 0.0, -1, 0.0003, 1023.2 } } }, 99 | { json.decode, { '00123' }, true, { 123 } }, 100 | { json.decode, { '05.2' }, true, { 5.2 } }, 101 | { json.decode, { '0e10' }, true, { 0 } }, 102 | { json.decode, { '0x6' }, true, { 6 } }, 103 | { json.decode, { '[ +Inf, Inf, -Inf ]' }, true, { { Inf, Inf, -Inf } } }, 104 | { json.decode, { '[ +Infinity, Infinity, -Infinity ]' }, 105 | true, { { Inf, Inf, -Inf } } }, 106 | { json.decode, { '[ +NaN, NaN, -NaN ]' }, true, { { NaN, NaN, NaN } } }, 107 | { json.decode, { 'Infrared' }, 108 | false, { "Expected the end but found invalid token at character 4" } }, 109 | { json.decode, { 'Noodle' }, 110 | false, { "Expected value but found invalid token at character 1" } }, 111 | } 112 | 113 | local encode_table_tests = { 114 | function() 115 | cjson.encode_sparse_array(true, 2, 3) 116 | cjson.encode_max_depth(5) 117 | return "Setting sparse array (true, 2, 3) / max depth (5)" 118 | end, 119 | { json.encode, { { [3] = "sparse test" } }, 120 | true, { '[null,null,"sparse test"]' } }, 121 | { json.encode, { { [1] = "one", [4] = "sparse test" } }, 122 | true, { '["one",null,null,"sparse test"]' } }, 123 | { json.encode, { { [1] = "one", [5] = "sparse test" } }, 124 | true, { '{"1":"one","5":"sparse test"}' } }, 125 | 126 | { json.encode, { { ["2"] = "numeric string key test" } }, 127 | true, { '{"2":"numeric string key test"}' } }, 128 | 129 | { json.encode, { nested5 }, true, { '[[[[["nested"]]]]]' } }, 130 | { json.encode, { { nested5 } }, 131 | false, { "Cannot serialise, excessive nesting (6)" } }, 132 | { json.encode, { table_cycle }, 133 | false, { "Cannot serialise, excessive nesting (6)" } } 134 | } 135 | 136 | local encode_error_tests = { 137 | { json.encode, { { [false] = "wrong" } }, 138 | false, { "Cannot serialise boolean: table key must be a number or string" } }, 139 | { json.encode, { function () end }, 140 | false, { "Cannot serialise function: type not supported" } }, 141 | function () 142 | json.refuse_invalid_numbers("encode") 143 | return 'Setting refuse_invalid_numbers("encode")' 144 | end, 145 | { json.encode, { NaN }, 146 | false, { "Cannot serialise number: must not be NaN or Inf" } }, 147 | { json.encode, { Inf }, 148 | false, { "Cannot serialise number: must not be NaN or Inf" } }, 149 | function () 150 | json.refuse_invalid_numbers(false) 151 | return 'Setting refuse_invalid_numbers(false).' 152 | end, 153 | function () 154 | print('NOTE: receiving "-nan" in the following test is ok..') 155 | return 156 | end, 157 | { json.encode, { NaN }, true, { "nan" } }, 158 | { json.encode, { Inf }, true, { "inf" } }, 159 | function () 160 | json.refuse_invalid_numbers("encode") 161 | return 'Setting refuse_invalid_numbers("encode")' 162 | end, 163 | } 164 | 165 | local json_nested = string.rep("[", 100000) .. string.rep("]", 100000) 166 | 167 | local decode_error_tests = { 168 | { json.decode, { '\0"\0"' }, 169 | false, { "JSON parser does not support UTF-16 or UTF-32" } }, 170 | { json.decode, { '"\0"\0' }, 171 | false, { "JSON parser does not support UTF-16 or UTF-32" } }, 172 | { json.decode, { '{ "unexpected eof": ' }, 173 | false, { "Expected value but found T_END at character 21" } }, 174 | { json.decode, { '{ "extra data": true }, false' }, 175 | false, { "Expected the end but found T_COMMA at character 23" } }, 176 | { json.decode, { ' { "bad escape \\q code" } ' }, 177 | false, { "Expected object key string but found invalid escape code at character 16" } }, 178 | { json.decode, { ' { "bad unicode \\u0f6 escape" } ' }, 179 | false, { "Expected object key string but found invalid unicode escape code at character 17" } }, 180 | { json.decode, { ' [ "bad barewood", test ] ' }, 181 | false, { "Expected value but found invalid token at character 20" } }, 182 | { json.decode, { '[ -+12 ]' }, 183 | false, { "Expected value but found invalid number at character 3" } }, 184 | { json.decode, { '-v' }, 185 | false, { "Expected value but found invalid number at character 1" } }, 186 | { json.decode, { '[ 0.4eg10 ]' }, 187 | false, { "Expected comma or array end but found invalid token at character 6" } }, 188 | { json.decode, { json_nested }, 189 | false, { "Too many nested data structures" } } 190 | } 191 | 192 | local escape_tests = { 193 | -- Test 8bit clean 194 | { json.encode, { octets_raw }, true, { octets_escaped } }, 195 | { json.decode, { octets_escaped }, true, { octets_raw } }, 196 | -- Ensure high bits are removed from surrogate codes 197 | { json.decode, { '"\\uF800"' }, true, { "\239\160\128" } }, 198 | -- Test inverted surrogate pairs 199 | { json.decode, { '"\\uDB00\\uD800"' }, 200 | false, { "Expected value but found invalid unicode escape code at character 2" } }, 201 | -- Test 2x high surrogate code units 202 | { json.decode, { '"\\uDB00\\uDB00"' }, 203 | false, { "Expected value but found invalid unicode escape code at character 2" } }, 204 | -- Test invalid 2nd escape 205 | { json.decode, { '"\\uDB00\\"' }, 206 | false, { "Expected value but found invalid unicode escape code at character 2" } }, 207 | { json.decode, { '"\\uDB00\\uD"' }, 208 | false, { "Expected value but found invalid unicode escape code at character 2" } }, 209 | -- Test decoding of all UTF-16 escapes 210 | { json.decode, { utf16_escaped }, true, { utf8_raw } } 211 | } 212 | 213 | print(string.format("Testing CJSON v%s\n", cjson.version)) 214 | 215 | run_test_group("decode simple value", decode_simple_tests) 216 | run_test_group("encode simple value", encode_simple_tests) 217 | run_test_group("decode numeric", decode_numeric_tests) 218 | 219 | -- INCLUDE: 220 | -- - Sparse array exception.. 221 | -- - .. 222 | -- cjson.encode_sparse_array(true, 2, 3) 223 | 224 | run_test_group("encode table", encode_table_tests) 225 | run_test_group("decode error", decode_error_tests) 226 | run_test_group("encode error", encode_error_tests) 227 | run_test_group("escape", escape_tests) 228 | 229 | cjson.refuse_invalid_numbers(false) 230 | cjson.encode_max_depth(20) 231 | for i = 1, #arg do 232 | run_test("decode cycle " .. arg[i], test_decode_cycle, { arg[i] }, 233 | true, { true }) 234 | end 235 | 236 | -- vi:ai et sw=4 ts=4: 237 | -------------------------------------------------------------------------------- /tests/types.json: -------------------------------------------------------------------------------- 1 | { "array": [ 10, true, null ] } 2 | --------------------------------------------------------------------------------