diff options
-rw-r--r-- | README.md | 34 | ||||
-rw-r--r-- | lua_cjson.c | 63 | ||||
-rw-r--r-- | tests/agentzh.t | 79 |
3 files changed, 155 insertions, 21 deletions
@@ -11,6 +11,7 @@ Table of Contents | |||
11 | * [Additions to mpx/lua](#additions) | 11 | * [Additions to mpx/lua](#additions) |
12 | * [encode_empty_table_as_object](#encode_empty_table_as_object) | 12 | * [encode_empty_table_as_object](#encode_empty_table_as_object) |
13 | * [empty_array](#empty_array) | 13 | * [empty_array](#empty_array) |
14 | * [array_mt](#array_mt) | ||
14 | * [empty_array_mt](#empty_array_mt) | 15 | * [empty_array_mt](#empty_array_mt) |
15 | * [encode_number_precision](#encode_number_precision) | 16 | * [encode_number_precision](#encode_number_precision) |
16 | 17 | ||
@@ -76,6 +77,39 @@ This will generate: | |||
76 | 77 | ||
77 | [Back to TOC](#table-of-contents) | 78 | [Back to TOC](#table-of-contents) |
78 | 79 | ||
80 | array_mt | ||
81 | -------- | ||
82 | **syntax:** `setmetatable({}, cjson.array_mt)` | ||
83 | |||
84 | When lua-cjson encodes a table with this metatable, it will systematically | ||
85 | encode it as a JSON Array. The resulting, encoded Array will contain the array | ||
86 | part of the table, and will be of the same length as the `#` operator on that | ||
87 | table. Holes in the table will be encoded with the `null` JSON value. | ||
88 | |||
89 | Example: | ||
90 | |||
91 | ```lua | ||
92 | local t = { "hello", "world" } | ||
93 | setmetatable(t, cjson.array_mt) | ||
94 | cjson.encode(t) -- ["hello","world"] | ||
95 | ``` | ||
96 | |||
97 | Or: | ||
98 | |||
99 | ```lua | ||
100 | local t = {} | ||
101 | t[1] = "one" | ||
102 | t[2] = "two" | ||
103 | t[4] = "three" | ||
104 | t.foo = "bar" | ||
105 | setmetatable(t, cjson.array_mt) | ||
106 | cjson.encode(t) -- ["one","two",null,"three"] | ||
107 | ``` | ||
108 | |||
109 | This value was introduced in the `2.1.0.5` release of this module. | ||
110 | |||
111 | [Back to TOC](#table-of-contents) | ||
112 | |||
79 | empty_array_mt | 113 | empty_array_mt |
80 | -------------- | 114 | -------------- |
81 | **syntax:** `setmetatable({}, cjson.empty_array_mt)` | 115 | **syntax:** `setmetatable({}, cjson.empty_array_mt)` |
diff --git a/lua_cjson.c b/lua_cjson.c index d04f151..8d6e313 100644 --- a/lua_cjson.c +++ b/lua_cjson.c | |||
@@ -92,6 +92,7 @@ | |||
92 | #endif | 92 | #endif |
93 | 93 | ||
94 | static const char * const *json_empty_array; | 94 | static const char * const *json_empty_array; |
95 | static const char * const *json_array; | ||
95 | 96 | ||
96 | typedef enum { | 97 | typedef enum { |
97 | T_OBJ_BEGIN, | 98 | T_OBJ_BEGIN, |
@@ -713,21 +714,37 @@ static void json_append_data(lua_State *l, json_config_t *cfg, | |||
713 | case LUA_TTABLE: | 714 | case LUA_TTABLE: |
714 | current_depth++; | 715 | current_depth++; |
715 | json_check_encode_depth(l, cfg, current_depth, json); | 716 | json_check_encode_depth(l, cfg, current_depth, json); |
716 | len = lua_array_length(l, cfg, json); | 717 | |
717 | if (len > 0 || (len == 0 && !cfg->encode_empty_table_as_object)) | 718 | int as_array = 0; |
719 | int has_metatable = lua_getmetatable(l, -1); | ||
720 | |||
721 | if (has_metatable) { | ||
722 | lua_pushlightuserdata(l, &json_array); | ||
723 | lua_rawget(l, LUA_REGISTRYINDEX); | ||
724 | as_array = lua_rawequal(l, -1, -2); | ||
725 | lua_pop(l, 2); | ||
726 | } | ||
727 | |||
728 | if (as_array) { | ||
729 | len = lua_objlen(l, -1); | ||
718 | json_append_array(l, cfg, current_depth, json, len); | 730 | json_append_array(l, cfg, current_depth, json, len); |
719 | else { | 731 | } else { |
720 | int as_array = 0; | 732 | len = lua_array_length(l, cfg, json); |
721 | if (lua_getmetatable(l, -1)) { | ||
722 | lua_pushlightuserdata(l, &json_empty_array); | ||
723 | lua_rawget(l, LUA_REGISTRYINDEX); | ||
724 | as_array = lua_rawequal(l, -1, -2); | ||
725 | lua_pop(l, 2); | ||
726 | } | ||
727 | 733 | ||
728 | if (as_array) { | 734 | if (len > 0 || (len == 0 && !cfg->encode_empty_table_as_object)) { |
729 | json_append_array(l, cfg, current_depth, json, 0); | 735 | json_append_array(l, cfg, current_depth, json, len); |
730 | } else { | 736 | } else { |
737 | if (has_metatable) { | ||
738 | lua_getmetatable(l, -1); | ||
739 | lua_pushlightuserdata(l, &json_empty_array); | ||
740 | lua_rawget(l, LUA_REGISTRYINDEX); | ||
741 | as_array = lua_rawequal(l, -1, -2); | ||
742 | lua_pop(l, 2); /* pop pointer + metatable */ | ||
743 | if (as_array) { | ||
744 | json_append_array(l, cfg, current_depth, json, 0); | ||
745 | break; | ||
746 | } | ||
747 | } | ||
731 | json_append_object(l, cfg, current_depth, json); | 748 | json_append_object(l, cfg, current_depth, json); |
732 | } | 749 | } |
733 | } | 750 | } |
@@ -738,7 +755,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg, | |||
738 | case LUA_TLIGHTUSERDATA: | 755 | case LUA_TLIGHTUSERDATA: |
739 | if (lua_touserdata(l, -1) == NULL) { | 756 | if (lua_touserdata(l, -1) == NULL) { |
740 | strbuf_append_mem(json, "null", 4); | 757 | strbuf_append_mem(json, "null", 4); |
741 | } else if (lua_touserdata(l, -1) == &json_empty_array) { | 758 | } else if (lua_touserdata(l, -1) == &json_array) { |
742 | json_append_array(l, cfg, current_depth, json, 0); | 759 | json_append_array(l, cfg, current_depth, json, 0); |
743 | } | 760 | } |
744 | break; | 761 | break; |
@@ -1412,20 +1429,27 @@ static int lua_cjson_new(lua_State *l) | |||
1412 | /* Initialise number conversions */ | 1429 | /* Initialise number conversions */ |
1413 | fpconv_init(); | 1430 | fpconv_init(); |
1414 | 1431 | ||
1415 | /* Test if empty array metatable is in registry */ | 1432 | /* Test if array metatables are in registry */ |
1416 | lua_pushlightuserdata(l, &json_empty_array); | 1433 | lua_pushlightuserdata(l, &json_empty_array); |
1417 | lua_rawget(l, LUA_REGISTRYINDEX); | 1434 | lua_rawget(l, LUA_REGISTRYINDEX); |
1418 | if (lua_isnil(l, -1)) { | 1435 | if (lua_isnil(l, -1)) { |
1419 | /* Create empty array metatable. | 1436 | /* Create array metatables. |
1420 | * | 1437 | * |
1421 | * If multiple calls to lua_cjson_new() are made, | 1438 | * If multiple calls to lua_cjson_new() are made, |
1422 | * this prevents overriding the table at the given | 1439 | * this prevents overriding the tables at the given |
1423 | * registry's index with a new one. | 1440 | * registry's index with a new one. |
1424 | */ | 1441 | */ |
1425 | lua_pop(l, 1); | 1442 | lua_pop(l, 1); |
1443 | |||
1444 | /* empty_array_mt */ | ||
1426 | lua_pushlightuserdata(l, &json_empty_array); | 1445 | lua_pushlightuserdata(l, &json_empty_array); |
1427 | lua_newtable(l); | 1446 | lua_newtable(l); |
1428 | lua_rawset(l, LUA_REGISTRYINDEX); | 1447 | lua_rawset(l, LUA_REGISTRYINDEX); |
1448 | |||
1449 | /* array_mt */ | ||
1450 | lua_pushlightuserdata(l, &json_array); | ||
1451 | lua_newtable(l); | ||
1452 | lua_rawset(l, LUA_REGISTRYINDEX); | ||
1429 | } | 1453 | } |
1430 | 1454 | ||
1431 | /* cjson module table */ | 1455 | /* cjson module table */ |
@@ -1444,8 +1468,13 @@ static int lua_cjson_new(lua_State *l) | |||
1444 | lua_rawget(l, LUA_REGISTRYINDEX); | 1468 | lua_rawget(l, LUA_REGISTRYINDEX); |
1445 | lua_setfield(l, -2, "empty_array_mt"); | 1469 | lua_setfield(l, -2, "empty_array_mt"); |
1446 | 1470 | ||
1471 | /* Set cjson.array_mt */ | ||
1472 | lua_pushlightuserdata(l, &json_array); | ||
1473 | lua_rawget(l, LUA_REGISTRYINDEX); | ||
1474 | lua_setfield(l, -2, "array_mt"); | ||
1475 | |||
1447 | /* Set cjson.empty_array */ | 1476 | /* Set cjson.empty_array */ |
1448 | lua_pushlightuserdata(l, &json_empty_array); | 1477 | lua_pushlightuserdata(l, &json_array); |
1449 | lua_setfield(l, -2, "empty_array"); | 1478 | lua_setfield(l, -2, "empty_array"); |
1450 | 1479 | ||
1451 | /* Set module name / version fields */ | 1480 | /* Set module name / version fields */ |
diff --git a/tests/agentzh.t b/tests/agentzh.t index 0b281a1..dd70fb8 100644 --- a/tests/agentzh.t +++ b/tests/agentzh.t | |||
@@ -116,7 +116,78 @@ print(cjson.encode(data)) | |||
116 | 116 | ||
117 | 117 | ||
118 | 118 | ||
119 | === TEST 9: multiple calls to lua_cjson_new (1/2) | 119 | === TEST 9: array_mt on empty tables |
120 | --- lua | ||
121 | local cjson = require "cjson" | ||
122 | local data = {} | ||
123 | setmetatable(data, cjson.array_mt) | ||
124 | print(cjson.encode(data)) | ||
125 | --- out | ||
126 | [] | ||
127 | |||
128 | |||
129 | |||
130 | === TEST 10: array_mt on non-empty tables | ||
131 | --- lua | ||
132 | local cjson = require "cjson" | ||
133 | local data = { "foo", "bar" } | ||
134 | setmetatable(data, cjson.array_mt) | ||
135 | print(cjson.encode(data)) | ||
136 | --- out | ||
137 | ["foo","bar"] | ||
138 | |||
139 | |||
140 | |||
141 | === TEST 11: array_mt on non-empty tables with holes | ||
142 | --- lua | ||
143 | local cjson = require "cjson" | ||
144 | local data = {} | ||
145 | data[1] = "foo" | ||
146 | data[2] = "bar" | ||
147 | data[4] = "last" | ||
148 | data[9] = "none" | ||
149 | setmetatable(data, cjson.array_mt) | ||
150 | print(cjson.encode(data)) | ||
151 | --- out | ||
152 | ["foo","bar",null,"last"] | ||
153 | |||
154 | |||
155 | |||
156 | === TEST 12: array_mt on tables with hash part | ||
157 | --- lua | ||
158 | local cjson = require "cjson" | ||
159 | local data | ||
160 | |||
161 | if jit and string.find(jit.version, "LuaJIT 2.1.0", nil, true) then | ||
162 | local new_tab = require "table.new" | ||
163 | data = new_tab(0, 2) -- allocating hash part only | ||
164 | |||
165 | else | ||
166 | data = {} | ||
167 | end | ||
168 | |||
169 | data.foo = "bar" | ||
170 | data[1] = "hello" | ||
171 | setmetatable(data, cjson.array_mt) | ||
172 | print(cjson.encode(data)) | ||
173 | --- out | ||
174 | ["hello"] | ||
175 | |||
176 | |||
177 | |||
178 | === TEST 13: multiple calls to lua_cjson_new (1/3) | ||
179 | --- lua | ||
180 | local cjson = require "cjson" | ||
181 | package.loaded["cjson"] = nil | ||
182 | require "cjson" | ||
183 | local arr = setmetatable({}, cjson.array_mt) | ||
184 | print(cjson.encode(arr)) | ||
185 | --- out | ||
186 | [] | ||
187 | |||
188 | |||
189 | |||
190 | === TEST 14: multiple calls to lua_cjson_new (2/3) | ||
120 | --- lua | 191 | --- lua |
121 | local cjson = require "cjson" | 192 | local cjson = require "cjson" |
122 | package.loaded["cjson"] = nil | 193 | package.loaded["cjson"] = nil |
@@ -128,7 +199,7 @@ print(cjson.encode(arr)) | |||
128 | 199 | ||
129 | 200 | ||
130 | 201 | ||
131 | === TEST 10: multiple calls to lua_cjson_new (2/2) | 202 | === TEST 15: multiple calls to lua_cjson_new (3/3) |
132 | --- lua | 203 | --- lua |
133 | local cjson = require "cjson.safe" | 204 | local cjson = require "cjson.safe" |
134 | -- load another cjson instance (not in package.loaded) | 205 | -- load another cjson instance (not in package.loaded) |
@@ -140,7 +211,7 @@ print(cjson.encode(arr)) | |||
140 | 211 | ||
141 | 212 | ||
142 | 213 | ||
143 | === TEST 11: & in JSON | 214 | === TEST 16: & in JSON |
144 | --- lua | 215 | --- lua |
145 | local cjson = require "cjson" | 216 | local cjson = require "cjson" |
146 | local a="[\"a=1&b=2\"]" | 217 | local a="[\"a=1&b=2\"]" |
@@ -151,7 +222,7 @@ print(cjson.encode(b)) | |||
151 | 222 | ||
152 | 223 | ||
153 | 224 | ||
154 | === TEST 12: default and max precision | 225 | === TEST 17: default and max precision |
155 | --- lua | 226 | --- lua |
156 | local math = require "math" | 227 | local math = require "math" |
157 | local cjson = require "cjson" | 228 | local cjson = require "cjson" |