├── .gitignore ├── CHANGES ├── LICENSE ├── README.md ├── lib └── resty │ ├── libcjson.lua │ └── libcjson │ ├── libcjsondec.lua │ ├── libcjsonenc.lua │ ├── luacjsondec.lua │ └── luacjsonenc.lua └── lua-resty-libcjson-dev-1.rockspec /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | **/*.o 4 | **/*.so -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Changes with lua-resty-libcjson 1.3 25 Apr 2016 2 | 3 | *) Just a small adjustments. 4 | 5 | Changes with lua-resty-libcjson 1.2 25 Apr 2016 6 | 7 | *) Bugfix: Fixed a memory leak on cjson.cJSON_Print char *. 8 | 9 | Changes with lua-resty-libcjson 1.1 28 Sep 2015 10 | 11 | *) Bugfix: There was a minor problem in releasing the memory on a 12 | non-allocated buffers. 13 | 14 | Changes with lua-resty-libcjson 1.0 30 Sep 2014 15 | 16 | *) Feature: LuaRocks Support via MoonRocks. 17 | 18 | *) Change: Renamed module to libcjson to fix name conflict with 19 | OpenResty's bundled cjson. 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 - 2016 Aapo Talvensaari 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-resty-libcjson 2 | 3 | `lua-resty-libcjson` is a LuaJIT FFI-based cJSON library (tested with OpenResty too). 4 | 5 | ## Installation 6 | 7 | Just place [`libcjson.lua`](https://github.com/bungle/lua-resty-libcjson/blob/master/lib/resty/libcjson.lua) somewhere in your `package.path`, preferably under `resty` directory. If you are using OpenResty, the default location would be `/usr/local/openresty/lualib/resty`. 8 | 9 | ### Compiling and Installing cJSON C-library 10 | 11 | These are just rudimentary notes. Better installation instructions will follow: 12 | 13 | 1. First clone this repo: https://github.com/DaveGamble/cJSON 14 | 2. Unzip / Extract the archive 15 | 3. Run `gcc cJSON.c -O3 -o libcjson.so -shared -fPIC` (on Linux) or `gcc cJSON.c -O3 -o libcjson.dylib -shared` (OS X) 16 | 4. Place `libcjson.so` somewhere in the default search path for dynamic libraries of your operating system (or modify `libcjson.lua` and point `ffi_load "cjson"` with full path to `libcjson.so`, e.g. `local json = ffi_load("/usr/local/lib/lua/5.1/libcjson.so")`). 17 | 18 | ### Using LuaRocks or MoonRocks 19 | 20 | If you are using LuaRocks >= 2.2: 21 | 22 | ```Shell 23 | $ luarocks install lua-resty-libcjson 24 | ``` 25 | 26 | If you are using LuaRocks < 2.2: 27 | 28 | ```Shell 29 | $ luarocks install --server=http://rocks.moonscript.org moonrocks 30 | $ moonrocks install lua-resty-libcjson 31 | ``` 32 | 33 | MoonRocks repository for `lua-resty-libcjson` is located here: https://rocks.moonscript.org/modules/bungle/lua-resty-libcjson. 34 | 35 | ## Lua API 36 | #### mixed json.decode(value) 37 | 38 | Decodes JSON value or structure (JSON array or object), and returns either Lua `table` or some simple value (e.g. `boolean`, `string`, `number`, `nil` or `json.null` (when running in context of OpenResty the `json.null` is the same as `ngx.null`). 39 | 40 | ##### Example 41 | 42 | ```lua 43 | local json = require "resty.libcjson" 44 | local obj = json.decode "{}" -- table (with obj.__jsontype == "object") 45 | local arr = json.decode "[]" -- table (with arr.__jsontype == "array") 46 | local nbr = json.decode "1" -- 1 47 | local bln = json.decode "true" -- true 48 | local str = json.decode '"test"' -- "test" 49 | local str = json.decode '""' -- "" 50 | local num = json.decode(5) -- 5 51 | local num = json.decode(math) -- math 52 | local num = json.decode(json.null) -- json.null 53 | local nul = json.decode "null" -- json.null 54 | local nul = json.decode "" -- nil 55 | local nul = json.decode(nil) -- nil 56 | local nul = json.decode() -- nil 57 | ``` 58 | 59 | Nested JSON structures are parsed as nested Lua tables. 60 | 61 | #### string json.encode(value, formatted) 62 | 63 | Encodes Lua value or table, and returns equivalent JSON value or structure as a string. Optionally you may pass `formatted` argument with value of `false` to get unformatted JSON string as output. 64 | 65 | ##### Example 66 | 67 | ```lua 68 | local json = require "resty.libcjson" 69 | local str = json.encode{} -- "[]" 70 | local str = json.encode(setmetatable({}, json.object)) -- "{}" 71 | local str = json.encode(1) -- "1" 72 | local str = json.encode(1.1) -- "1.100000" 73 | local str = json.encode "test" -- '"test"' 74 | local str = json.encode "" -- '""' 75 | local str = json.encode(false) -- "false" 76 | local str = json.encode(nil) -- "null" 77 | local str = json.encode(json.null) -- "null" 78 | local str = json.encode() -- "null" 79 | local str = json.encode{ a = "b" } -- '{ "a": "b" }' 80 | local str = json.encode{ "a", b = 1 } -- '{ "1": "a", "b": 1 }' 81 | local str = json.encode{ 1, 1.1, "a", "", false } -- '[1, 1.100000, "a", "", false]' 82 | ``` 83 | 84 | Nested Lua tables are encoded as nested JSON structures (JSON objects or arrays). 85 | 86 | #### About JSON Arrays and Object Encoding and Decoding 87 | 88 | See this comment: https://github.com/bungle/lua-resty-libcjson/issues/1#issuecomment-38567447. 89 | 90 | ## Benchmarks 91 | 92 | About 190 MB citylots.json: 93 | 94 | ```bash 95 | # Lua cJSON 96 | Decoding Time: 5.882825 97 | Encoding Time: 4.902301 98 | # lua-resty-libcjson 99 | Decoding Time: 6.409872 100 | Encoding Time: (takes forever) 101 | ``` 102 | 103 | ## License 104 | 105 | `lua-resty-libcjson` uses two clause BSD license. 106 | 107 | ``` 108 | Copyright (c) 2014 - 2016, Aapo Talvensaari 109 | All rights reserved. 110 | 111 | Redistribution and use in source and binary forms, with or without modification, 112 | are permitted provided that the following conditions are met: 113 | 114 | * Redistributions of source code must retain the above copyright notice, this 115 | list of conditions and the following disclaimer. 116 | 117 | * Redistributions in binary form must reproduce the above copyright notice, this 118 | list of conditions and the following disclaimer in the documentation and/or 119 | other materials provided with the distribution. 120 | 121 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 122 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 123 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 124 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 125 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 126 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 127 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 128 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 129 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 130 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 131 | ``` 132 | -------------------------------------------------------------------------------- /lib/resty/libcjson.lua: -------------------------------------------------------------------------------- 1 | local require = require 2 | local ffi = require "ffi" 3 | local ffi_new = ffi.new 4 | local ffi_typeof = ffi.typeof 5 | local ffi_cdef = ffi.cdef 6 | local ffi_load = ffi.load 7 | local ffi_str = ffi.string 8 | local ffi_gc = ffi.gc 9 | local C = ffi.C 10 | local next = next 11 | local floor = math.floor 12 | local inf = math.huge 13 | local max = math.max 14 | local pcall = pcall 15 | local type = type 16 | local next = next 17 | local error = error 18 | local pairs = pairs 19 | local ipairs = ipairs 20 | local tostring = tostring 21 | local getmetatable = getmetatable 22 | local setmetatable = setmetatable 23 | local null = {} 24 | if ngx and ngx.null then null = ngx.null end 25 | ffi_cdef[[ 26 | typedef struct cJSON { 27 | struct cJSON *next, *prev; 28 | struct cJSON *child; 29 | int type; 30 | char *valuestring; 31 | int valueint; 32 | double valuedouble; 33 | char *string; 34 | } cJSON; 35 | cJSON *cJSON_Parse(const char *value); 36 | char *cJSON_Print(cJSON *item); 37 | char *cJSON_PrintUnformatted(cJSON *item); 38 | void cJSON_Delete(cJSON *c); 39 | int cJSON_GetArraySize(cJSON *array); 40 | cJSON *cJSON_CreateNull(void); 41 | cJSON *cJSON_CreateTrue(void); 42 | cJSON *cJSON_CreateFalse(void); 43 | cJSON *cJSON_CreateBool(int b); 44 | cJSON *cJSON_CreateNumber(double num); 45 | cJSON *cJSON_CreateString(const char *string); 46 | cJSON *cJSON_CreateArray(void); 47 | cJSON *cJSON_CreateObject(void); 48 | void cJSON_AddItemToArray(cJSON *array, cJSON *item); 49 | void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item); 50 | void cJSON_Minify(char *json); 51 | void free(void *ptr); 52 | ]] 53 | local ok, newtab = pcall(require, "table.new") 54 | if not ok then newtab = function() return {} end end 55 | local cjson = ffi_load "cjson" 56 | local json = newtab(0, 6) 57 | local char_t = ffi_typeof("char[?]") 58 | local mt_arr = { __index = { __jsontype = "array" }} 59 | local mt_obj = { __index = { __jsontype = "object" }} 60 | local function is_array(t) 61 | local m, c = 0, 0 62 | for k, _ in pairs(t) do 63 | if type(k) ~= "number" or k < 0 or floor(k) ~= k then return false end 64 | m = max(m, k) 65 | c = c + 1 66 | end 67 | return c == m 68 | end 69 | function json.decval(j) 70 | local t = j.type 71 | if t == 1 then return false end 72 | if t == 2 then return true end 73 | if t == 4 then return null end 74 | if t == 8 then return j.valuedouble end 75 | if t == 16 then return ffi_str(j.valuestring) end 76 | if t == 32 then return setmetatable(json.parse(j.child, newtab(cjson.cJSON_GetArraySize(j), 0)) or {}, mt_arr) end 77 | if t == 64 then return setmetatable(json.parse(j.child, newtab(0, cjson.cJSON_GetArraySize(j))) or {}, mt_obj) end 78 | return nil 79 | end 80 | function json.parse(j, r) 81 | if j == nil then return nil end 82 | local c = j 83 | repeat 84 | r[c.string ~= nil and ffi_str(c.string) or #r + 1] = json.decval(c) 85 | c = c.next 86 | until c == nil 87 | return r 88 | end 89 | function json.decode(value) 90 | if type(value) ~= "string" then return value end 91 | local j = ffi_gc(cjson.cJSON_Parse(value), cjson.cJSON_Delete) 92 | if j == nil then return nil end 93 | local t = j.type 94 | if t == 5 then return setmetatable(json.parse(j.child, newtab(cjson.cJSON_GetArraySize(j), 0)) or {}, mt_arr) end 95 | if t == 6 then return setmetatable(json.parse(j.child, newtab(0, cjson.cJSON_GetArraySize(j))) or {}, mt_obj) end 96 | return json.decval(j) 97 | end 98 | function json.encval(value) 99 | local t = type(value) 100 | if t == "string" then 101 | return cjson.cJSON_CreateString(value) 102 | elseif t == "number" then 103 | if value ~= value then 104 | return error "nan is not allowed in JSON" 105 | elseif value == inf or value == -inf then 106 | return error "inf is not allowed in JSON" 107 | else 108 | return cjson.cJSON_CreateNumber(value) 109 | end 110 | elseif t == "boolean" then 111 | return value and cjson.cJSON_CreateTrue() or cjson.cJSON_CreateFalse() 112 | elseif t == "table" then 113 | if next(value) == nil then return (getmetatable(value) ~= mt_obj and is_array(value)) and cjson.cJSON_CreateArray() or cjson.cJSON_CreateObject() end 114 | if getmetatable(value) ~= mt_obj and is_array(value) then 115 | local j = cjson.cJSON_CreateArray() 116 | for _, v in ipairs(value) do 117 | cjson.cJSON_AddItemToArray(j, json.encval(v)) 118 | end 119 | return j 120 | end 121 | local j = cjson.cJSON_CreateObject() 122 | for k, v in pairs(value) do 123 | cjson.cJSON_AddItemToObject(j, type(k) ~= "string" and tostring(k) or k, json.encval(v)) 124 | end 125 | return j 126 | else 127 | return cjson.cJSON_CreateNull() 128 | end 129 | end 130 | function json.encode(value, formatted) 131 | local j = ffi_gc(json.encval(value), cjson.cJSON_Delete) 132 | if j == nil then return nil end 133 | return formatted ~= false and ffi_str(ffi_gc(cjson.cJSON_Print(j), C.free)) or ffi_str(ffi_gc(cjson.cJSON_PrintUnformatted(j), C.free)) 134 | end 135 | function json.minify(value) 136 | local t = type(value) ~= "string" and json.encode(value) or value 137 | local m = ffi_new(char_t, #t, t) 138 | cjson.cJSON_Minify(m) 139 | return ffi_str(m) 140 | end 141 | return { 142 | decode = json.decode, 143 | encode = json.encode, 144 | minify = json.minify, 145 | array = mt_arr, 146 | object = mt_obj, 147 | null = null 148 | } -------------------------------------------------------------------------------- /lib/resty/libcjson/libcjsondec.lua: -------------------------------------------------------------------------------- 1 | local open, clock, format = io.open, os.clock, string.format 2 | local d = require "resty.libcjson".decode 3 | local file = open("resty/libcjson/citylots.json", "rb") 4 | local content = file:read "*a" 5 | file:close() 6 | local x = clock() 7 | d(content) 8 | local z = clock() - x 9 | print(format("Decoding Time: %.6f", z)) 10 | -------------------------------------------------------------------------------- /lib/resty/libcjson/libcjsonenc.lua: -------------------------------------------------------------------------------- 1 | local open, clock, format = io.open, os.clock, string.format 2 | local d = require "resty.libcjson".decode 3 | local e = require "resty.libcjson".encode 4 | local file = open("resty/libcjson/citylots.json", "rb") 5 | local content = file:read "*a" 6 | file:close() 7 | local t = d(content) 8 | local x = clock() 9 | e(t) 10 | local z = clock() - x 11 | print(format("Encoding Time: %.6f", z)) 12 | -------------------------------------------------------------------------------- /lib/resty/libcjson/luacjsondec.lua: -------------------------------------------------------------------------------- 1 | local open, clock, format = io.open, os.clock, string.format 2 | local d = require "cjson.safe".decode 3 | local file = open("resty/libcjson/citylots.json", "rb") 4 | local content = file:read "*a" 5 | file:close() 6 | local x = clock() 7 | d(content) 8 | local z = clock() - x 9 | print(format("Decoding Time: %.6f", z)) 10 | -------------------------------------------------------------------------------- /lib/resty/libcjson/luacjsonenc.lua: -------------------------------------------------------------------------------- 1 | local open, clock, format = io.open, os.clock, string.format 2 | local d = require "cjson.safe".decode 3 | local e = require "cjson.safe".encode 4 | local file = open("resty/libcjson/citylots.json", "rb") 5 | local content = file:read "*a" 6 | file:close() 7 | local t = d(content) 8 | local x = clock() 9 | e(t) 10 | local z = clock() - x 11 | print(format("Encoding Time: %.6f", z)) 12 | -------------------------------------------------------------------------------- /lua-resty-libcjson-dev-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-libcjson" 2 | version = "dev-1" 3 | source = { 4 | url = "git://github.com/bungle/lua-resty-libcjson.git" 5 | } 6 | description = { 7 | summary = "LuaJIT FFI-based cJSON library (tested with OpenResty too).", 8 | detailed = "lua-resty-libcjson is a JSON library for cJSON C-library (LuaJIT bindings).", 9 | homepage = "https://github.com/bungle/lua-resty-libcjson", 10 | maintainer = "Aapo Talvensaari ", 11 | license = "BSD" 12 | } 13 | dependencies = { 14 | "lua >= 5.1" 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | ["resty.libcjson"] = "lib/resty/libcjson.lua" 20 | } 21 | } 22 | --------------------------------------------------------------------------------