From b5e364c7c60167995944ed3a3b9c54d9a377fc1d Mon Sep 17 00:00:00 2001 From: Thibault Charbonnier Date: Sat, 5 Aug 2017 15:19:43 -0700 Subject: feature: set cjson.array_mt on decoded JSON arrays. this can be turned on via cjson.decode_array_with_array_mt(true). off by default. Signed-off-by: Yichun Zhang (agentzh) --- README.md | 37 ++++++++++++++++++++++++++++++++++ lua_cjson.c | 21 ++++++++++++++++++++ tests/agentzh.t | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 113 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1cdb53b..83bfd76 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Table of Contents * [array_mt](#array_mt) * [empty_array_mt](#empty_array_mt) * [encode_number_precision](#encode_number_precision) + * [decode_array_with_array_mt](#decode_array_with_array_mt) Description =========== @@ -156,3 +157,39 @@ encode_number_precision This fork allows encoding of numbers with a `precision` up to 16 decimals (vs. 14 in mpx/lua-cjson). [Back to TOC](#table-of-contents) + +decode_array_with_array_mt +-------------------------- +**syntax:** `cjson.decode_array_with_array_mt(enabled)` + +**default:** false + +If enabled, JSON Arrays decoded by `cjson.decode` will result in Lua +tables with the [`array_mt`](#array_mt) metatable. This can ensure a 1-to-1 +relationship between arrays upon multiple encoding/decoding of your +JSON data with this module. + +If disabled, JSON Arrays will be decoded to plain Lua tables, without +the `array_mt` metatable. + +The `enabled` argument is a boolean. + +Example: + +```lua +local cjson = require "cjson" + +-- default behavior +local my_json = [[{"my_array":[]}]] +local t = cjson.decode(my_json) +cjson.encode(t) -- {"my_array":{}} back to an object + +-- now, if this behavior is enabled +cjson.decode_array_with_array_mt(true) + +local my_json = [[{"my_array":[]}]] +local t = cjson.decode(my_json) +cjson.encode(t) -- {"my_array":[]} properly re-encoded as an array +``` + +[Back to TOC](#table-of-contents) diff --git a/lua_cjson.c b/lua_cjson.c index 8d6e313..6c7eb4e 100644 --- a/lua_cjson.c +++ b/lua_cjson.c @@ -79,6 +79,7 @@ #define DEFAULT_ENCODE_KEEP_BUFFER 1 #define DEFAULT_ENCODE_NUMBER_PRECISION 14 #define DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT 1 +#define DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT 0 #ifdef DISABLE_INVALID_NUMBERS #undef DEFAULT_DECODE_INVALID_NUMBERS @@ -148,6 +149,7 @@ typedef struct { int decode_invalid_numbers; int decode_max_depth; + int decode_array_with_array_mt; } json_config_t; typedef struct { @@ -329,6 +331,16 @@ static int json_cfg_encode_empty_table_as_object(lua_State *l) return json_enum_option(l, 1, &cfg->encode_empty_table_as_object, NULL, 1); } +/* Configures how to decode arrays */ +static int json_cfg_decode_array_with_array_mt(lua_State *l) +{ + json_config_t *cfg = json_arg_init(l, 1); + + json_enum_option(l, 1, &cfg->decode_array_with_array_mt, NULL, 1); + + return 1; +} + /* Configures JSON encoding buffer persistence */ static int json_cfg_encode_keep_buffer(lua_State *l) { @@ -420,6 +432,7 @@ static void json_create_config(lua_State *l) cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER; cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION; cfg->encode_empty_table_as_object = DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT; + cfg->decode_array_with_array_mt = DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT; #if DEFAULT_ENCODE_KEEP_BUFFER > 0 strbuf_init(&cfg->encode_buf, 0); @@ -1261,6 +1274,13 @@ static void json_parse_array_context(lua_State *l, json_parse_t *json) lua_newtable(l); + /* set array_mt on the table at the top of the stack */ + if (json->cfg->decode_array_with_array_mt) { + lua_pushlightuserdata(l, &json_array); + lua_rawget(l, LUA_REGISTRYINDEX); + lua_setmetatable(l, -2); + } + json_next_token(json, &token); /* Handle empty arrays */ @@ -1415,6 +1435,7 @@ static int lua_cjson_new(lua_State *l) { "encode", json_encode }, { "decode", json_decode }, { "encode_empty_table_as_object", json_cfg_encode_empty_table_as_object }, + { "decode_array_with_array_mt", json_cfg_decode_array_with_array_mt }, { "encode_sparse_array", json_cfg_encode_sparse_array }, { "encode_max_depth", json_cfg_encode_max_depth }, { "decode_max_depth", json_cfg_decode_max_depth }, diff --git a/tests/agentzh.t b/tests/agentzh.t index dd70fb8..7967337 100644 --- a/tests/agentzh.t +++ b/tests/agentzh.t @@ -153,7 +153,56 @@ print(cjson.encode(data)) -=== TEST 12: array_mt on tables with hash part +=== TEST 12: decode() by default does not set array_mt on empty arrays +--- lua +local cjson = require "cjson" +local json = [[{"my_array":[]}]] +local t = cjson.decode(json) +local has_metatable = getmetatable(t.my_array) == cjson.array_mt +print("decoded JSON array has metatable: " .. tostring(has_metatable)) +print(cjson.encode(t)) +--- out +decoded JSON array has metatable: false +{"my_array":{}} + + + +=== TEST 13: decode() sets array_mt on non-empty arrays if enabled +--- lua +local cjson = require "cjson" +cjson.decode_array_with_array_mt(true) +local json = [[{"my_array":["hello","world"]}]] +local t = cjson.decode(json) +t.my_array.hash_value = "adding a hash value" +-- emptying the array part +t.my_array[1] = nil +t.my_array[2] = nil +local has_metatable = getmetatable(t.my_array) == cjson.array_mt +print("decoded JSON array has metatable: " .. tostring(has_metatable)) +print(cjson.encode(t)) +--- out +decoded JSON array has metatable: true +{"my_array":[]} + + + +=== TEST 14: cfg can enable/disable setting array_mt +--- lua +local cjson = require "cjson" +cjson.decode_array_with_array_mt(true) +cjson.decode_array_with_array_mt(false) +local json = [[{"my_array":[]}]] +local t = cjson.decode(json) +local has_metatable = getmetatable(t.my_array) == cjson.array_mt +print("decoded JSON array has metatable: " .. tostring(has_metatable)) +print(cjson.encode(t)) +--- out +decoded JSON array has metatable: false +{"my_array":{}} + + + +=== TEST 15: array_mt on tables with hash part --- lua local cjson = require "cjson" local data @@ -175,7 +224,7 @@ print(cjson.encode(data)) -=== TEST 13: multiple calls to lua_cjson_new (1/3) +=== TEST 16: multiple calls to lua_cjson_new (1/3) --- lua local cjson = require "cjson" package.loaded["cjson"] = nil @@ -187,7 +236,7 @@ print(cjson.encode(arr)) -=== TEST 14: multiple calls to lua_cjson_new (2/3) +=== TEST 17: multiple calls to lua_cjson_new (2/3) --- lua local cjson = require "cjson" package.loaded["cjson"] = nil @@ -199,7 +248,7 @@ print(cjson.encode(arr)) -=== TEST 15: multiple calls to lua_cjson_new (3/3) +=== TEST 18: multiple calls to lua_cjson_new (3/3) --- lua local cjson = require "cjson.safe" -- load another cjson instance (not in package.loaded) @@ -211,7 +260,7 @@ print(cjson.encode(arr)) -=== TEST 16: & in JSON +=== TEST 19: & in JSON --- lua local cjson = require "cjson" local a="[\"a=1&b=2\"]" @@ -222,7 +271,7 @@ print(cjson.encode(b)) -=== TEST 17: default and max precision +=== TEST 20: default and max precision --- lua local math = require "math" local cjson = require "cjson" -- cgit v1.2.3-55-g6feb