Skip to content

Commit b5e364c

Browse files
thibaultchaagentzh
authored andcommitted
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) <agentzh@gmail.com>
1 parent 5f9efa4 commit b5e364c

3 files changed

Lines changed: 113 additions & 6 deletions

File tree

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Table of Contents
1414
* [array_mt](#array_mt)
1515
* [empty_array_mt](#empty_array_mt)
1616
* [encode_number_precision](#encode_number_precision)
17+
* [decode_array_with_array_mt](#decode_array_with_array_mt)
1718

1819
Description
1920
===========
@@ -156,3 +157,39 @@ encode_number_precision
156157
This fork allows encoding of numbers with a `precision` up to 16 decimals (vs. 14 in mpx/lua-cjson).
157158

158159
[Back to TOC](#table-of-contents)
160+
161+
decode_array_with_array_mt
162+
--------------------------
163+
**syntax:** `cjson.decode_array_with_array_mt(enabled)`
164+
165+
**default:** false
166+
167+
If enabled, JSON Arrays decoded by `cjson.decode` will result in Lua
168+
tables with the [`array_mt`](#array_mt) metatable. This can ensure a 1-to-1
169+
relationship between arrays upon multiple encoding/decoding of your
170+
JSON data with this module.
171+
172+
If disabled, JSON Arrays will be decoded to plain Lua tables, without
173+
the `array_mt` metatable.
174+
175+
The `enabled` argument is a boolean.
176+
177+
Example:
178+
179+
```lua
180+
local cjson = require "cjson"
181+
182+
-- default behavior
183+
local my_json = [[{"my_array":[]}]]
184+
local t = cjson.decode(my_json)
185+
cjson.encode(t) -- {"my_array":{}} back to an object
186+
187+
-- now, if this behavior is enabled
188+
cjson.decode_array_with_array_mt(true)
189+
190+
local my_json = [[{"my_array":[]}]]
191+
local t = cjson.decode(my_json)
192+
cjson.encode(t) -- {"my_array":[]} properly re-encoded as an array
193+
```
194+
195+
[Back to TOC](#table-of-contents)

lua_cjson.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
#define DEFAULT_ENCODE_KEEP_BUFFER 1
8080
#define DEFAULT_ENCODE_NUMBER_PRECISION 14
8181
#define DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT 1
82+
#define DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT 0
8283

8384
#ifdef DISABLE_INVALID_NUMBERS
8485
#undef DEFAULT_DECODE_INVALID_NUMBERS
@@ -148,6 +149,7 @@ typedef struct {
148149

149150
int decode_invalid_numbers;
150151
int decode_max_depth;
152+
int decode_array_with_array_mt;
151153
} json_config_t;
152154

153155
typedef struct {
@@ -329,6 +331,16 @@ static int json_cfg_encode_empty_table_as_object(lua_State *l)
329331
return json_enum_option(l, 1, &cfg->encode_empty_table_as_object, NULL, 1);
330332
}
331333

334+
/* Configures how to decode arrays */
335+
static int json_cfg_decode_array_with_array_mt(lua_State *l)
336+
{
337+
json_config_t *cfg = json_arg_init(l, 1);
338+
339+
json_enum_option(l, 1, &cfg->decode_array_with_array_mt, NULL, 1);
340+
341+
return 1;
342+
}
343+
332344
/* Configures JSON encoding buffer persistence */
333345
static int json_cfg_encode_keep_buffer(lua_State *l)
334346
{
@@ -420,6 +432,7 @@ static void json_create_config(lua_State *l)
420432
cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER;
421433
cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION;
422434
cfg->encode_empty_table_as_object = DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT;
435+
cfg->decode_array_with_array_mt = DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT;
423436

424437
#if DEFAULT_ENCODE_KEEP_BUFFER > 0
425438
strbuf_init(&cfg->encode_buf, 0);
@@ -1261,6 +1274,13 @@ static void json_parse_array_context(lua_State *l, json_parse_t *json)
12611274

12621275
lua_newtable(l);
12631276

1277+
/* set array_mt on the table at the top of the stack */
1278+
if (json->cfg->decode_array_with_array_mt) {
1279+
lua_pushlightuserdata(l, &json_array);
1280+
lua_rawget(l, LUA_REGISTRYINDEX);
1281+
lua_setmetatable(l, -2);
1282+
}
1283+
12641284
json_next_token(json, &token);
12651285

12661286
/* Handle empty arrays */
@@ -1415,6 +1435,7 @@ static int lua_cjson_new(lua_State *l)
14151435
{ "encode", json_encode },
14161436
{ "decode", json_decode },
14171437
{ "encode_empty_table_as_object", json_cfg_encode_empty_table_as_object },
1438+
{ "decode_array_with_array_mt", json_cfg_decode_array_with_array_mt },
14181439
{ "encode_sparse_array", json_cfg_encode_sparse_array },
14191440
{ "encode_max_depth", json_cfg_encode_max_depth },
14201441
{ "decode_max_depth", json_cfg_decode_max_depth },

tests/agentzh.t

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,56 @@ print(cjson.encode(data))
153153
154154
155155
156-
=== TEST 12: array_mt on tables with hash part
156+
=== TEST 12: decode() by default does not set array_mt on empty arrays
157+
--- lua
158+
local cjson = require "cjson"
159+
local json = [[{"my_array":[]}]]
160+
local t = cjson.decode(json)
161+
local has_metatable = getmetatable(t.my_array) == cjson.array_mt
162+
print("decoded JSON array has metatable: " .. tostring(has_metatable))
163+
print(cjson.encode(t))
164+
--- out
165+
decoded JSON array has metatable: false
166+
{"my_array":{}}
167+
168+
169+
170+
=== TEST 13: decode() sets array_mt on non-empty arrays if enabled
171+
--- lua
172+
local cjson = require "cjson"
173+
cjson.decode_array_with_array_mt(true)
174+
local json = [[{"my_array":["hello","world"]}]]
175+
local t = cjson.decode(json)
176+
t.my_array.hash_value = "adding a hash value"
177+
-- emptying the array part
178+
t.my_array[1] = nil
179+
t.my_array[2] = nil
180+
local has_metatable = getmetatable(t.my_array) == cjson.array_mt
181+
print("decoded JSON array has metatable: " .. tostring(has_metatable))
182+
print(cjson.encode(t))
183+
--- out
184+
decoded JSON array has metatable: true
185+
{"my_array":[]}
186+
187+
188+
189+
=== TEST 14: cfg can enable/disable setting array_mt
190+
--- lua
191+
local cjson = require "cjson"
192+
cjson.decode_array_with_array_mt(true)
193+
cjson.decode_array_with_array_mt(false)
194+
local json = [[{"my_array":[]}]]
195+
local t = cjson.decode(json)
196+
local has_metatable = getmetatable(t.my_array) == cjson.array_mt
197+
print("decoded JSON array has metatable: " .. tostring(has_metatable))
198+
print(cjson.encode(t))
199+
--- out
200+
decoded JSON array has metatable: false
201+
{"my_array":{}}
202+
203+
204+
205+
=== TEST 15: array_mt on tables with hash part
157206
--- lua
158207
local cjson = require "cjson"
159208
local data
@@ -175,7 +224,7 @@ print(cjson.encode(data))
175224
176225
177226
178-
=== TEST 13: multiple calls to lua_cjson_new (1/3)
227+
=== TEST 16: multiple calls to lua_cjson_new (1/3)
179228
--- lua
180229
local cjson = require "cjson"
181230
package.loaded["cjson"] = nil
@@ -187,7 +236,7 @@ print(cjson.encode(arr))
187236
188237
189238
190-
=== TEST 14: multiple calls to lua_cjson_new (2/3)
239+
=== TEST 17: multiple calls to lua_cjson_new (2/3)
191240
--- lua
192241
local cjson = require "cjson"
193242
package.loaded["cjson"] = nil
@@ -199,7 +248,7 @@ print(cjson.encode(arr))
199248
200249
201250
202-
=== TEST 15: multiple calls to lua_cjson_new (3/3)
251+
=== TEST 18: multiple calls to lua_cjson_new (3/3)
203252
--- lua
204253
local cjson = require "cjson.safe"
205254
-- load another cjson instance (not in package.loaded)
@@ -211,7 +260,7 @@ print(cjson.encode(arr))
211260
212261
213262
214-
=== TEST 16: & in JSON
263+
=== TEST 19: & in JSON
215264
--- lua
216265
local cjson = require "cjson"
217266
local a="[\"a=1&b=2\"]"
@@ -222,7 +271,7 @@ print(cjson.encode(b))
222271
223272
224273
225-
=== TEST 17: default and max precision
274+
=== TEST 20: default and max precision
226275
--- lua
227276
local math = require "math"
228277
local cjson = require "cjson"

0 commit comments

Comments
 (0)