aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.gitattributes5
-rw-r--r--.gitignore7
-rw-r--r--CMakeLists.txt37
-rw-r--r--LICENSE20
-rw-r--r--Makefile70
-rw-r--r--NEWS31
-rw-r--r--THANKS9
-rwxr-xr-xbuild-packages.sh36
-rw-r--r--devel/json_parser_outline.txt50
-rw-r--r--fpconv.c173
-rw-r--r--fpconv.h14
-rw-r--r--lua-cjson-1.0devel-1.rockspec47
-rw-r--r--lua-cjson.spec64
-rw-r--r--lua_cjson.c1344
-rw-r--r--manual.txt497
-rw-r--r--performance.txt51
-rw-r--r--rfc4627.txt563
-rwxr-xr-xruntests.sh91
-rw-r--r--strbuf.c251
-rw-r--r--strbuf.h154
-rw-r--r--tests/README4
-rwxr-xr-xtests/bench.lua83
-rw-r--r--tests/cjson-misc.lua253
-rw-r--r--tests/example1.json22
-rw-r--r--tests/example2.json11
-rw-r--r--tests/example3.json26
-rw-r--r--tests/example4.json88
-rw-r--r--tests/example5.json27
-rwxr-xr-xtests/genutf8.pl23
-rwxr-xr-xtests/json2lua.lua14
-rwxr-xr-xtests/lua2json.lua42
-rw-r--r--tests/numbers.json7
-rw-r--r--tests/octets-escaped.dat1
-rw-r--r--tests/rfc-example1.json13
-rw-r--r--tests/rfc-example2.json22
-rwxr-xr-xtests/test.lua252
-rw-r--r--tests/types.json1
37 files changed, 4403 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..f349fed
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
1.gitattributes export-ignore
2.gitignore export-ignore
3build-packages.sh export-ignore
4TODO export-ignore
5devel export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..56345ea
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
1*.o
2*.so
3manual.html
4notes
5packages
6tags
7tests/utf8.dat
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..8d8a420
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,37 @@
1# If Lua is installed in a non-standard location, please set the LUA_DIR
2# environment variable to point to prefix for the install. Eg:
3# Unix: export LUA_DIR=/home/user/pkg
4# Windows: set LUA_DIR=c:\lua51
5
6project(lua_cjson C)
7cmake_minimum_required(VERSION 2.6)
8
9set(CMAKE_BUILD_TYPE Release)
10
11find_package(Lua51 REQUIRED)
12include_directories(${LUA_INCLUDE_DIR})
13
14# Handle platforms missing isinf() macro (Eg, some Solaris systems).
15include(CheckSymbolExists)
16CHECK_SYMBOL_EXISTS(isinf math.h HAVE_ISINF)
17if(NOT HAVE_ISINF)
18 add_definitions(-DUSE_INTERNAL_ISINF)
19endif()
20
21if(APPLE)
22 set(CMAKE_SHARED_MODULE_CREATE_C_FLAGS
23 "${CMAKE_SHARED_MODULE_CREATE_C_FLAGS} -undefined dynamic_lookup")
24endif()
25
26get_filename_component(_lua_lib_dir ${LUA_LIBRARY} PATH)
27if(WIN32)
28 set(_lua_module_dir "${_lua_lib_dir}")
29else()
30 set(_lua_module_dir "${_lua_lib_dir}/lua/5.1")
31endif()
32
33add_library(cjson MODULE lua_cjson.c strbuf.c fpconv.c)
34set_target_properties(cjson PROPERTIES PREFIX "")
35install(TARGETS cjson DESTINATION "${_lua_module_dir}")
36
37# vi:ai et sw=4 ts=4:
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8b16d47
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
1Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au>
2
3Permission is hereby granted, free of charge, to any person obtaining
4a copy of this software and associated documentation files (the
5"Software"), to deal in the Software without restriction, including
6without limitation the rights to use, copy, modify, merge, publish,
7distribute, sublicense, and/or sell copies of the Software, and to
8permit persons to whom the Software is furnished to do so, subject to
9the following conditions:
10
11The above copyright notice and this permission notice shall be
12included in all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..57f2e1b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,70 @@
1##### Available defines for CJSON_CFLAGS #####
2##
3## USE_INTERNAL_ISINF: Workaround for Solaris platforms missing isinf().
4## DISABLE_CJSON_GLOBAL: Do not store module is "cjson" global.
5## DISABLE_INVALID_NUMBERS: Permanently disable invalid JSON numbers:
6## NaN, Infinity, hex.
7
8##### Build defaults #####
9LUA_VERSION = 5.1
10TARGET = cjson.so
11PREFIX = /usr/local
12#CFLAGS = -g -Wall -pedantic -fno-inline
13CFLAGS = -O3 -Wall -pedantic -DNDEBUG
14CJSON_CFLAGS = -fpic
15CJSON_LDFLAGS = -shared
16LUA_INCLUDE_DIR = $(PREFIX)/include
17LUA_MODULE_DIR = $(PREFIX)/lib/lua/$(LUA_VERSION)
18INSTALL_CMD = install
19
20##### Platform overrides #####
21##
22## Tweak one of the platform sections below to suit your situation.
23##
24## See http://lua-users.org/wiki/BuildingModules for further platform
25## specific details.
26
27## Linux
28
29## FreeBSD
30#LUA_INCLUDE_DIR = $(PREFIX)/include/lua51
31
32## MacOSX (Macports)
33#PREFIX = /opt/local
34#CJSON_LDFLAGS = -bundle -undefined dynamic_lookup
35
36## Solaris
37#CJSON_CFLAGS = -fpic -DUSE_INTERNAL_ISINF
38
39## Windows (MinGW)
40#TARGET = cjson.dll
41#PREFIX = /home/user/opt
42#CJSON_CFLAGS = -DDISABLE_INVALID_NUMBERS
43#CJSON_LDFLAGS = -shared -L$(PREFIX)/lib -llua51
44
45##### End customisable sections #####
46
47BUILD_CFLAGS = -I$(LUA_INCLUDE_DIR) $(CJSON_CFLAGS)
48OBJS := lua_cjson.o strbuf.o fpconv.o
49
50.PHONY: all clean install package doc
51
52all: $(TARGET)
53
54doc: manual.html
55
56.c.o:
57 $(CC) -c $(CFLAGS) $(CPPFLAGS) $(BUILD_CFLAGS) -o $@ $<
58
59$(TARGET): $(OBJS)
60 $(CC) $(LDFLAGS) $(CJSON_LDFLAGS) -o $@ $(OBJS)
61
62install: $(TARGET)
63 mkdir -p $(DESTDIR)/$(LUA_MODULE_DIR)
64 $(INSTALL_CMD) $(TARGET) $(DESTDIR)/$(LUA_MODULE_DIR)
65
66manual.html: manual.txt
67 asciidoc -n -a toc manual.txt
68
69clean:
70 rm -f *.o $(TARGET)
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..ba48b9c
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,31 @@
1Version 1.0.5 (?)
2* Added support for Lua 5.2
3* Added HTML reference manual
4* Added CMake build support
5* Updated Makefile for compatibility with non-GNU make implementations
6* Improved compatibility for strtod/sprintf locale workaround
7* Encoding now supports pre-emptive threads within a single Lua state
8
9Version 1.0.4 (Nov 30 2011)
10* Fixed numeric conversion under locales with a comma decimal separator
11
12Version 1.0.3 (Sep 15 2011)
13* Fixed detection of objects with numeric string keys
14* Provided work around for missing isinf() on Solaris
15
16Version 1.0.2 (May 30 2011)
17* Portability improvements for Windows
18 - No longer links with -lm
19 - Use "socket" instead of "posix" for sub-second timing
20* Removed UTF-8 test dependency on Perl Text::Iconv
21* Added simple CLI commands for testing Lua <-> JSON conversions
22* Added cjson.encode_number_precision()
23
24Version 1.0.1 (May 10 2011)
25* Added build support for OSX
26* Removed unnecessary whitespace from JSON output
27* Added cjson.encode_keep_buffer()
28* Fixed memory leak on Lua stack overflow exception
29
30Version 1.0 (May 9 2011)
31* Initial release
diff --git a/THANKS b/THANKS
new file mode 100644
index 0000000..4aade13
--- /dev/null
+++ b/THANKS
@@ -0,0 +1,9 @@
1The following people have helped with bug reports, testing and/or
2suggestions:
3
4- Louis-Philippe Perron (@loopole)
5- Ondřej Jirman
6- Steve Donovan <steve.j.donovan@gmail.com>
7- Zhang "agentzh" Yichun <agentzh@gmail.com>
8
9Thanks!
diff --git a/build-packages.sh b/build-packages.sh
new file mode 100755
index 0000000..8d9f620
--- /dev/null
+++ b/build-packages.sh
@@ -0,0 +1,36 @@
1#!/bin/sh
2
3# build-packages.sh [ REF ]
4
5# Build packages. Use current checked out version, or a specific tag/commit.
6
7# Files requiring a version bump
8VERSION_FILES="lua-cjson-1.0devel-1.rockspec lua-cjson.spec lua_cjson.c manual.txt runtests.sh"
9
10[ "$1" ] && BRANCH="$1" || BRANCH="`git describe --match '1.[0-9]*'`"
11VERSION="`git describe --match '1.[0-9]*' $BRANCH`"
12VERSION="${VERSION//-/.}"
13
14PREFIX="lua-cjson-$VERSION"
15
16set -x
17set -e
18
19DESTDIR="`pwd`/packages"
20mkdir -p "$DESTDIR"
21BUILDROOT="`mktemp -d`"
22trap "rm -rf '$BUILDROOT'" 0
23
24git archive --prefix="$PREFIX/" "$BRANCH" | tar xf - -C "$BUILDROOT"
25cd "$BUILDROOT"
26
27cd "$PREFIX"
28rename 1.0devel "$VERSION" $VERSION_FILES
29perl -pi -e "s/\\b1.0devel\\b/$VERSION/g" ${VERSION_FILES/1.0devel/$VERSION};
30cd ..
31
32make -C "$PREFIX" doc
33tar cf - "$PREFIX" | gzip -9 > "$DESTDIR/$PREFIX.tar.gz"
34zip -9rq "$DESTDIR/$PREFIX.zip" "$PREFIX"
35
36# vi:ai et sw=4 ts=4:
diff --git a/devel/json_parser_outline.txt b/devel/json_parser_outline.txt
new file mode 100644
index 0000000..01db78d
--- /dev/null
+++ b/devel/json_parser_outline.txt
@@ -0,0 +1,50 @@
1parser:
2 - call parse_value
3 - next_token
4 ? <EOF> nop.
5
6parse_value:
7 - next_token
8 ? <OBJ_BEGIN> call parse_object.
9 ? <ARR_BEGIN> call parse_array.
10 ? <STRING> push. return.
11 ? <BOOLEAN> push. return.
12 ? <NULL> push. return.
13 ? <NUMBER> push. return.
14
15parse_object:
16 - push table
17 - next_token
18 ? <STRING> push.
19 - next_token
20 ? <COLON> nop.
21 - call parse_value
22 - set table
23 - next_token
24 ? <OBJ_END> return.
25 ? <COMMA> loop parse_object.
26
27parse_array:
28 - push table
29 - call parse_value
30 - table append
31 - next_token
32 ? <COMMA> loop parse_array.
33 ? ] return.
34
35next_token:
36 - check next character
37 ? { return <OBJ_BEGIN>
38 ? } return <OBJ_END>
39 ? [ return <ARR_BEGIN>
40 ? ] return <ARR_END>
41 ? , return <COMMA>
42 ? : return <COLON>
43 ? [-0-9] gobble number. return <NUMBER>
44 ? " gobble string. return <STRING>
45 ? [ \t\n] eat whitespace.
46 ? n Check "null". return <NULL> or <UNKNOWN>
47 ? t Check "true". return <BOOLEAN> or <UNKNOWN>
48 ? f Check "false". return <BOOLEAN> or <UNKNOWN>
49 ? . return <UNKNOWN>
50 ? \0 return <END>
diff --git a/fpconv.c b/fpconv.c
new file mode 100644
index 0000000..3bcef41
--- /dev/null
+++ b/fpconv.c
@@ -0,0 +1,173 @@
1/* JSON uses a '.' decimal separator. strtod() / sprintf() under C libraries
2 * with locale support will break when the decimal separator is a comma.
3 *
4 * fpconv_* will around these issues with a translation buffer if required.
5 */
6
7#include <stdio.h>
8#include <stdlib.h>
9#include <assert.h>
10#include <string.h>
11
12#include "fpconv.h"
13
14/* Lua CJSON assumes the locale is the same for all threads within a
15 * process and doesn't change after initialisation.
16 *
17 * This avoids the need for per thread storage or expensive checks
18 * for call. */
19static char locale_decimal_point = '.';
20
21/* In theory multibyte decimal_points are possible, but
22 * Lua CJSON only supports UTF-8 and known locales only have
23 * single byte decimal points ([.,]).
24 *
25 * localconv() may not be thread safe (=>crash), and nl_langinfo() is
26 * not supported on some platforms. Use sprintf() instead - if the
27 * locale does change, at least Lua CJSON won't crash. */
28static void fpconv_update_locale()
29{
30 char buf[8];
31
32 snprintf(buf, sizeof(buf), "%g", 0.5);
33
34 /* Failing this test might imply the platform has a buggy dtoa
35 * implementation or wide characters */
36 if (buf[0] != '0' || buf[2] != '5' || buf[3] != 0) {
37 fprintf(stderr, "Error: wide characters found or printf() bug.");
38 abort();
39 }
40
41 locale_decimal_point = buf[1];
42}
43
44/* Check for a valid number character: [-+0-9a-yA-Y.]
45 * Eg: -0.6e+5, infinity, 0xF0.F0pF0
46 *
47 * Used to find the probable end of a number. It doesn't matter if
48 * invalid characters are counted - strtod() will find the valid
49 * number if it exists. The risk is that slightly more memory might
50 * be allocated before a parse error occurs. */
51static inline int valid_number_character(char ch)
52{
53 char lower_ch;
54
55 if ('0' <= ch && ch <= '9')
56 return 1;
57 if (ch == '-' || ch == '+' || ch == '.')
58 return 1;
59
60 /* Hex digits, exponent (e), base (p), "infinity",.. */
61 lower_ch = ch | 0x20;
62 if ('a' <= lower_ch && lower_ch <= 'y')
63 return 1;
64
65 return 0;
66}
67
68/* Calculate the size of the buffer required for a strtod locale
69 * conversion. */
70static int strtod_buffer_size(const char *s)
71{
72 const char *p = s;
73
74 while (valid_number_character(*p))
75 p++;
76
77 return p - s;
78}
79
80/* Similar to strtod(), but must be passed the current locale's decimal point
81 * character. Guaranteed to be called at the start of any valid number in a string */
82double fpconv_strtod(const char *nptr, char **endptr)
83{
84 char *buf, *endbuf, *dp;
85 int buflen;
86 double value;
87
88 /* System strtod() is fine when decimal point is '.' */
89 if (locale_decimal_point == '.')
90 return strtod(nptr, endptr);
91
92 buflen = strtod_buffer_size(nptr);
93 if (!buflen) {
94 /* No valid characters found, standard strtod() return */
95 *endptr = (char *)nptr;
96 return 0;
97 }
98
99 /* Duplicate number into buffer */
100 buf = malloc(buflen + 1);
101 if (!buf) {
102 fprintf(stderr, "Out of memory");
103 abort();
104 }
105 memcpy(buf, nptr, buflen);
106 buf[buflen] = 0;
107
108 /* Update decimal point character if found */
109 dp = strchr(buf, '.');
110 if (dp)
111 *dp = locale_decimal_point;
112
113 value = strtod(buf, &endbuf);
114 *endptr = (char *)&nptr[endbuf - buf];
115 free(buf);
116
117 return value;
118}
119
120/* "fmt" must point to a buffer of at least 6 characters */
121static void set_number_format(char *fmt, int precision)
122{
123 int d1, d2, i;
124
125 assert(1 <= precision && precision <= 14);
126
127 /* Create printf format (%.14g) from precision */
128 d1 = precision / 10;
129 d2 = precision % 10;
130 fmt[0] = '%';
131 fmt[1] = '.';
132 i = 2;
133 if (d1) {
134 fmt[i++] = '0' + d1;
135 }
136 fmt[i++] = '0' + d2;
137 fmt[i++] = 'g';
138 fmt[i] = 0;
139}
140
141/* Assumes there is always at least 32 characters available in the target buffer */
142int fpconv_g_fmt(char *str, double num, int precision)
143{
144 char buf[FPCONV_G_FMT_BUFSIZE];
145 char fmt[6];
146 int len;
147 char *b;
148
149 set_number_format(fmt, precision);
150
151 /* Pass through when decimal point character is dot. */
152 if (locale_decimal_point == '.')
153 return snprintf(str, FPCONV_G_FMT_BUFSIZE, fmt, num);
154
155 /* snprintf() to a buffer then translate for other decimal point characters */
156 len = snprintf(buf, FPCONV_G_FMT_BUFSIZE, fmt, num);
157
158 /* Copy into target location. Translate decimal point if required */
159 b = buf;
160 do {
161 *str++ = (*b == locale_decimal_point ? '.' : *b);
162 } while(*b++);
163
164 return len;
165}
166
167void fpconv_init()
168{
169 fpconv_update_locale();
170}
171
172/* vi:ai et sw=4 ts=4:
173 */
diff --git a/fpconv.h b/fpconv.h
new file mode 100644
index 0000000..ea875c0
--- /dev/null
+++ b/fpconv.h
@@ -0,0 +1,14 @@
1/* Lua CJSON floating point conversion routines */
2
3/* Buffer required to store the largest string representation of a double.
4 *
5 * Longest double printed with %.14g is 21 characters long:
6 * -1.7976931348623e+308 */
7# define FPCONV_G_FMT_BUFSIZE 32
8
9extern void fpconv_init();
10extern int fpconv_g_fmt(char*, double, int);
11extern double fpconv_strtod(const char*, char**);
12
13/* vi:ai et sw=4 ts=4:
14 */
diff --git a/lua-cjson-1.0devel-1.rockspec b/lua-cjson-1.0devel-1.rockspec
new file mode 100644
index 0000000..315ec2f
--- /dev/null
+++ b/lua-cjson-1.0devel-1.rockspec
@@ -0,0 +1,47 @@
1package = "lua-cjson"
2version = "1.0devel-1"
3
4source = {
5 url = "http://www.kyne.com.au/~mark/software/lua-cjson-1.0devel.zip",
6}
7
8description = {
9 summary = "A fast JSON encoding/parsing library",
10 detailed = [[
11 The Lua CJSON library provides JSON support for Lua. It features:
12 - Fast, standards compliant encoding/parsing routines
13 - Full support for JSON with UTF-8, including decoding surrogate pairs
14 - Optional run-time support for common exceptions to the JSON specification
15 (NaN, Infinity,..)
16 - No external dependencies
17 ]],
18 homepage = "http://www.kyne.com.au/~mark/software/lua-cjson.php",
19 license = "MIT"
20}
21
22dependencies = {
23 "lua >= 5.1"
24}
25
26build = {
27 type = "builtin",
28 modules = {
29 cjson = {
30 sources = { "lua_cjson.c", "strbuf.c", "fpconv.c" },
31 defines = {
32-- LuaRocks does not support platform specific configuration for Solaris.
33-- Uncomment the line below on Solaris platforms if required.
34-- "USE_INTERNAL_ISINF"
35 }
36 }
37 },
38 -- Override default build options (per platform)
39 platforms = {
40 win32 = { modules = { cjson = { defines = {
41 "DISABLE_INVALID_NUMBERS"
42 } } } }
43 },
44 copy_directories = { "tests" }
45}
46
47-- vi:ai et sw=4 ts=4:
diff --git a/lua-cjson.spec b/lua-cjson.spec
new file mode 100644
index 0000000..4c949e5
--- /dev/null
+++ b/lua-cjson.spec
@@ -0,0 +1,64 @@
1%define luaver 5.1
2%define lualibdir %{_libdir}/lua/%{luaver}
3
4Name: lua-cjson
5Version: 1.0devel
6Release: 1%{?dist}
7Summary: A fast JSON encoding/parsing library for Lua
8
9Group: Development/Libraries
10License: MIT
11URL: http://www.kyne.com.au/~mark/software/lua-cjson/
12Source0: http://www.kyne.com.au/~mark/software/lua-cjson/lua-cjson-%{version}.tar.gz
13BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
14
15BuildRequires: lua >= %{luaver}, lua-devel >= %{luaver}
16Requires: lua >= %{luaver}
17
18%description
19The Lua CJSON library provides JSON support for Lua. It features:
20- Fast, standards compliant encoding/parsing routines
21- Full support for JSON with UTF-8, including decoding surrogate pairs
22- Optional run-time support for common exceptions to the JSON specification
23 (NaN, Infinity,..)
24- No external dependencies
25
26
27%prep
28%setup -q
29
30
31%build
32make %{?_smp_mflags} CFLAGS="%{optflags}" LUA_INCLUDE_DIR="%{_includedir}"
33
34
35%install
36rm -rf "$RPM_BUILD_ROOT"
37make install DESTDIR="$RPM_BUILD_ROOT" LUA_MODULE_DIR="%{lualibdir}"
38
39
40%clean
41rm -rf "$RPM_BUILD_ROOT"
42
43
44%files
45%defattr(-,root,root,-)
46%doc LICENSE NEWS performance.txt manual.html manual.txt rfc4627.txt tests THANKS
47%{lualibdir}/*
48
49
50%changelog
51* Wed Nov 27 2011 Mark Pulford <mark@kyne.com.au> - 1.0.4-1
52- Updated for 1.0.4
53
54* Wed Sep 15 2011 Mark Pulford <mark@kyne.com.au> - 1.0.3-1
55- Updated for 1.0.3
56
57* Sun May 29 2011 Mark Pulford <mark@kyne.com.au> - 1.0.2-1
58- Updated for 1.0.2
59
60* Sun May 10 2011 Mark Pulford <mark@kyne.com.au> - 1.0.1-1
61- Updated for 1.0.1
62
63* Sun May 1 2011 Mark Pulford <mark@kyne.com.au> - 1.0-1
64- Initial package
diff --git a/lua_cjson.c b/lua_cjson.c
new file mode 100644
index 0000000..708e695
--- /dev/null
+++ b/lua_cjson.c
@@ -0,0 +1,1344 @@
1/* CJSON - JSON support for Lua
2 *
3 * Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au>
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 <assert.h>
40#include <string.h>
41#include <math.h>
42#include <lua.h>
43#include <lauxlib.h>
44
45#include "strbuf.h"
46#include "fpconv.h"
47
48#ifndef CJSON_MODNAME
49#define CJSON_MODNAME "cjson"
50#endif
51
52#ifndef CJSON_VERSION
53#define CJSON_VERSION "1.0devel"
54#endif
55
56/* Workaround for Solaris platforms missing isinf() */
57#if !defined(isinf) && (defined(USE_INTERNAL_ISINF) || defined(MISSING_ISINF))
58#define isinf(x) (!isnan(x) && isnan((x) - (x)))
59#endif
60
61#define DEFAULT_SPARSE_CONVERT 0
62#define DEFAULT_SPARSE_RATIO 2
63#define DEFAULT_SPARSE_SAFE 10
64#define DEFAULT_MAX_DEPTH 20
65#define DEFAULT_ENCODE_REFUSE_BADNUM 1
66#define DEFAULT_DECODE_REFUSE_BADNUM 0
67#define DEFAULT_ENCODE_KEEP_BUFFER 1
68#define DEFAULT_ENCODE_NUMBER_PRECISION 14
69
70#ifdef DISABLE_INVALID_NUMBERS
71#undef DEFAULT_DECODE_REFUSE_BADNUM
72#define DEFAULT_DECODE_REFUSE_BADNUM 1
73#endif
74
75typedef enum {
76 T_OBJ_BEGIN,
77 T_OBJ_END,
78 T_ARR_BEGIN,
79 T_ARR_END,
80 T_STRING,
81 T_NUMBER,
82 T_BOOLEAN,
83 T_NULL,
84 T_COLON,
85 T_COMMA,
86 T_END,
87 T_WHITESPACE,
88 T_ERROR,
89 T_UNKNOWN
90} json_token_type_t;
91
92static const char *json_token_type_name[] = {
93 "T_OBJ_BEGIN",
94 "T_OBJ_END",
95 "T_ARR_BEGIN",
96 "T_ARR_END",
97 "T_STRING",
98 "T_NUMBER",
99 "T_BOOLEAN",
100 "T_NULL",
101 "T_COLON",
102 "T_COMMA",
103 "T_END",
104 "T_WHITESPACE",
105 "T_ERROR",
106 "T_UNKNOWN",
107 NULL
108};
109
110typedef struct {
111 json_token_type_t ch2token[256];
112 char escape2char[256]; /* Decoding */
113
114 /* encode_buf is only allocated and used when
115 * encode_keep_buffer is set */
116 strbuf_t encode_buf;
117
118 int encode_sparse_convert;
119 int encode_sparse_ratio;
120 int encode_sparse_safe;
121 int encode_max_depth;
122 int encode_refuse_badnum;
123 int decode_refuse_badnum;
124 int encode_keep_buffer;
125 int encode_number_precision;
126} json_config_t;
127
128typedef struct {
129 const char *data;
130 int index;
131 strbuf_t *tmp; /* Temporary storage for strings */
132 json_config_t *cfg;
133} json_parse_t;
134
135typedef struct {
136 json_token_type_t type;
137 int index;
138 union {
139 const char *string;
140 double number;
141 int boolean;
142 } value;
143 int string_len;
144} json_token_t;
145
146static const char *char2escape[256] = {
147 "\\u0000", "\\u0001", "\\u0002", "\\u0003",
148 "\\u0004", "\\u0005", "\\u0006", "\\u0007",
149 "\\b", "\\t", "\\n", "\\u000b",
150 "\\f", "\\r", "\\u000e", "\\u000f",
151 "\\u0010", "\\u0011", "\\u0012", "\\u0013",
152 "\\u0014", "\\u0015", "\\u0016", "\\u0017",
153 "\\u0018", "\\u0019", "\\u001a", "\\u001b",
154 "\\u001c", "\\u001d", "\\u001e", "\\u001f",
155 NULL, NULL, "\\\"", NULL, NULL, NULL, NULL, NULL,
156 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,
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, "\\u007f",
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 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
171 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
172 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
173 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
174 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
175 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
176 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
177 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
178 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
179 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
180 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
181 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
182 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
183};
184
185/* ===== CONFIGURATION ===== */
186
187static json_config_t *json_fetch_config(lua_State *l)
188{
189 json_config_t *cfg;
190
191 cfg = lua_touserdata(l, lua_upvalueindex(1));
192 if (!cfg)
193 luaL_error(l, "BUG: Unable to fetch CJSON configuration");
194
195 return cfg;
196}
197
198static void json_verify_arg_count(lua_State *l, int args)
199{
200 luaL_argcheck(l, lua_gettop(l) <= args, args + 1,
201 "found too many arguments");
202}
203
204/* Configures handling of extremely sparse arrays:
205 * convert: Convert extremely sparse arrays into objects? Otherwise error.
206 * ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio
207 * safe: Always use an array when the max index <= safe */
208static int json_cfg_encode_sparse_array(lua_State *l)
209{
210 json_config_t *cfg;
211 int val;
212
213 json_verify_arg_count(l, 3);
214 cfg = json_fetch_config(l);
215
216 switch (lua_gettop(l)) {
217 case 3:
218 val = luaL_checkinteger(l, 3);
219 luaL_argcheck(l, val >= 0, 3, "expected integer >= 0");
220 cfg->encode_sparse_safe = val;
221 case 2:
222 val = luaL_checkinteger(l, 2);
223 luaL_argcheck(l, val >= 0, 2, "expected integer >= 0");
224 cfg->encode_sparse_ratio = val;
225 case 1:
226 luaL_argcheck(l, lua_isboolean(l, 1), 1, "expected boolean");
227 cfg->encode_sparse_convert = lua_toboolean(l, 1);
228 }
229
230 lua_pushboolean(l, cfg->encode_sparse_convert);
231 lua_pushinteger(l, cfg->encode_sparse_ratio);
232 lua_pushinteger(l, cfg->encode_sparse_safe);
233
234 return 3;
235}
236
237/* Configures the maximum number of nested arrays/objects allowed when
238 * encoding */
239static int json_cfg_encode_max_depth(lua_State *l)
240{
241 json_config_t *cfg;
242 int depth;
243
244 json_verify_arg_count(l, 1);
245 cfg = json_fetch_config(l);
246
247 if (lua_gettop(l)) {
248 depth = luaL_checkinteger(l, 1);
249 luaL_argcheck(l, depth > 0, 1, "expected positive integer");
250 cfg->encode_max_depth = depth;
251 }
252
253 lua_pushinteger(l, cfg->encode_max_depth);
254
255 return 1;
256}
257
258/* Configures number precision when converting doubles to text */
259static int json_cfg_encode_number_precision(lua_State *l)
260{
261 json_config_t *cfg;
262 int precision;
263
264 json_verify_arg_count(l, 1);
265 cfg = json_fetch_config(l);
266
267 if (lua_gettop(l)) {
268 precision = luaL_checkinteger(l, 1);
269 luaL_argcheck(l, 1 <= precision && precision <= 14, 1,
270 "expected integer between 1 and 14");
271 cfg->encode_number_precision = precision;
272 }
273
274 lua_pushinteger(l, cfg->encode_number_precision);
275
276 return 1;
277}
278
279/* Configures JSON encoding buffer persistence */
280static int json_cfg_encode_keep_buffer(lua_State *l)
281{
282 json_config_t *cfg;
283 int old_value;
284
285 json_verify_arg_count(l, 1);
286 cfg = json_fetch_config(l);
287
288 old_value = cfg->encode_keep_buffer;
289
290 if (lua_gettop(l)) {
291 luaL_checktype(l, 1, LUA_TBOOLEAN);
292 cfg->encode_keep_buffer = lua_toboolean(l, 1);
293 }
294
295 /* Init / free the buffer if the setting has changed */
296 if (old_value ^ cfg->encode_keep_buffer) {
297 if (cfg->encode_keep_buffer)
298 strbuf_init(&cfg->encode_buf, 0);
299 else
300 strbuf_free(&cfg->encode_buf);
301 }
302
303 lua_pushboolean(l, cfg->encode_keep_buffer);
304
305 return 1;
306}
307
308/* On argument: decode enum and set config variables
309 * **options must point to a NULL terminated array of 4 enums
310 * Returns: current enum value */
311static void json_enum_option(lua_State *l, const char **options,
312 int *opt1, int *opt2)
313{
314 int setting;
315
316 if (lua_gettop(l)) {
317 if (lua_isboolean(l, 1))
318 setting = lua_toboolean(l, 1) * 3;
319 else
320 setting = luaL_checkoption(l, 1, NULL, options);
321
322 *opt1 = setting & 1 ? 1 : 0;
323 *opt2 = setting & 2 ? 1 : 0;
324 } else {
325 setting = *opt1 | (*opt2 << 1);
326 }
327
328 if (setting)
329 lua_pushstring(l, options[setting]);
330 else
331 lua_pushboolean(l, 0);
332}
333
334
335/* When enabled, rejects: NaN, Infinity, hexadecimal numbers */
336static int json_cfg_refuse_invalid_numbers(lua_State *l)
337{
338 static const char *options_enc_dec[] = { "none", "encode", "decode",
339 "both", NULL };
340 json_config_t *cfg;
341
342 json_verify_arg_count(l, 1);
343 cfg = json_fetch_config(l);
344
345 json_enum_option(l, options_enc_dec,
346 &cfg->encode_refuse_badnum,
347 &cfg->decode_refuse_badnum);
348
349#if DISABLE_INVALID_NUMBERS
350 /* Some non-POSIX platforms don't handle double <-> string translations
351 * for Infinity/NaN/hexadecimal properly. Throw an error if the
352 * user attempts to enable them. */
353 if (!cfg->encode_refuse_badnum || !cfg->decode_refuse_badnum) {
354 cfg->encode_refuse_badnum = cfg->decode_refuse_badnum = 1;
355 luaL_error(l, "Infinity, NaN, and/or hexadecimal numbers are not supported.");
356 }
357#endif
358
359 return 1;
360}
361
362static int json_destroy_config(lua_State *l)
363{
364 json_config_t *cfg;
365
366 cfg = lua_touserdata(l, 1);
367 if (cfg)
368 strbuf_free(&cfg->encode_buf);
369 cfg = NULL;
370
371 return 0;
372}
373
374static void json_create_config(lua_State *l)
375{
376 json_config_t *cfg;
377 int i;
378
379 cfg = lua_newuserdata(l, sizeof(*cfg));
380
381 /* Create GC method to clean up strbuf */
382 lua_newtable(l);
383 lua_pushcfunction(l, json_destroy_config);
384 lua_setfield(l, -2, "__gc");
385 lua_setmetatable(l, -2);
386
387 cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT;
388 cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO;
389 cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE;
390 cfg->encode_max_depth = DEFAULT_MAX_DEPTH;
391 cfg->encode_refuse_badnum = DEFAULT_ENCODE_REFUSE_BADNUM;
392 cfg->decode_refuse_badnum = DEFAULT_DECODE_REFUSE_BADNUM;
393 cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER;
394 cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION;
395
396#if DEFAULT_ENCODE_KEEP_BUFFER > 0
397 strbuf_init(&cfg->encode_buf, 0);
398#endif
399
400 /* Decoding init */
401
402 /* Tag all characters as an error */
403 for (i = 0; i < 256; i++)
404 cfg->ch2token[i] = T_ERROR;
405
406 /* Set tokens that require no further processing */
407 cfg->ch2token['{'] = T_OBJ_BEGIN;
408 cfg->ch2token['}'] = T_OBJ_END;
409 cfg->ch2token['['] = T_ARR_BEGIN;
410 cfg->ch2token[']'] = T_ARR_END;
411 cfg->ch2token[','] = T_COMMA;
412 cfg->ch2token[':'] = T_COLON;
413 cfg->ch2token['\0'] = T_END;
414 cfg->ch2token[' '] = T_WHITESPACE;
415 cfg->ch2token['\t'] = T_WHITESPACE;
416 cfg->ch2token['\n'] = T_WHITESPACE;
417 cfg->ch2token['\r'] = T_WHITESPACE;
418
419 /* Update characters that require further processing */
420 cfg->ch2token['f'] = T_UNKNOWN; /* false? */
421 cfg->ch2token['i'] = T_UNKNOWN; /* inf, ininity? */
422 cfg->ch2token['I'] = T_UNKNOWN;
423 cfg->ch2token['n'] = T_UNKNOWN; /* null, nan? */
424 cfg->ch2token['N'] = T_UNKNOWN;
425 cfg->ch2token['t'] = T_UNKNOWN; /* true? */
426 cfg->ch2token['"'] = T_UNKNOWN; /* string? */
427 cfg->ch2token['+'] = T_UNKNOWN; /* number? */
428 cfg->ch2token['-'] = T_UNKNOWN;
429 for (i = 0; i < 10; i++)
430 cfg->ch2token['0' + i] = T_UNKNOWN;
431
432 /* Lookup table for parsing escape characters */
433 for (i = 0; i < 256; i++)
434 cfg->escape2char[i] = 0; /* String error */
435 cfg->escape2char['"'] = '"';
436 cfg->escape2char['\\'] = '\\';
437 cfg->escape2char['/'] = '/';
438 cfg->escape2char['b'] = '\b';
439 cfg->escape2char['t'] = '\t';
440 cfg->escape2char['n'] = '\n';
441 cfg->escape2char['f'] = '\f';
442 cfg->escape2char['r'] = '\r';
443 cfg->escape2char['u'] = 'u'; /* Unicode parsing required */
444}
445
446/* ===== ENCODING ===== */
447
448static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex,
449 const char *reason)
450{
451 if (!cfg->encode_keep_buffer)
452 strbuf_free(json);
453 luaL_error(l, "Cannot serialise %s: %s",
454 lua_typename(l, lua_type(l, lindex)), reason);
455}
456
457/* json_append_string args:
458 * - lua_State
459 * - JSON strbuf
460 * - String (Lua stack index)
461 *
462 * Returns nothing. Doesn't remove string from Lua stack */
463static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
464{
465 const char *escstr;
466 int i;
467 const char *str;
468 size_t len;
469
470 str = lua_tolstring(l, lindex, &len);
471
472 /* Worst case is len * 6 (all unicode escapes).
473 * This buffer is reused constantly for small strings
474 * If there are any excess pages, they won't be hit anyway.
475 * This gains ~5% speedup. */
476 strbuf_ensure_empty_length(json, len * 6 + 2);
477
478 strbuf_append_char_unsafe(json, '\"');
479 for (i = 0; i < len; i++) {
480 escstr = char2escape[(unsigned char)str[i]];
481 if (escstr)
482 strbuf_append_string(json, escstr);
483 else
484 strbuf_append_char_unsafe(json, str[i]);
485 }
486 strbuf_append_char_unsafe(json, '\"');
487}
488
489/* Find the size of the array on the top of the Lua stack
490 * -1 object (not a pure array)
491 * >=0 elements in array
492 */
493static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json)
494{
495 double k;
496 int max;
497 int items;
498
499 max = 0;
500 items = 0;
501
502 lua_pushnil(l);
503 /* table, startkey */
504 while (lua_next(l, -2) != 0) {
505 /* table, key, value */
506 if (lua_type(l, -2) == LUA_TNUMBER &&
507 (k = lua_tonumber(l, -2))) {
508 /* Integer >= 1 ? */
509 if (floor(k) == k && k >= 1) {
510 if (k > max)
511 max = k;
512 items++;
513 lua_pop(l, 1);
514 continue;
515 }
516 }
517
518 /* Must not be an array (non integer key) */
519 lua_pop(l, 2);
520 return -1;
521 }
522
523 /* Encode excessively sparse arrays as objects (if enabled) */
524 if (cfg->encode_sparse_ratio > 0 &&
525 max > items * cfg->encode_sparse_ratio &&
526 max > cfg->encode_sparse_safe) {
527 if (!cfg->encode_sparse_convert)
528 json_encode_exception(l, cfg, json, -1, "excessively sparse array");
529
530 return -1;
531 }
532
533 return max;
534}
535
536static void json_check_encode_depth(lua_State *l, json_config_t *cfg,
537 int current_depth, strbuf_t *json)
538{
539 if (current_depth > cfg->encode_max_depth) {
540 if (!cfg->encode_keep_buffer)
541 strbuf_free(json);
542 luaL_error(l, "Cannot serialise, excessive nesting (%d)",
543 current_depth);
544 }
545}
546
547static void json_append_data(lua_State *l, json_config_t *cfg,
548 int current_depth, strbuf_t *json);
549
550/* json_append_array args:
551 * - lua_State
552 * - JSON strbuf
553 * - Size of passwd Lua array (top of stack) */
554static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth,
555 strbuf_t *json, int array_length)
556{
557 int comma, i;
558
559 strbuf_append_char(json, '[');
560
561 comma = 0;
562 for (i = 1; i <= array_length; i++) {
563 if (comma)
564 strbuf_append_char(json, ',');
565 else
566 comma = 1;
567
568 lua_rawgeti(l, -1, i);
569 json_append_data(l, cfg, current_depth, json);
570 lua_pop(l, 1);
571 }
572
573 strbuf_append_char(json, ']');
574}
575
576static void json_append_number(lua_State *l, json_config_t *cfg,
577 strbuf_t *json, int lindex)
578{
579 double num = lua_tonumber(l, lindex);
580 int len;
581
582 if (cfg->encode_refuse_badnum && (isinf(num) || isnan(num)))
583 json_encode_exception(l, cfg, json, lindex, "must not be NaN or Inf");
584
585 if (isnan(num)) {
586 /* Some platforms may print -nan, just hard code it */
587 strbuf_append_mem(json, "nan", 3);
588 } else {
589 strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE);
590 len = fpconv_g_fmt(strbuf_empty_ptr(json), num, cfg->encode_number_precision);
591 strbuf_extend_length(json, len);
592 }
593}
594
595static void json_append_object(lua_State *l, json_config_t *cfg,
596 int current_depth, strbuf_t *json)
597{
598 int comma, keytype;
599
600 /* Object */
601 strbuf_append_char(json, '{');
602
603 lua_pushnil(l);
604 /* table, startkey */
605 comma = 0;
606 while (lua_next(l, -2) != 0) {
607 if (comma)
608 strbuf_append_char(json, ',');
609 else
610 comma = 1;
611
612 /* table, key, value */
613 keytype = lua_type(l, -2);
614 if (keytype == LUA_TNUMBER) {
615 strbuf_append_char(json, '"');
616 json_append_number(l, cfg, json, -2);
617 strbuf_append_mem(json, "\":", 2);
618 } else if (keytype == LUA_TSTRING) {
619 json_append_string(l, json, -2);
620 strbuf_append_char(json, ':');
621 } else {
622 json_encode_exception(l, cfg, json, -2,
623 "table key must be a number or string");
624 /* never returns */
625 }
626
627 /* table, key, value */
628 json_append_data(l, cfg, current_depth, json);
629 lua_pop(l, 1);
630 /* table, key */
631 }
632
633 strbuf_append_char(json, '}');
634}
635
636/* Serialise Lua data into JSON string. */
637static void json_append_data(lua_State *l, json_config_t *cfg,
638 int current_depth, strbuf_t *json)
639{
640 int len;
641
642 switch (lua_type(l, -1)) {
643 case LUA_TSTRING:
644 json_append_string(l, json, -1);
645 break;
646 case LUA_TNUMBER:
647 json_append_number(l, cfg, json, -1);
648 break;
649 case LUA_TBOOLEAN:
650 if (lua_toboolean(l, -1))
651 strbuf_append_mem(json, "true", 4);
652 else
653 strbuf_append_mem(json, "false", 5);
654 break;
655 case LUA_TTABLE:
656 len = lua_array_length(l, cfg, json);
657 current_depth++;
658 json_check_encode_depth(l, cfg, current_depth, json);
659 if (len > 0)
660 json_append_array(l, cfg, current_depth, json, len);
661 else
662 json_append_object(l, cfg, current_depth, json);
663 break;
664 case LUA_TNIL:
665 strbuf_append_mem(json, "null", 4);
666 break;
667 case LUA_TLIGHTUSERDATA:
668 if (lua_touserdata(l, -1) == NULL) {
669 strbuf_append_mem(json, "null", 4);
670 break;
671 }
672 default:
673 /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD,
674 * and LUA_TLIGHTUSERDATA) cannot be serialised */
675 json_encode_exception(l, cfg, json, -1, "type not supported");
676 /* never returns */
677 }
678}
679
680static int json_encode(lua_State *l)
681{
682 json_config_t *cfg;
683 strbuf_t local_encode_buf;
684 strbuf_t *encode_buf;
685 char *json;
686 int len;
687
688 /* Can't use json_verify_arg_count() since we need to ensure
689 * there is only 1 argument */
690 luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
691
692 cfg = json_fetch_config(l);
693
694 if (!cfg->encode_keep_buffer) {
695 /* Use private buffer */
696 encode_buf = &local_encode_buf;
697 strbuf_init(encode_buf, 0);
698 } else {
699 /* Reuse existing buffer */
700 encode_buf = &cfg->encode_buf;
701 strbuf_reset(encode_buf);
702 }
703
704 json_append_data(l, cfg, 0, encode_buf);
705 json = strbuf_string(encode_buf, &len);
706
707 lua_pushlstring(l, json, len);
708
709 if (!cfg->encode_keep_buffer)
710 strbuf_free(encode_buf);
711
712 return 1;
713}
714
715/* ===== DECODING ===== */
716
717static void json_process_value(lua_State *l, json_parse_t *json,
718 json_token_t *token);
719
720static 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
733static 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 */
756static 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 */
800static 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
858static 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
866static 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 * json->tmp is sized to handle JSON containing only a string value.
880 */
881 strbuf_reset(json->tmp);
882
883 while ((ch = json->data[json->index]) != '"') {
884 if (!ch) {
885 /* Premature end of the string */
886 json_set_token_error(token, json, "unexpected end of string");
887 return;
888 }
889
890 /* Handle escapes */
891 if (ch == '\\') {
892 /* Fetch escape character */
893 ch = json->data[json->index + 1];
894
895 /* Translate escape code and append to tmp string */
896 ch = escape2char[(unsigned char)ch];
897 if (ch == 'u') {
898 if (json_append_unicode_escape(json) == 0)
899 continue;
900
901 json_set_token_error(token, json,
902 "invalid unicode escape code");
903 return;
904 }
905 if (!ch) {
906 json_set_token_error(token, json, "invalid escape code");
907 return;
908 }
909
910 /* Skip '\' */
911 json->index++;
912 }
913 /* Append normal character or translated single character
914 * Unicode escapes are handled above */
915 strbuf_append_char_unsafe(json->tmp, ch);
916 json->index++;
917 }
918 json->index++; /* Eat final quote (") */
919
920 strbuf_ensure_null(json->tmp);
921
922 token->type = T_STRING;
923 token->value.string = strbuf_string(json->tmp, &token->string_len);
924}
925
926/* JSON numbers should take the following form:
927 * -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)?
928 *
929 * json_next_number_token() uses strtod() which allows other forms:
930 * - numbers starting with '+'
931 * - NaN, -NaN, infinity, -infinity
932 * - hexadecimal numbers
933 * - numbers with leading zeros
934 *
935 * json_is_invalid_number() detects "numbers" which may pass strtod()'s
936 * error checking, but should not be allowed with strict JSON.
937 *
938 * json_is_invalid_number() may pass numbers which cause strtod()
939 * to generate an error.
940 */
941static int json_is_invalid_number(json_parse_t *json)
942{
943 int i = json->index;
944
945 /* Reject numbers starting with + */
946 if (json->data[i] == '+')
947 return 1;
948
949 /* Skip minus sign if it exists */
950 if (json->data[i] == '-')
951 i++;
952
953 /* Reject numbers starting with 0x, or leading zeros */
954 if (json->data[i] == '0') {
955 int ch2 = json->data[i + 1];
956
957 if ((ch2 | 0x20) == 'x' || /* Hex */
958 ('0' <= ch2 && ch2 <= '9')) /* Leading zero */
959 return 1;
960
961 return 0;
962 } else if (json->data[i] <= '9') {
963 return 0; /* Ordinary number */
964 }
965
966 /* Reject inf/nan */
967 if (!strncasecmp(&json->data[i], "inf", 3))
968 return 1;
969 if (!strncasecmp(&json->data[i], "nan", 3))
970 return 1;
971
972 /* Pass all other numbers which may still be invalid, but
973 * strtod() will catch them. */
974 return 0;
975}
976
977static void json_next_number_token(json_parse_t *json, json_token_t *token)
978{
979 const char *startptr;
980 char *endptr;
981
982 token->type = T_NUMBER;
983 startptr = &json->data[json->index];
984 token->value.number = fpconv_strtod(&json->data[json->index], &endptr);
985 if (startptr == endptr)
986 json_set_token_error(token, json, "invalid number");
987 else
988 json->index += endptr - startptr; /* Skip the processed number */
989
990 return;
991}
992
993/* Fills in the token struct.
994 * T_STRING will return a pointer to the json_parse_t temporary string
995 * T_ERROR will leave the json->index pointer at the error.
996 */
997static void json_next_token(json_parse_t *json, json_token_t *token)
998{
999 json_token_type_t *ch2token = json->cfg->ch2token;
1000 int ch;
1001
1002 /* Eat whitespace. FIXME: UGLY */
1003 token->type = ch2token[(unsigned char)json->data[json->index]];
1004 while (token->type == T_WHITESPACE)
1005 token->type = ch2token[(unsigned char)json->data[++json->index]];
1006
1007 token->index = json->index;
1008
1009 /* Don't advance the pointer for an error or the end */
1010 if (token->type == T_ERROR) {
1011 json_set_token_error(token, json, "invalid token");
1012 return;
1013 }
1014
1015 if (token->type == T_END) {
1016 return;
1017 }
1018
1019 /* Found a known single character token, advance index and return */
1020 if (token->type != T_UNKNOWN) {
1021 json->index++;
1022 return;
1023 }
1024
1025 /* Process characters which triggered T_UNKNOWN */
1026 ch = json->data[json->index];
1027
1028 /* Must use strncmp() to match the front of the JSON string.
1029 * JSON identifier must be lowercase.
1030 * When strict_numbers if disabled, either case is allowed for
1031 * Infinity/NaN (since we are no longer following the spec..) */
1032 if (ch == '"') {
1033 json_next_string_token(json, token);
1034 return;
1035 } else if (ch == '-' || ('0' <= ch && ch <= '9')) {
1036 if (json->cfg->decode_refuse_badnum && json_is_invalid_number(json)) {
1037 json_set_token_error(token, json, "invalid number");
1038 return;
1039 }
1040 json_next_number_token(json, token);
1041 return;
1042 } else if (!strncmp(&json->data[json->index], "true", 4)) {
1043 token->type = T_BOOLEAN;
1044 token->value.boolean = 1;
1045 json->index += 4;
1046 return;
1047 } else if (!strncmp(&json->data[json->index], "false", 5)) {
1048 token->type = T_BOOLEAN;
1049 token->value.boolean = 0;
1050 json->index += 5;
1051 return;
1052 } else if (!strncmp(&json->data[json->index], "null", 4)) {
1053 token->type = T_NULL;
1054 json->index += 4;
1055 return;
1056 } else if (!json->cfg->decode_refuse_badnum &&
1057 json_is_invalid_number(json)) {
1058 /* When refuse_badnum is disabled, only attempt to process
1059 * numbers we know are invalid JSON (Inf, NaN, hex)
1060 * This is required to generate an appropriate token error,
1061 * otherwise all bad tokens will register as "invalid number"
1062 */
1063 json_next_number_token(json, token);
1064 return;
1065 }
1066
1067 /* Token starts with t/f/n but isn't recognised above. */
1068 json_set_token_error(token, json, "invalid token");
1069}
1070
1071/* This function does not return.
1072 * DO NOT CALL WITH DYNAMIC MEMORY ALLOCATED.
1073 * The only supported exception is the temporary parser string
1074 * json->tmp struct.
1075 * json and token should exist on the stack somewhere.
1076 * luaL_error() will long_jmp and release the stack */
1077static void json_throw_parse_error(lua_State *l, json_parse_t *json,
1078 const char *exp, json_token_t *token)
1079{
1080 const char *found;
1081
1082 strbuf_free(json->tmp);
1083
1084 if (token->type == T_ERROR)
1085 found = token->value.string;
1086 else
1087 found = json_token_type_name[token->type];
1088
1089 /* Note: token->index is 0 based, display starting from 1 */
1090 luaL_error(l, "Expected %s but found %s at character %d",
1091 exp, found, token->index + 1);
1092}
1093
1094static void json_decode_checkstack(lua_State *l, json_parse_t *json, int n)
1095{
1096 if (lua_checkstack(l, n))
1097 return;
1098
1099 strbuf_free(json->tmp);
1100 luaL_error(l, "Too many nested data structures");
1101}
1102
1103static void json_parse_object_context(lua_State *l, json_parse_t *json)
1104{
1105 json_token_t token;
1106
1107 /* 3 slots required:
1108 * .., table, key, value */
1109 json_decode_checkstack(l, json, 3);
1110
1111 lua_newtable(l);
1112
1113 json_next_token(json, &token);
1114
1115 /* Handle empty objects */
1116 if (token.type == T_OBJ_END) {
1117 return;
1118 }
1119
1120 while (1) {
1121 if (token.type != T_STRING)
1122 json_throw_parse_error(l, json, "object key string", &token);
1123
1124 /* Push key */
1125 lua_pushlstring(l, token.value.string, token.string_len);
1126
1127 json_next_token(json, &token);
1128 if (token.type != T_COLON)
1129 json_throw_parse_error(l, json, "colon", &token);
1130
1131 /* Fetch value */
1132 json_next_token(json, &token);
1133 json_process_value(l, json, &token);
1134
1135 /* Set key = value */
1136 lua_rawset(l, -3);
1137
1138 json_next_token(json, &token);
1139
1140 if (token.type == T_OBJ_END)
1141 return;
1142
1143 if (token.type != T_COMMA)
1144 json_throw_parse_error(l, json, "comma or object end", &token);
1145
1146 json_next_token(json, &token);
1147 }
1148}
1149
1150/* Handle the array context */
1151static void json_parse_array_context(lua_State *l, json_parse_t *json)
1152{
1153 json_token_t token;
1154 int i;
1155
1156 /* 2 slots required:
1157 * .., table, value */
1158 json_decode_checkstack(l, json, 2);
1159
1160 lua_newtable(l);
1161
1162 json_next_token(json, &token);
1163
1164 /* Handle empty arrays */
1165 if (token.type == T_ARR_END)
1166 return;
1167
1168 for (i = 1; ; i++) {
1169 json_process_value(l, json, &token);
1170 lua_rawseti(l, -2, i); /* arr[i] = value */
1171
1172 json_next_token(json, &token);
1173
1174 if (token.type == T_ARR_END)
1175 return;
1176
1177 if (token.type != T_COMMA)
1178 json_throw_parse_error(l, json, "comma or array end", &token);
1179
1180 json_next_token(json, &token);
1181 }
1182}
1183
1184/* Handle the "value" context */
1185static void json_process_value(lua_State *l, json_parse_t *json,
1186 json_token_t *token)
1187{
1188 switch (token->type) {
1189 case T_STRING:
1190 lua_pushlstring(l, token->value.string, token->string_len);
1191 break;;
1192 case T_NUMBER:
1193 lua_pushnumber(l, token->value.number);
1194 break;;
1195 case T_BOOLEAN:
1196 lua_pushboolean(l, token->value.boolean);
1197 break;;
1198 case T_OBJ_BEGIN:
1199 json_parse_object_context(l, json);
1200 break;;
1201 case T_ARR_BEGIN:
1202 json_parse_array_context(l, json);
1203 break;;
1204 case T_NULL:
1205 /* In Lua, setting "t[k] = nil" will delete k from the table.
1206 * Hence a NULL pointer lightuserdata object is used instead */
1207 lua_pushlightuserdata(l, NULL);
1208 break;;
1209 default:
1210 json_throw_parse_error(l, json, "value", token);
1211 }
1212}
1213
1214/* json_text must be null terminated string */
1215static void lua_json_decode(lua_State *l, const char *json_text, int json_len)
1216{
1217 json_parse_t json;
1218 json_token_t token;
1219
1220 json.cfg = json_fetch_config(l);
1221 json.data = json_text;
1222 json.index = 0;
1223
1224 /* Ensure the temporary buffer can hold the entire string.
1225 * This means we no longer need to do length checks since the decoded
1226 * string must be smaller than the entire json string */
1227 json.tmp = strbuf_new(json_len);
1228
1229 json_next_token(&json, &token);
1230 json_process_value(l, &json, &token);
1231
1232 /* Ensure there is no more input left */
1233 json_next_token(&json, &token);
1234
1235 if (token.type != T_END)
1236 json_throw_parse_error(l, &json, "the end", &token);
1237
1238 strbuf_free(json.tmp);
1239}
1240
1241static int json_decode(lua_State *l)
1242{
1243 const char *json;
1244 size_t len;
1245
1246 json_verify_arg_count(l, 1);
1247
1248 json = luaL_checklstring(l, 1, &len);
1249
1250 /* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3)
1251 *
1252 * CJSON can support any simple data type, hence only the first
1253 * character is guaranteed to be ASCII (at worst: '"'). This is
1254 * still enough to detect whether the wrong encoding is in use. */
1255 if (len >= 2 && (!json[0] || !json[1]))
1256 luaL_error(l, "JSON parser does not support UTF-16 or UTF-32");
1257
1258 lua_json_decode(l, json, len);
1259
1260 return 1;
1261}
1262
1263/* ===== INITIALISATION ===== */
1264
1265#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
1266/* Compatibility for Lua 5.1.
1267 *
1268 * luaL_setfuncs() is used to create a module table where the functions have
1269 * json_config_t as their first upvalue. Code borrowed from Lua 5.2 source. */
1270static void luaL_setfuncs (lua_State *l, const luaL_Reg *reg, int nup)
1271{
1272 int i;
1273
1274 luaL_checkstack(l, nup, "too many upvalues");
1275 for (; reg->name != NULL; reg++) { /* fill the table with given functions */
1276 for (i = 0; i < nup; i++) /* copy upvalues to the top */
1277 lua_pushvalue(l, -nup);
1278 lua_pushcclosure(l, reg->func, nup); /* closure with those upvalues */
1279 lua_setfield(l, -(nup + 2), reg->name);
1280 }
1281 lua_pop(l, nup); /* remove upvalues */
1282}
1283#endif
1284
1285static int lua_cjson_new(lua_State *l)
1286{
1287 luaL_Reg reg[] = {
1288 { "encode", json_encode },
1289 { "decode", json_decode },
1290 { "encode_sparse_array", json_cfg_encode_sparse_array },
1291 { "encode_max_depth", json_cfg_encode_max_depth },
1292 { "encode_number_precision", json_cfg_encode_number_precision },
1293 { "encode_keep_buffer", json_cfg_encode_keep_buffer },
1294 { "refuse_invalid_numbers", json_cfg_refuse_invalid_numbers },
1295 { "new", lua_cjson_new },
1296 { NULL, NULL }
1297 };
1298
1299 /* Initialise number conversions */
1300 fpconv_init();
1301
1302 /* cjson module table */
1303 lua_newtable(l);
1304
1305 /* Register functions with config data as upvalue */
1306 json_create_config(l);
1307 luaL_setfuncs(l, reg, 1);
1308
1309 /* Set cjson.null */
1310 lua_pushlightuserdata(l, NULL);
1311 lua_setfield(l, -2, "null");
1312
1313 /* Set module name / version fields */
1314 lua_pushliteral(l, CJSON_MODNAME);
1315 lua_setfield(l, -2, "_NAME");
1316 lua_pushliteral(l, CJSON_VERSION);
1317 lua_setfield(l, -2, "_VERSION");
1318 lua_pushliteral(l, CJSON_VERSION);
1319 lua_setfield(l, -2, "version");
1320
1321 return 1;
1322}
1323
1324int luaopen_cjson(lua_State *l)
1325{
1326 lua_cjson_new(l);
1327
1328#if !defined(DISABLE_CJSON_GLOBAL) && (!defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502)
1329 /* Register a global "cjson" table to maintain compatibility with earlier
1330 * versions of Lua CJSON which used luaL_register() under Lua 5.1.
1331 *
1332 * From Lua 5.2 onwards, Lua CJSON does not automatically register a global
1333 * table.
1334 */
1335 lua_pushvalue(l, -1);
1336 lua_setglobal(l, CJSON_MODNAME);
1337#endif
1338
1339 /* Return cjson table */
1340 return 1;
1341}
1342
1343/* vi:ai et sw=4 ts=4:
1344 */
diff --git a/manual.txt b/manual.txt
new file mode 100644
index 0000000..72dd196
--- /dev/null
+++ b/manual.txt
@@ -0,0 +1,497 @@
1= Lua CJSON 1.0devel Manual =
2Mark Pulford <mark@kyne.com.au>
3:revdate: November 30, 2011
4
5Overview
6--------
7
8The Lua CJSON library provides JSON support for Lua.
9
10.Features
11- Fast, standards compliant encoding/parsing routines
12- Full support for JSON with UTF-8, including decoding surrogate
13 pairs
14- Optional run-time support for common exceptions to the JSON
15 specification (NaN, Infinity,..)
16- No external dependencies
17
18.Caveats
19- UTF-16 and UTF-32 are not supported.
20
21Lua CJSON is covered by the MIT license. Review the file +LICENSE+ for
22details.
23
24The latest version of this software is available from the
25http://www.kyne.com.au/~mark/software/lua-cjson.php[Lua CJSON website].
26
27Feel free to email me if you have any patches, suggestions, or
28comments.
29
30
31Installation Methods
32--------------------
33
34Lua CJSON requires either http://www.lua.org[Lua] 5.1, Lua 5.2, or
35http://www.luajit.org[LuaJIT] to build.
36
37There are 4 build methods available:
38
39[horizontal]
40Make:: Unix (including Linux, BSD, Mac OSX & Solaris)
41CMake:: Unix, Windows
42RPM:: Linux
43LuaRocks:: Unix, Windows
44
45
46Make
47~~~~
48
49Review and update the included Makefile to suit your platform. Next,
50build and install the module:
51
52[source,sh]
53make install
54
55Or install manually:
56
57[source,sh]
58make
59cp cjson.so $your_lua_module_directory
60
61
62CMake
63~~~~~
64
65http://www.cmake.org[CMake] can generate build configuration for many different
66platforms (including Unix and Windows).
67
68[source,sh]
69mkdir build
70cd build
71cmake ..
72make
73cp cjson.so $your_lua_module_directory
74
75Review the http://www.cmake.org/cmake/help/documentation.html[CMake documentation]
76for further details.
77
78
79RPM
80~~~
81
82Linux distributions using RPM should be able to create a package via
83the included RPM spec file. Install the +rpm-build+ package (or
84similar) then:
85
86[source,sh]
87rpmbuild -tb lua-cjson-1.0devel.tar.gz
88rpm -Uvh $newly_built_lua_cjson_rpm
89
90
91LuaRocks
92~~~~~~~~
93
94http://luarocks.org[LuaRocks] can be used to install and manage Lua
95modules on a wide range of platforms (including Windows).
96
97Extract the Lua CJSON source package into a directory and run:
98
99[source,sh]
100cd lua-cjson-1.0devel
101luarocks make
102
103[NOTE]
104LuaRocks does not support platform specific configuration for Solaris.
105On Solaris, you may need to manually uncomment +USE_INTERNAL_ISINF+ in
106the rockspec before building this module.
107
108Review the http://luarocks.org/en/Documentation[LuaRocks documentation]
109for further details.
110
111
112Build Options (#define)
113~~~~~~~~~~~~~~~~~~~~~~~
114
115[horizontal]
116USE_INTERNAL_ISINF:: Workaround for Solaris platforms missing ++isinf++(3).
117DISABLE_CJSON_GLOBAL:: Do not store module table in global "cjson"
118 variable. Redundant from Lua 5.2 onwards.
119DISABLE_INVALID_NUMBERS:: Recommended on platforms where ++strtod++(3) /
120 ++sprintf++(3) are not POSIX compliant (Eg, Windows MinGW). Restricts
121 the +cjson.refuse_invalid_numbers+ runtime configuration to +true+.
122
123
124API (Functions)
125---------------
126
127Synopsis
128~~~~~~~~
129
130[source,lua]
131------------
132-- Module initalisation methods
133local cjson = require "cjson"
134local cjson2 = cjson.new()
135
136-- Translate Lua value to/from JSON
137text = cjson.encode(value)
138value = cjson.decode(text)
139
140-- Get and/or set Lua CJSON configuration
141setting = cjson.refuse_invalid_numbers([setting])
142depth = cjson.encode_max_depth([depth])
143convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]])
144keep = cjson.encode_keep_buffer([keep])
145------------
146
147
148Module Instantiation
149~~~~~~~~~~~~~~~~~~~~
150
151[source,lua]
152------------
153local cjson = require "cjson"
154local cjson2 = cjson.new()
155------------
156
157Lua CJSON can be loaded via the Lua +require+ function. A global
158+cjson+ module table is registered under Lua 5.1 to maintain backward
159compatibility. Lua CJSON does not register a global table under Lua
1605.2 since this practice is discouraged.
161
162+cjson.new+ can be used to instantiate an independent copy of the Lua
163CJSON module. The new module has a separate persistent encoding
164buffer, and default settings.
165
166Lua CJSON can support Lua implementations using multiple pre-emptive
167threads within a single Lua state provided the persistent encoding
168buffer is not shared. This can be achieved by one of the following
169methods:
170
171- Disabling the persistent encoding buffer with
172 +cjson.encode_keep_buffer+
173- Ensuring each thread calls +cjson.encode+ at a time
174- Using a separate +cjson+ module table per pre-emptive thread
175 (+cjson.new+)
176
177[NOTE]
178Lua CJSON uses ++strtod++(3) and ++snprintf++(3) to perform numeric
179conversion as they are usually well supported, fast and bug free.
180However, these functions require a workaround for JSON
181encoding/parsing under locales using a comma decimal separator. Lua
182CJSON detects the current locale during instantiation to determine
183whether a workaround is required. CJSON should be reinitialised via
184+cjson.new+ if the locale of the current process changes. Different
185locales per thread are not supported.
186
187
188decode
189~~~~~~
190
191[source,lua]
192------------
193value = cjson.decode(json_text)
194------------
195
196+cjson.decode+ will deserialise any UTF-8 JSON string into a Lua value
197or table. It may return any of the types that +cjson.encode+ supports.
198
199UTF-16 and UTF-32 JSON strings are not supported.
200
201+cjson.decode+ requires that any NULL (ASCII 0) and double quote
202(ASCII 34) characters are escaped within strings. All escape codes
203will be decoded and other characters will be passed transparently.
204UTF-8 characters are not validated during decoding and should be
205checked elsewhere if required.
206
207JSON +null+ will be converted to a NULL +lightuserdata+ value. This
208can be compared with +cjson.null+ for convenience.
209
210By default, numbers incompatible with the JSON specification (NaN,
211Infinity, Hexadecimal) can be decoded. This default can be changed
212with +cjson.refuse_invalid_numbers+.
213
214.Example: Decoding
215[source,lua]
216json_text = '[ true, { "foo": "bar" } ]'
217value = cjson.decode(json_text)
218-- Returns: { true, { foo = "bar" } }
219
220[CAUTION]
221Care must be taken when after decoding JSON objects with numeric keys. Each
222numeric key will be stored as a Lua +string+. Any code assuming type
223+number+ may break.
224
225
226encode
227~~~~~~
228
229[source,lua]
230------------
231json_text = cjson.encode(value)
232------------
233
234+cjson.encode+ will serialise a Lua value into a string containing the
235JSON representation.
236
237+cjson.encode+ supports the following types:
238
239- +boolean+
240- +lightuserdata+ (NULL value only)
241- +nil+
242- +number+
243- +string+
244- +table+
245
246The remaining Lua types will generate an error:
247
248- +function+
249- +lightuserdata+ (non-NULL values)
250- +thread+
251- +userdata+
252
253By default, numbers are encoded using the standard Lua number
254+printf+(3) format (+%.14g+).
255
256Lua CJSON will escape the following characters within each UTF-8
257string:
258
259- Control characters (ASCII 0 - 31)
260- Double quote (ASCII 34)
261- Forward slash (ASCII 47)
262- Blackslash (ASCII 92)
263- Delete (ASCII 127)
264
265All other characters are passed transparently.
266
267[CAUTION]
268=========
269Lua CJSON will successfully encode/decode binary strings, but this is
270technically not supported by JSON and may not be compatible with other
271JSON libraries. Applications should ensure all Lua strings passed to
272+cjson.encode+ are valid UTF-8 to ensure the output is valid JSON.
273
274Base64 is a common way to transport binary data through JSON. Lua
275Base64 routines can be found in the
276http://w3.impa.br/~diego/software/luasocket/[LuaSocket] and
277http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/#lbase64[lbase64] packages.
278=========
279
280Lua CJSON uses a heuristic to determine whether to encode a Lua table
281as a JSON array or an object. A Lua table with only positive integers
282keys of type +number+ will be encoded as a JSON array. All other
283tables will be encoded as a JSON object.
284
285Refer to <<encode_sparse_array,+cjson.encode_sparse_array+>> for details
286on sparse array handling.
287
288Lua CJSON does not use metamethods when serialising tables.
289
290- +rawget+ is used to iterate over Lua arrays
291- +next+ is used to iterate over Lua objects
292
293JSON object keys are always strings. +cjson.encode+ can only handle
294table keys which are type +number+ or +string+. All other types will
295generate an error.
296
297[NOTE]
298Standards compliant JSON must be encapsulated in either an object
299(+{}+) or an array (+[]+). Hence a table must be passed to
300+cjson.encode+ to generate standards compliant JSON output.
301This may not be required by some applications.
302
303By default, the following Lua values will generate errors:
304
305- Numbers incompatible with the JSON specification (NaN, Infinity, Hexadecimal)
306- Tables nested more than 20 levels deep
307- Excessively sparse Lua arrays
308
309These defaults can be changed with:
310
311- <<encode_max_depth,+cjson.encode_max_depth+>>
312- <<encode_sparse_array,+cjson.encode_sparse_array+>>
313- <<refuse_invalid_numbers,+cjson.refuse_invalid_numbers+>>
314
315.Example: Encoding
316[source,lua]
317value = { true, { foo = "bar" } }
318json_text = cjson.encode(value)
319-- Returns: '[true,{"foo":"bar"}]'
320
321
322encode_keep_buffer
323~~~~~~~~~~~~~~~~~~
324
325[source,lua]
326------------
327keep = cjson.encode_keep_buffer([keep])
328-- "keep" must be a boolean
329------------
330
331By default, Lua CJSON will reuse the JSON encoding buffer to improve
332performance. The buffer will grow to the largest size required and is
333not freed until the Lua CJSON module is garbage collected. Setting this
334option to +false+ will cause the buffer to be freed after each call to
335+cjson.encode+.
336
337This setting is only changed when an argument is provided. The current
338setting is always returned.
339
340
341[[encode_max_depth]]
342encode_max_depth
343~~~~~~~~~~~~~~~~
344
345[source,lua]
346------------
347depth = cjson.encode_max_depth([depth])
348-- "depth" must be a positive integer
349------------
350
351By default, Lua CJSON will reject data structure with more than 20 nested
352tables.
353
354This setting is only changed when an argument is provided. The current
355setting is always returned.
356
357This check prevents a deeply nested or recursive data structure from
358crashing the application.
359
360.Example: Recursive Lua tables
361[source,lua]
362a = {}; b = { a }; a[1] = b
363
364Once the maximum table depth has been reached Lua CJSON will throw an error.
365
366
367encode_number_precision
368~~~~~~~~~~~~~~~~~~~~~~~
369
370[source,lua]
371------------
372precision = cjson.encode_number_precision([precision])
373-- "precision" must be between 1 and 14 (inclusive)
374------------
375
376By default, Lua CJSON will output 14 significant digits when
377converting a number to text.
378
379This setting is only changed when an argument is provided. The current
380setting is always returned.
381
382Reducing number precision to _3_ can improve performance of number
383heavy JSON conversions by up to 50%.
384
385
386[[encode_sparse_array]]
387encode_sparse_array
388~~~~~~~~~~~~~~~~~~~
389
390[source,lua]
391------------
392convert, ratio, safe = cjson.encode_sparse_array([convert[, ratio[, safe]]])
393-- "convert" must be a boolean. Default: false.
394-- "ratio" must be a positive integer. Default: 2
395-- "safe" must be a positive integer. Default: 10
396------------
397
398Lua CJSON classifies Lua tables into 3 types when encoding as a JSON
399array. This is determined by the number of values missing for keys up
400to the maximum index:
401
402Each particular setting is only changed when that argument is
403provided. The current settings are always returned.
404
405[horizontal]
406Normal:: All values are available.
407Sparse:: At least 1 value is missing.
408Excessively sparse:: The number of values missing exceed the configured ratio.
409
410Lua CJSON encodes sparse Lua arrays by as JSON arrays using JSON +null+.
411
412.Example: Encoding a sparse array
413[source,lua]
414cjson.encode({ [3] = "data" })
415-- Returns: '[null,null,"data"]'
416
417Lua CJSON checks for excessively sparse arrays when the maximum index
418is greater an the +safe+ limit and +ratio+ is greater than +0+. An
419array is _excessively sparse_ when:
420
421_maximum_index_ > _item_count_ * +ratio+
422
423By default, attempting to encode excessively sparse arrays will
424generate an error. If +convert+ is set to +true+, excessively sparse
425arrays will be converted to a JSON object:
426
427.Example: Enabling conversion to a JSON object
428[source,lua]
429cjson.encode_sparse_array(true)
430cjson.encode({ [1000] = "excessively sparse" })
431-- Returns: '{"1000":"excessively sparse"}'
432
433
434[[refuse_invalid_numbers]]
435refuse_invalid_numbers
436~~~~~~~~~~~~~~~~~~~~~~
437
438[source,lua]
439------------
440setting = cjson.refuse_invalid_numbers([setting])
441-- "setting" must be on of:
442-- false, "encode", "decode", "both", true
443------------
444
445Lua CJSON can throw an error for numbers outside of the JSON
446specification (_invalid numbers_):
447
448- Infinity
449- NaN
450- Hexadecimal
451
452By default Lua CJSON will decode _invalid numbers_, but will refuse to
453encode them.
454
455This setting is only changed when an argument is provided. The current
456setting is always returned.
457
458This setting can be configured separately for encoding and/or
459decoding:
460
461[horizontal]
462Enabled:: An error will be generated if an _invalid number_ is found.
463Disabled for encoding:: NaN and Infinity can be encoded.
464Disabled for decoding:: All numbers supported by +strtod+(3) will be parsed.
465
466
467API (Variables)
468---------------
469
470_NAME
471~~~~~
472
473The name of the Lua CJSON module (+"cjson"+).
474
475
476_VERSION
477~~~~~~~~
478
479The version number of the Lua CJSON module (Eg, +"1.0devel"+).
480
481
482null
483~~~~
484
485Lua CJSON decodes JSON +null+ as a Lua +lightuserdata+ NULL pointer.
486+cjson.null+ is provided for comparison.
487
488
489[sect1]
490References
491----------
492
493- http://tools.ietf.org/html/rfc4627[RFC 4627]
494- http://www.json.org/[JSON website]
495
496
497// vi:ft=asciidoc tw=70:
diff --git a/performance.txt b/performance.txt
new file mode 100644
index 0000000..51c61ec
--- /dev/null
+++ b/performance.txt
@@ -0,0 +1,51 @@
1JSON Performance Comparison under Lua
2-------------------------------------
3
4The 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- CSJON 1.0.2: Pure C.
10
11LuaJSON 1.2.2 appeared to be slower during initial testing, so 1.0 was
12used instead.
13
14The following Lua implementations were used for this comparison:
15- Lua 5.1.4
16- LuaJIT 2.0.0-beta7
17
18The example JSON files used were taken from http://json.org/ and
19RFC 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
23example1 1.0x 2.0x 3.4x 4.0x 7.1x 10.1x 13.2x 19.4x
24example2 1.0x 2.1x 3.5x 4.5x 6.6x 9.8x 12.7x 20.0x
25example3 1.0x 2.0x 3.9x 4.7x 7.0x 9.4x 13.2x 19.3x
26example4 1.0x 1.9x 3.7x 4.4x 7.4x 10.6x 11.8x 18.0x
27example5 1.0x 2.1x 4.0x 4.7x 7.7x 11.4x 14.7x 22.3x
28numbers 1.0x 2.1x 2.1x 3.4x 4.6x 5.7x 8.6x 10.4x
29rfc-example1 1.0x 2.0x 3.2x 4.2x 5.8x 8.2x 11.8x 17.7x
30rfc-example2 1.0x 2.0x 3.6x 4.5x 7.0x 9.3x 14.5x 20.5x
31types 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 ==
35example1 1.0x 1.9x 0.6x 1.4x 3.5x 5.6x 23.1x 29.1x
36example2 1.0x 2.0x 0.5x 1.2x 3.0x 4.9x 23.4x 28.5x
37example3 1.0x 1.8x 0.6x 1.3x 3.0x 4.7x 13.3x 14.9x
38example4 1.0x 1.7x 0.7x 1.5x 4.2x 6.6x 15.4x 18.5x
39example5 1.0x 2.0x 0.6x 1.4x 3.4x 5.5x 22.7x 25.5x
40numbers 1.0x 2.4x 0.4x 0.9x 1.4x 2.1x 4.3x 4.6x
41rfc-example1 1.0x 1.9x 0.5x 1.2x 2.3x 3.6x 8.8x 9.6x
42rfc-example2 1.0x 1.9x 0.6x 1.3x 2.8x 4.3x 10.7x 10.7x
43types 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
47Number conversion is a relatively expensive operation. Number heavy
48JSON will show less performance difference between libraries.
49
50Performance can vary widely between platforms and data sets. These
51results should only be considered as a rough guide.
diff --git a/rfc4627.txt b/rfc4627.txt
new file mode 100644
index 0000000..67b8909
--- /dev/null
+++ b/rfc4627.txt
@@ -0,0 +1,563 @@
1
2
3
4
5
6
7Network Working Group D. Crockford
8Request for Comments: 4627 JSON.org
9Category: Informational July 2006
10
11
12 The application/json Media Type for JavaScript Object Notation (JSON)
13
14Status 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
20Copyright Notice
21
22 Copyright (C) The Internet Society (2006).
23
24Abstract
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
321. 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
58Crockford Informational [Page 1]
59
60RFC 4627 JSON July 2006
61
62
631.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
722. 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
1052.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
114Crockford Informational [Page 2]
115
116RFC 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
1302.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
1432.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
1502.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
170Crockford Informational [Page 3]
171
172RFC 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
1952.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
226Crockford Informational [Page 4]
227
228RFC 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
2513. 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
2674. 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
282Crockford Informational [Page 5]
283
284RFC 4627 JSON July 2006
285
286
2875. Generators
288
289 A JSON generator produces JSON text. The resulting text MUST
290 strictly conform to the JSON grammar.
291
2926. 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
338Crockford Informational [Page 6]
339
340RFC 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
3727. Security Considerations
373
374 See Security Considerations in Section 6.
375
3768. 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
394Crockford Informational [Page 7]
395
396RFC 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
4309. References
431
4329.1. Normative References
433
434 [ECMA] European Computer Manufacturers Association, "ECMAScript
435 Language Specification 3rd Edition", December 1999,
436 <http://www.ecma-international.org/publications/files/
437 ecma-st/ECMA-262.pdf>.
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
450Crockford Informational [Page 8]
451
452RFC 4627 JSON July 2006
453
454
455 [UNICODE] The Unicode Consortium, "The Unicode Standard Version 4.0",
456 2003, <http://www.unicode.org/versions/Unicode4.1.0/>.
457
458Author'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
506Crockford Informational [Page 9]
507
508RFC 4627 JSON July 2006
509
510
511Full 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
527Intellectual 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
551Acknowledgement
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
562Crockford Informational [Page 10]
563
diff --git a/runtests.sh b/runtests.sh
new file mode 100755
index 0000000..d313db3
--- /dev/null
+++ b/runtests.sh
@@ -0,0 +1,91 @@
1#!/bin/sh
2
3PLATFORM="`uname -s`"
4[ "$1" ] && VERSION="$1" || VERSION="1.0devel"
5
6set -e
7
8# Portable "ggrep -A" replacement
9# contextgrep PATTERN POST_MATCH_LINES
10contextgrep() {
11 awk "/$1/ { count = ($2 + 1) } count { count--; print }"
12}
13
14do_tests() {
15 echo
16 cd tests
17 lua -e 'require "cjson"; print("Testing Lua CJSON version " .. cjson.version)'
18 ./test.lua | contextgrep 'FAIL|Summary' 3 | grep -v PASS | cut -c -70
19 cd ..
20}
21
22echo "===== Setting LuaRocks PATH ====="
23eval "`luarocks path`"
24
25echo "===== Building UTF-8 test data ====="
26( cd tests && ./genutf8.pl; )
27
28echo "===== Cleaning old build data ====="
29make clean
30rm -f tests/cjson.so
31
32echo "===== Verifying cjson.so is not installed ====="
33
34cd tests
35if lua -e 'require "cjson"' 2>/dev/null
36then
37 cat <<EOT
38Please ensure you do not have the Lua CJSON module installed before
39running these tests.
40EOT
41 exit
42fi
43cd ..
44
45echo "===== Testing LuaRocks build ====="
46luarocks make --local
47do_tests
48luarocks remove --local lua-cjson
49make clean
50
51echo "===== Testing Makefile build ====="
52make
53cp cjson.so tests
54do_tests
55make clean
56rm -f tests/cjson.so
57
58echo "===== Testing Cmake build ====="
59mkdir build
60cd build
61cmake ..
62make
63cd ..
64cp build/cjson.so tests
65do_tests
66rm -rf build tests/cjson.so
67
68if [ "$PLATFORM" = "Linux" ]
69then
70 echo "===== Testing RPM build ====="
71 SRCTGZ=""
72 TGZ=lua-cjson-$VERSION.tar.gz
73 for D in .. packages .
74 do
75 [ -r "$D/$TGZ" ] && SRCTGZ="$D/$TGZ"
76 done
77 if [ "$SRCTGZ" ]
78 then
79 LOG=/tmp/build.$$
80 rpmbuild -tb "$SRCTGZ" > "$LOG"
81 RPM="`awk '/^Wrote: / && ! /debuginfo/ { print $2}' < "$LOG"`"
82 sudo -- rpm -Uvh \"$RPM\"
83 do_tests
84 sudo -- rpm -e lua-cjson
85 rm -f "$LOG"
86 else
87 echo "==> skipping, $TGZ not found"
88 fi
89fi
90
91# vi:ai et sw=4 ts=4:
diff --git a/strbuf.c b/strbuf.c
new file mode 100644
index 0000000..ab00f0a
--- /dev/null
+++ b/strbuf.c
@@ -0,0 +1,251 @@
1/* strbuf - string buffer routines
2 *
3 * Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au>
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 <stdio.h>
26#include <stdlib.h>
27#include <stdarg.h>
28#include <string.h>
29
30#include "strbuf.h"
31
32static 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
44void 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
68strbuf_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
84void 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
94static 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() */
104void 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
116char *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
134static 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). */
164void 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
182void 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. */
202void 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. */
221void 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 */
diff --git a/strbuf.h b/strbuf.h
new file mode 100644
index 0000000..fbc8651
--- /dev/null
+++ b/strbuf.h
@@ -0,0 +1,154 @@
1/* strbuf - String buffer routines
2 *
3 * Copyright (c) 2010-2011 Mark Pulford <mark@kyne.com.au>
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 <stdlib.h>
26#include <stdarg.h>
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
34typedef 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 */
52extern strbuf_t *strbuf_new(int len);
53extern void strbuf_init(strbuf_t *s, int len);
54extern void strbuf_set_increment(strbuf_t *s, int increment);
55
56/* Release */
57extern void strbuf_free(strbuf_t *s);
58extern char *strbuf_free_to_string(strbuf_t *s, int *len);
59
60/* Management */
61extern void strbuf_resize(strbuf_t *s, int len);
62static int strbuf_empty_length(strbuf_t *s);
63static int strbuf_length(strbuf_t *s);
64static char *strbuf_string(strbuf_t *s, int *len);
65static void strbuf_ensure_empty_length(strbuf_t *s, int len);
66static char *strbuf_empty_ptr(strbuf_t *s);
67static void strbuf_extend_length(strbuf_t *s, int len);
68
69/* Update */
70extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...);
71extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...);
72static void strbuf_append_mem(strbuf_t *s, const char *c, int len);
73extern void strbuf_append_string(strbuf_t *s, const char *str);
74static void strbuf_append_char(strbuf_t *s, const char c);
75static void strbuf_ensure_null(strbuf_t *s);
76
77/* Reset string for before use */
78static inline void strbuf_reset(strbuf_t *s)
79{
80 s->length = 0;
81}
82
83static inline int strbuf_allocated(strbuf_t *s)
84{
85 return s->buf != NULL;
86}
87
88/* Return bytes remaining in the string buffer
89 * Ensure there is space for a NULL terminator. */
90static inline int strbuf_empty_length(strbuf_t *s)
91{
92 return s->size - s->length - 1;
93}
94
95static inline void strbuf_ensure_empty_length(strbuf_t *s, int len)
96{
97 if (len > strbuf_empty_length(s))
98 strbuf_resize(s, s->length + len);
99}
100
101static inline char *strbuf_empty_ptr(strbuf_t *s)
102{
103 return s->buf + s->length;
104}
105
106static inline void strbuf_extend_length(strbuf_t *s, int len)
107{
108 s->length += len;
109}
110
111static inline int strbuf_length(strbuf_t *s)
112{
113 return s->length;
114}
115
116static inline void strbuf_append_char(strbuf_t *s, const char c)
117{
118 strbuf_ensure_empty_length(s, 1);
119 s->buf[s->length++] = c;
120}
121
122static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c)
123{
124 s->buf[s->length++] = c;
125}
126
127static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len)
128{
129 strbuf_ensure_empty_length(s, len);
130 memcpy(s->buf + s->length, c, len);
131 s->length += len;
132}
133
134static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len)
135{
136 memcpy(s->buf + s->length, c, len);
137 s->length += len;
138}
139
140static inline void strbuf_ensure_null(strbuf_t *s)
141{
142 s->buf[s->length] = 0;
143}
144
145static inline char *strbuf_string(strbuf_t *s, int *len)
146{
147 if (len)
148 *len = s->length;
149
150 return s->buf;
151}
152
153/* vi:ai et sw=4 ts=4:
154 */
diff --git a/tests/README b/tests/README
new file mode 100644
index 0000000..39e8bd4
--- /dev/null
+++ b/tests/README
@@ -0,0 +1,4 @@
1These JSON examples were taken from the JSON website
2(http://json.org/example.html) and RFC 4627.
3
4Used with permission.
diff --git a/tests/bench.lua b/tests/bench.lua
new file mode 100755
index 0000000..2b5177b
--- /dev/null
+++ b/tests/bench.lua
@@ -0,0 +1,83 @@
1#!/usr/bin/env lua
2
3-- Simple JSON benchmark.
4--
5-- Your Mileage May Vary.
6--
7-- Mark Pulford <mark@kyne.com.au>
8
9require "socket"
10local json = require "cjson"
11local misc = require "cjson-misc"
12
13function benchmark(tests, seconds, rep)
14 local function bench(func, iter)
15 -- collectgarbage("stop")
16 collectgarbage("collect")
17 local t = socket.gettime()
18 for i = 1, iter do
19 func(i)
20 end
21 t = socket.gettime() - t
22 -- collectgarbage("restart")
23 return (iter / t)
24 end
25
26 -- Roughly calculate the number of interations required
27 -- to obtain a particular time period.
28 local function calc_iter(func, seconds)
29 local base_iter = 10
30 local rate = (bench(func, base_iter) + bench(func, base_iter)) / 2
31 return math.ceil(seconds * rate)
32 end
33
34 local test_results = {}
35 for name, func in pairs(tests) do
36 -- k(number), v(string)
37 -- k(string), v(function)
38 -- k(number), v(function)
39 if type(func) == "string" then
40 name = func
41 func = _G[name]
42 end
43 local iter = calc_iter(func, seconds)
44 local result = {}
45 for i = 1, rep do
46 result[i] = bench(func, iter)
47 end
48 table.sort(result)
49 test_results[name] = result[rep]
50 end
51
52 return test_results
53end
54
55function bench_file(filename)
56 local data_json = misc.file_load(filename)
57 local data_obj = json.decode(data_json)
58
59 local function test_encode ()
60 json.encode(data_obj)
61 end
62 local function test_decode ()
63 json.decode(data_json)
64 end
65
66 local tests = {
67 encode = test_encode,
68 decode = test_decode
69 }
70
71 return benchmark(tests, 0.1, 5)
72end
73
74json.encode_keep_buffer(true)
75
76for i = 1, #arg do
77 local results = bench_file(arg[i])
78 for k, v in pairs(results) do
79 print(string.format("%s: %s: %d", arg[i], k, v))
80 end
81end
82
83-- vi:ai et sw=4 ts=4:
diff --git a/tests/cjson-misc.lua b/tests/cjson-misc.lua
new file mode 100644
index 0000000..d8bfe5e
--- /dev/null
+++ b/tests/cjson-misc.lua
@@ -0,0 +1,253 @@
1local json = require "cjson"
2
3-- Misc routines to assist with CJSON testing
4--
5-- Mark Pulford <mark@kyne.com.au>
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
13local 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
29end
30
31local serialise_value
32
33local function serialise_table(value, indent, depth)
34 local spacing, spacing2, indent2
35 if indent then
36 spacing = "\n" .. indent
37 spacing2 = spacing .. " "
38 indent2 = indent .. " "
39 else
40 spacing, spacing2, indent2 = " ", " ", false
41 end
42 depth = depth + 1
43 if depth > 50 then
44 return "Cannot serialise any further: too many nested tables"
45 end
46
47 local max = is_array(value)
48
49 local comma = false
50 local fragment = { "{" .. spacing2 }
51 if max > 0 then
52 -- Serialise array
53 for i = 1, max do
54 if comma then
55 table.insert(fragment, "," .. spacing2)
56 end
57 table.insert(fragment, serialise_value(value[i], indent2, depth))
58 comma = true
59 end
60 elseif max < 0 then
61 -- Serialise table
62 for k, v in pairs(value) do
63 if comma then
64 table.insert(fragment, "," .. spacing2)
65 end
66 table.insert(fragment, string.format(
67 "[%s] = %s", serialise_value(k, indent2, depth),
68 serialise_value(v, indent2, depth))
69 )
70 comma = true
71 end
72 end
73 table.insert(fragment, spacing .. "}")
74
75 return table.concat(fragment)
76end
77
78function serialise_value(value, indent, depth)
79 if indent == nil then indent = "" end
80 if depth == nil then depth = 0 end
81
82 if value == json.null then
83 return "json.null"
84 elseif type(value) == "string" then
85 return string.format("%q", value)
86 elseif type(value) == "nil" or type(value) == "number" or
87 type(value) == "boolean" then
88 return tostring(value)
89 elseif type(value) == "table" then
90 return serialise_table(value, indent, depth)
91 else
92 return "\"<" .. type(value) .. ">\""
93 end
94end
95
96local function file_load(filename)
97 local file
98 if filename == nil then
99 file = io.stdin
100 else
101 local err
102 file, err = io.open(filename)
103 if file == nil then
104 error(string.format("Unable to read '%s': %s", filename, err))
105 end
106 end
107 local data = file:read("*a")
108
109 if filename ~= nil then
110 file:close()
111 end
112
113 if data == nil then
114 error("Failed to read " .. filename)
115 end
116
117 return data
118end
119
120local function file_save(filename, data)
121 local file
122 if filename == nil then
123 file = io.stdout
124 else
125 local err
126 file, err = io.open(filename, "w")
127 if file == nil then
128 error(string.format("Unable to write '%s': %s", filename, err))
129 end
130 end
131 file:write(data)
132 if filename ~= nil then
133 file:close()
134 end
135end
136
137local function compare_values(val1, val2)
138 local type1 = type(val1)
139 local type2 = type(val2)
140 if type1 ~= type2 then
141 return false
142 end
143
144 -- Check for NaN
145 if type1 == "number" and val1 ~= val1 and val2 ~= val2 then
146 return true
147 end
148
149 if type1 ~= "table" then
150 return val1 == val2
151 end
152
153 -- check_keys stores all the keys that must be checked in val2
154 local check_keys = {}
155 for k, _ in pairs(val1) do
156 check_keys[k] = true
157 end
158
159 for k, v in pairs(val2) do
160 if not check_keys[k] then
161 return false
162 end
163
164 if not compare_values(val1[k], val2[k]) then
165 return false
166 end
167
168 check_keys[k] = nil
169 end
170 for k, _ in pairs(check_keys) do
171 -- Not the same if any keys from val1 were not found in val2
172 return false
173 end
174 return true
175end
176
177local test_count_pass = 0
178local test_count_total = 0
179
180local function run_test_summary()
181 return test_count_pass, test_count_total
182end
183
184local function run_test(testname, func, input, should_work, output)
185 local function status_line(name, status, value)
186 local statusmap = { [true] = ":success", [false] = ":error" }
187 if status ~= nil then
188 name = name .. statusmap[status]
189 end
190 print(string.format("[%s] %s", name, serialise_value(value, false)))
191 end
192
193 local result = { pcall(func, unpack(input)) }
194 local success = table.remove(result, 1)
195
196 local correct = false
197 if success == should_work and compare_values(result, output) then
198 correct = true
199 test_count_pass = test_count_pass + 1
200 end
201 test_count_total = test_count_total + 1
202
203 local teststatus = { [true] = "PASS", [false] = "FAIL" }
204 print(string.format("==> Test[%d] / %s: %s",
205 test_count_total, testname, teststatus[correct]))
206
207 status_line("Input", nil, input)
208 if not correct then
209 status_line("Expected", should_work, output)
210 end
211 status_line("Received", success, result)
212 print()
213
214 return correct, result
215end
216
217local function run_test_group(testgroup, tests)
218 local function run_config(configname, func)
219 local success, msg = pcall(func)
220 if msg then
221 print(string.format("==> Config %s: %s", configname, msg))
222 end
223 print()
224 end
225
226 local function test_id(group, id)
227 return string.format("%s[%d]", group, id)
228 end
229
230 for k, v in ipairs(tests) do
231 if type(v) == "function" then
232 -- Useful for changing configuration during a batch
233 run_config(test_id(testgroup, k), v)
234 elseif type(v) == "table" then
235 run_test(test_id(testgroup, k), unpack(v))
236 else
237 error("Testgroup can only contain functions and tables")
238 end
239 end
240end
241
242-- Export functions
243return {
244 serialise_value = serialise_value,
245 file_load = file_load,
246 file_save = file_save,
247 compare_values = compare_values,
248 run_test_summary = run_test_summary,
249 run_test = run_test,
250 run_test_group = run_test_group
251}
252
253-- vi:ai et sw=4 ts=4:
diff --git a/tests/example1.json b/tests/example1.json
new file mode 100644
index 0000000..42486ce
--- /dev/null
+++ b/tests/example1.json
@@ -0,0 +1,22 @@
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}
diff --git a/tests/example2.json b/tests/example2.json
new file mode 100644
index 0000000..5600991
--- /dev/null
+++ b/tests/example2.json
@@ -0,0 +1,11 @@
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}}
diff --git a/tests/example3.json b/tests/example3.json
new file mode 100644
index 0000000..d7237a5
--- /dev/null
+++ b/tests/example3.json
@@ -0,0 +1,26 @@
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}}
diff --git a/tests/example4.json b/tests/example4.json
new file mode 100644
index 0000000..d31a395
--- /dev/null
+++ b/tests/example4.json
@@ -0,0 +1,88 @@
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"}}}
diff --git a/tests/example5.json b/tests/example5.json
new file mode 100644
index 0000000..49980ca
--- /dev/null
+++ b/tests/example5.json
@@ -0,0 +1,27 @@
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}}
diff --git a/tests/genutf8.pl b/tests/genutf8.pl
new file mode 100755
index 0000000..db661a1
--- /dev/null
+++ b/tests/genutf8.pl
@@ -0,0 +1,23 @@
1#!/usr/bin/env perl
2
3# Create test comparison data using a different UTF-8 implementation.
4
5# The generated utf8.dat file must have the following MD5 sum:
6# cff03b039d850f370a7362f3313e5268
7
8use strict;
9
10# 0xD800 - 0xDFFF are used to encode supplementary codepoints
11# 0x10000 - 0x10FFFF are supplementary codepoints
12my (@codepoints) = (0 .. 0xD7FF, 0xE000 .. 0x10FFFF);
13
14my $utf8 = pack("U*", @codepoints);
15defined($utf8) or die "Unable create UTF-8 string\n";
16
17open(FH, ">:utf8", "utf8.dat")
18 or die "Unable to open utf8.dat: $!\n";
19print FH $utf8
20 or die "Unable to write utf8.dat\n";
21close(FH);
22
23# vi:ai et sw=4 ts=4:
diff --git a/tests/json2lua.lua b/tests/json2lua.lua
new file mode 100755
index 0000000..050b89d
--- /dev/null
+++ b/tests/json2lua.lua
@@ -0,0 +1,14 @@
1#!/usr/bin/env lua
2
3-- usage: json2lua.lua [json_file]
4--
5-- Eg:
6-- echo '[ "testing" ]' | ./json2lua.lua
7-- ./json2lua.lua test.json
8
9local json = require "cjson"
10local misc = require "cjson-misc"
11
12local json_text = misc.file_load(arg[1])
13local t = json.decode(json_text)
14print(misc.serialise_value(t))
diff --git a/tests/lua2json.lua b/tests/lua2json.lua
new file mode 100755
index 0000000..ebe7380
--- /dev/null
+++ b/tests/lua2json.lua
@@ -0,0 +1,42 @@
1#!/usr/bin/env lua
2
3-- usage: lua2json.lua [lua_file]
4--
5-- Eg:
6-- echo '{ "testing" }' | ./lua2json.lua
7-- ./lua2json.lua test.lua
8
9local json = require "cjson"
10local misc = require "cjson-misc"
11
12function get_lua_table(s)
13 local env = {}
14 local func
15
16 env.json = {}
17 env.json.null = json.null
18 env.null = json.null
19 s = "data = " .. s
20
21 -- Use setfenv() if it exists, otherwise assume Lua 5.2 load() exists
22 if _G.setfenv then
23 func = loadstring(s)
24 if func then
25 setfenv(func, env)
26 end
27 else
28 func = load(s, nil, nil, env)
29 end
30
31 if func == nil then
32 error("Invalid syntax. Failed to parse Lua table.")
33 end
34 func()
35
36 return env.data
37end
38
39local t = get_lua_table(misc.file_load(arg[1]))
40print(json.encode(t))
41
42-- vi:ai et sw=4 ts=4:
diff --git a/tests/numbers.json b/tests/numbers.json
new file mode 100644
index 0000000..ef11a26
--- /dev/null
+++ b/tests/numbers.json
@@ -0,0 +1,7 @@
1[ 0.110001000000000000000001,
2 0.12345678910111213141516,
3 0.412454033640,
4 2.6651441426902251886502972498731,
5 2.718281828459045235360287471352,
6 3.141592653589793238462643383279,
7 2.14069263277926 ]
diff --git a/tests/octets-escaped.dat b/tests/octets-escaped.dat
new file mode 100644
index 0000000..ee99a6b
--- /dev/null
+++ b/tests/octets-escaped.dat
@@ -0,0 +1 @@
"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-.\/0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ" \ No newline at end of file
diff --git a/tests/rfc-example1.json b/tests/rfc-example1.json
new file mode 100644
index 0000000..73532fa
--- /dev/null
+++ b/tests/rfc-example1.json
@@ -0,0 +1,13 @@
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}
diff --git a/tests/rfc-example2.json b/tests/rfc-example2.json
new file mode 100644
index 0000000..2a0cb68
--- /dev/null
+++ b/tests/rfc-example2.json
@@ -0,0 +1,22 @@
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]
diff --git a/tests/test.lua b/tests/test.lua
new file mode 100755
index 0000000..c860878
--- /dev/null
+++ b/tests/test.lua
@@ -0,0 +1,252 @@
1#!/usr/bin/env lua
2
3-- CJSON tests
4--
5-- Mark Pulford <mark@kyne.com.au>
6--
7-- Note: The output of this script is easier to read with "less -S"
8
9local json = require "cjson"
10local misc = require "cjson-misc"
11
12local function gen_ascii()
13 local chars = {}
14 for i = 0, 255 do chars[i + 1] = string.char(i) end
15 return table.concat(chars)
16end
17
18-- Generate every UTF-16 codepoint, including supplementary codes
19local 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)
48end
49
50function test_decode_cycle(filename)
51 local obj1 = json.decode(file_load(filename))
52 local obj2 = json.decode(json.encode(obj1))
53 return misc.compare_values(obj1, obj2)
54end
55
56local Inf = math.huge;
57local NaN = math.huge * 0;
58local octets_raw = gen_ascii()
59local octets_escaped = misc.file_load("octets-escaped.dat")
60local utf8_loaded, utf8_raw = pcall(misc.file_load, "utf8.dat")
61if not utf8_loaded then
62 utf8_raw = "Failed to load utf8.dat"
63end
64local utf16_escaped = gen_utf16_escaped()
65local nested5 = {{{{{ "nested" }}}}}
66local table_cycle = {}
67local table_cycle2 = { table_cycle }
68table_cycle[1] = table_cycle2
69
70local 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
82local 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
95local decode_numeric_tests = {
96 { json.decode, { '[ 0.0, -1, 0.3e-3, 1023.2 ]' },
97 true, { { 0.0, -1, 0.0003, 1023.2 } } },
98 { json.decode, { '00123' }, true, { 123 } },
99 { json.decode, { '05.2' }, true, { 5.2 } },
100 { json.decode, { '0e10' }, true, { 0 } },
101 { json.decode, { '0x6' }, true, { 6 } },
102 { json.decode, { '[ +Inf, Inf, -Inf ]' }, true, { { Inf, Inf, -Inf } } },
103 { json.decode, { '[ +Infinity, Infinity, -Infinity ]' },
104 true, { { Inf, Inf, -Inf } } },
105 { json.decode, { '[ +NaN, NaN, -NaN ]' }, true, { { NaN, NaN, NaN } } },
106 { json.decode, { 'Infrared' },
107 false, { "Expected the end but found invalid token at character 4" } },
108 { json.decode, { 'Noodle' },
109 false, { "Expected value but found invalid token at character 1" } },
110}
111
112local encode_table_tests = {
113 function()
114 json.encode_sparse_array(true, 2, 3)
115 json.encode_max_depth(5)
116 return "Setting sparse array (true, 2, 3) / max depth (5)"
117 end,
118 { json.encode, { { [3] = "sparse test" } },
119 true, { '[null,null,"sparse test"]' } },
120 { json.encode, { { [1] = "one", [4] = "sparse test" } },
121 true, { '["one",null,null,"sparse test"]' } },
122 { json.encode, { { [1] = "one", [5] = "sparse test" } },
123 true, { '{"1":"one","5":"sparse test"}' } },
124
125 { json.encode, { { ["2"] = "numeric string key test" } },
126 true, { '{"2":"numeric string key test"}' } },
127
128 { json.encode, { nested5 }, true, { '[[[[["nested"]]]]]' } },
129 { json.encode, { { nested5 } },
130 false, { "Cannot serialise, excessive nesting (6)" } },
131 { json.encode, { table_cycle },
132 false, { "Cannot serialise, excessive nesting (6)" } }
133}
134
135local encode_error_tests = {
136 { json.encode, { { [false] = "wrong" } },
137 false, { "Cannot serialise boolean: table key must be a number or string" } },
138 { json.encode, { function () end },
139 false, { "Cannot serialise function: type not supported" } },
140 function ()
141 json.refuse_invalid_numbers("encode")
142 return 'Setting refuse_invalid_numbers("encode")'
143 end,
144 { json.encode, { NaN },
145 false, { "Cannot serialise number: must not be NaN or Inf" } },
146 { json.encode, { Inf },
147 false, { "Cannot serialise number: must not be NaN or Inf" } },
148 function ()
149 json.refuse_invalid_numbers(false)
150 return 'Setting refuse_invalid_numbers(false).'
151 end,
152 { json.encode, { NaN }, true, { "nan" } },
153 { json.encode, { Inf }, true, { "inf" } },
154 function ()
155 json.refuse_invalid_numbers("encode")
156 return 'Setting refuse_invalid_numbers("encode")'
157 end,
158}
159
160local json_nested = string.rep("[", 100000) .. string.rep("]", 100000)
161
162local decode_error_tests = {
163 { json.decode, { '\0"\0"' },
164 false, { "JSON parser does not support UTF-16 or UTF-32" } },
165 { json.decode, { '"\0"\0' },
166 false, { "JSON parser does not support UTF-16 or UTF-32" } },
167 { json.decode, { '{ "unexpected eof": ' },
168 false, { "Expected value but found T_END at character 21" } },
169 { json.decode, { '{ "extra data": true }, false' },
170 false, { "Expected the end but found T_COMMA at character 23" } },
171 { json.decode, { ' { "bad escape \\q code" } ' },
172 false, { "Expected object key string but found invalid escape code at character 16" } },
173 { json.decode, { ' { "bad unicode \\u0f6 escape" } ' },
174 false, { "Expected object key string but found invalid unicode escape code at character 17" } },
175 { json.decode, { ' [ "bad barewood", test ] ' },
176 false, { "Expected value but found invalid token at character 20" } },
177 { json.decode, { '[ -+12 ]' },
178 false, { "Expected value but found invalid number at character 3" } },
179 { json.decode, { '-v' },
180 false, { "Expected value but found invalid number at character 1" } },
181 { json.decode, { '[ 0.4eg10 ]' },
182 false, { "Expected comma or array end but found invalid token at character 6" } },
183 { json.decode, { json_nested },
184 false, { "Too many nested data structures" } }
185}
186
187local escape_tests = {
188 -- Test 8bit clean
189 { json.encode, { octets_raw }, true, { octets_escaped } },
190 { json.decode, { octets_escaped }, true, { octets_raw } },
191 -- Ensure high bits are removed from surrogate codes
192 { json.decode, { '"\\uF800"' }, true, { "\239\160\128" } },
193 -- Test inverted surrogate pairs
194 { json.decode, { '"\\uDB00\\uD800"' },
195 false, { "Expected value but found invalid unicode escape code at character 2" } },
196 -- Test 2x high surrogate code units
197 { json.decode, { '"\\uDB00\\uDB00"' },
198 false, { "Expected value but found invalid unicode escape code at character 2" } },
199 -- Test invalid 2nd escape
200 { json.decode, { '"\\uDB00\\"' },
201 false, { "Expected value but found invalid unicode escape code at character 2" } },
202 { json.decode, { '"\\uDB00\\uD"' },
203 false, { "Expected value but found invalid unicode escape code at character 2" } },
204 -- Test decoding of all UTF-16 escapes
205 { json.decode, { utf16_escaped }, true, { utf8_raw } }
206}
207
208-- The standard Lua interpreter is ANSI C online doesn't support locales
209-- by default. Force a known problematic locale to test strtod()/sprintf().
210local locale_tests = {
211 function ()
212 os.setlocale("cs_CZ")
213 json.new()
214 return "Setting locale to cs_CZ (comma separator)"
215 end,
216 { json.encode, { 1.5 }, true, { '1.5' } },
217 { json.decode, { "[ 10, \"test\" ]" }, true, { { 10, "test" } } },
218 function ()
219 os.setlocale("C")
220 json.new()
221 return "Reverting locale to POSIX"
222 end
223}
224
225print(string.format("Testing Lua CJSON version %s\n", json.version))
226
227misc.run_test_group("decode simple value", decode_simple_tests)
228misc.run_test_group("encode simple value", encode_simple_tests)
229misc.run_test_group("decode numeric", decode_numeric_tests)
230misc.run_test_group("encode table", encode_table_tests)
231misc.run_test_group("decode error", decode_error_tests)
232misc.run_test_group("encode error", encode_error_tests)
233misc.run_test_group("escape", escape_tests)
234misc.run_test_group("locale", locale_tests)
235
236json.refuse_invalid_numbers(false)
237json.encode_max_depth(20)
238for i = 1, #arg do
239 misc.run_test("decode cycle " .. arg[i], test_decode_cycle, { arg[i] },
240 true, { true })
241end
242
243local pass, total = misc.run_test_summary()
244
245if pass == total then
246 print("==> Summary: all tests succeeded")
247else
248 print(string.format("==> Summary: %d/%d tests failed", total - pass, total))
249 os.exit(1)
250end
251
252-- vi:ai et sw=4 ts=4:
diff --git a/tests/types.json b/tests/types.json
new file mode 100644
index 0000000..c01e7d2
--- /dev/null
+++ b/tests/types.json
@@ -0,0 +1 @@
{ "array": [ 10, true, null ] }