From 7a9c25ee69f38974e99322971eace37ba1753074 Mon Sep 17 00:00:00 2001 From: Thibault Charbonnier Date: Sun, 28 Feb 2016 20:03:12 -0800 Subject: feat: cjson.as_array metamethod to enforce empty array encoding A proposed improved patch of openresty/lua-cjson#1 (a patch commonly proposed to lua-cjson and its forks), taking into considerations comments from the original PR. - use a lightuserdata key to store the metatable in the Lua Registry (more efficient and avoiding conflicts) - provide a lightuserdata resulting in empty arrays as well - tests cases moved to t/agentzh.t, where cases for 'encode_empty_table_as_object' are already written. It seems like a better place for tests specific to the OpenResty fork's additions. - a more complex test case --- lua_cjson.c | 37 +++++++++++++++++++++++++--- tests/agentzh.t | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/lua_cjson.c b/lua_cjson.c index 5ff3bf7..5f4faf2 100644 --- a/lua_cjson.c +++ b/lua_cjson.c @@ -75,6 +75,8 @@ #define DEFAULT_DECODE_INVALID_NUMBERS 0 #endif +static const char * const *json_empty_array; + typedef enum { T_OBJ_BEGIN, T_OBJ_END, @@ -698,8 +700,21 @@ static void json_append_data(lua_State *l, json_config_t *cfg, len = lua_array_length(l, cfg, json); if (len > 0 || (len == 0 && !cfg->encode_empty_table_as_object)) json_append_array(l, cfg, current_depth, json, len); - else - json_append_object(l, cfg, current_depth, json); + else { + int as_array = 0; + if (lua_getmetatable(l, -1)) { + lua_pushlightuserdata(l, &json_empty_array); + lua_rawget(l, LUA_REGISTRYINDEX); + as_array = lua_rawequal(l, -1, -2); + lua_pop(l, 2); + } + + if (as_array) { + json_append_array(l, cfg, current_depth, json, 0); + } else { + json_append_object(l, cfg, current_depth, json); + } + } break; case LUA_TNIL: strbuf_append_mem(json, "null", 4); @@ -707,8 +722,10 @@ static void json_append_data(lua_State *l, json_config_t *cfg, case LUA_TLIGHTUSERDATA: if (lua_touserdata(l, -1) == NULL) { strbuf_append_mem(json, "null", 4); - break; + } else if (lua_touserdata(l, -1) == &json_empty_array) { + json_append_array(l, cfg, current_depth, json, 0); } + break; default: /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD, * and LUA_TLIGHTUSERDATA) cannot be serialised */ @@ -1378,6 +1395,11 @@ static int lua_cjson_new(lua_State *l) /* Initialise number conversions */ fpconv_init(); + /* Create empty array metatable */ + lua_pushlightuserdata(l, &json_empty_array); + lua_newtable(l); + lua_rawset(l, LUA_REGISTRYINDEX); + /* cjson module table */ lua_newtable(l); @@ -1389,6 +1411,15 @@ static int lua_cjson_new(lua_State *l) lua_pushlightuserdata(l, NULL); lua_setfield(l, -2, "null"); + /* Set cjson.empty_array_mt */ + lua_pushlightuserdata(l, &json_empty_array); + lua_rawget(l, LUA_REGISTRYINDEX); + lua_setfield(l, -2, "empty_array_mt"); + + /* Set cjson.empty_array */ + lua_pushlightuserdata(l, &json_empty_array); + lua_setfield(l, -2, "empty_array"); + /* Set module name / version fields */ lua_pushliteral(l, CJSON_MODNAME); lua_setfield(l, -2, "_NAME"); diff --git a/tests/agentzh.t b/tests/agentzh.t index 0b546ff..e76f910 100644 --- a/tests/agentzh.t +++ b/tests/agentzh.t @@ -41,7 +41,79 @@ print(cjson.encode({dogs = {}})) -=== TEST 4: & in JSON +=== TEST 4: empty_array userdata +--- lua +local cjson = require "cjson" +print(cjson.encode({arr = cjson.empty_array})) +--- out +{"arr":[]} + + + +=== TEST 5: empty_array_mt +--- lua +local cjson = require "cjson" +local empty_arr = setmetatable({}, cjson.empty_array_mt) +print(cjson.encode({arr = empty_arr})) +--- out +{"arr":[]} + + + +=== TEST 6: empty_array_mt and empty tables as objects (explicit) +--- lua +local cjson = require "cjson" +local empty_arr = setmetatable({}, cjson.empty_array_mt) +print(cjson.encode({obj = {}, arr = empty_arr})) +--- out +{"arr":[],"obj":{}} + + + +=== TEST 7: empty_array_mt and empty tables as objects (explicit) +--- lua +local cjson = require "cjson" +cjson.encode_empty_table_as_object(true) +local empty_arr = setmetatable({}, cjson.empty_array_mt) +local data = { + arr = empty_arr, + foo = { + obj = {}, + foobar = { + arr = cjson.empty_array, + obj = {} + } + } +} +print(cjson.encode(data)) +--- out +{"foo":{"foobar":{"obj":{},"arr":[]},"obj":{}},"arr":[]} + + + +=== TEST 8: empty_array_mt on non-empty tables +--- lua +local cjson = require "cjson" +cjson.encode_empty_table_as_object(true) +local array = {"hello", "world", "lua"} +setmetatable(array, cjson.empty_array_mt) +local data = { + arr = array, + foo = { + obj = {}, + foobar = { + arr = cjson.empty_array, + obj = {} + } + } +} +print(cjson.encode(data)) +--- out +{"foo":{"foobar":{"obj":{},"arr":[]},"obj":{}},"arr":["hello","world","lua"]} + + + +=== TEST 9: & in JSON --- lua local cjson = require "cjson" local a="[\"a=1&b=2\"]" @@ -52,7 +124,7 @@ print(cjson.encode(b)) -=== TEST 5: default and max precision +=== TEST 10: default and max precision --- lua local math = require "math" local cjson = require "cjson" -- cgit v1.2.3-55-g6feb