From 42064f63d05edf0769814734ada8ab1ce3435931 Mon Sep 17 00:00:00 2001
From: Brian Maher <brian@brimworks.com>
Date: Mon, 1 Jun 2015 12:37:01 -0700
Subject: [PATCH 1/3] Allow empty array to be encoded with a metatable
 containing __is_cjson_array=true.

---
 lua_cjson.c | 30 ++++++++++++++++++++++++++++--
 1 file changed, 28 insertions(+), 2 deletions(-)

diff --git a/lua_cjson.c b/lua_cjson.c
index c14a1c5..283e3af 100644
--- a/lua_cjson.c
+++ b/lua_cjson.c
@@ -494,9 +494,25 @@ static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json)
     int max;
     int items;
 
-    max = 0;
+    max = -1;
     items = 0;
 
+    if ( lua_getmetatable(l, -1) ) {
+        int is_array, has_is_array;
+
+        lua_pushliteral(l, "__is_cjson_array");
+        lua_rawget(l, -2);
+        has_is_array = lua_isboolean(l, -1);
+        is_array = has_is_array && lua_toboolean(l, -1);
+        lua_pop(l, 2);
+
+        if ( has_is_array && ! is_array ) {
+            return -1;
+        } else {
+            max = 0;
+        }
+    }
+
     lua_pushnil(l);
     /* table, startkey */
     while (lua_next(l, -2) != 0) {
@@ -685,7 +701,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
         current_depth++;
         json_check_encode_depth(l, cfg, current_depth, json);
         len = lua_array_length(l, cfg, json);
-        if (len > 0)
+        if (len >= 0)
             json_append_array(l, cfg, current_depth, json, len);
         else
             json_append_object(l, cfg, current_depth, json);
@@ -1204,6 +1220,16 @@ static void json_parse_array_context(lua_State *l, json_parse_t *json)
 
     /* Handle empty arrays */
     if (token.type == T_ARR_END) {
+        // Mark as array:
+        luaL_getmetatable(l, "cjson.array");
+        if ( lua_isnil(l, -1) ) {
+            lua_pop(l, 1);
+            luaL_newmetatable(l, "cjson.array");
+            lua_pushboolean(l, 1);
+            lua_setfield(l, -2, "__is_cjson_array");
+        }
+        lua_setmetatable(l, -2);
+
         json_decode_ascend(json);
         return;
     }
-- 
2.6.3


From 514c4b4761ac0552182acbf25039930ddca70070 Mon Sep 17 00:00:00 2001
From: Brian Maher <brian@brimworks.com>
Date: Thu, 3 Sep 2015 12:33:33 -0700
Subject: [PATCH 2/3] Replace __is_cjson_array with check for __name="array"
 and replace lightuserdata NULL with __name="null" meatable. This will allow
 better interchange with other modules that need to define "array" and "null"
 types. Note that you can also define custom implementations of "array" and
 "null" if you have access to the registry.

---
 lua_cjson.c | 65 ++++++++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 43 insertions(+), 22 deletions(-)

diff --git a/lua_cjson.c b/lua_cjson.c
index 283e3af..2140743 100644
--- a/lua_cjson.c
+++ b/lua_cjson.c
@@ -497,20 +497,13 @@ static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json)
     max = -1;
     items = 0;
 
-    if ( lua_getmetatable(l, -1) ) {
-        int is_array, has_is_array;
-
-        lua_pushliteral(l, "__is_cjson_array");
+    if (lua_getmetatable(l, -1)) {
+        lua_pushliteral(l, "__name");
         lua_rawget(l, -2);
-        has_is_array = lua_isboolean(l, -1);
-        is_array = has_is_array && lua_toboolean(l, -1);
-        lua_pop(l, 2);
-
-        if ( has_is_array && ! is_array ) {
-            return -1;
-        } else {
+        if (lua_isstring(l, -1) && strcmp("array", lua_tostring(l, -1)) == 0) {
             max = 0;
         }
+        lua_pop(l, 2);
     }
 
     lua_pushnil(l);
@@ -698,6 +691,16 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
             strbuf_append_mem(json, "false", 5);
         break;
     case LUA_TTABLE:
+        if (lua_getmetatable(l, -1)) {
+            lua_pushliteral(l, "__name");
+            lua_rawget(l, -2);
+            if (lua_isstring(l, -1) && strcmp("null", lua_tostring(l, -1)) == 0) {
+                strbuf_append_mem(json, "null", 4);
+                lua_pop(l, 2);
+                break;
+            }
+            lua_pop(l, 2);
+        }
         current_depth++;
         json_check_encode_depth(l, cfg, current_depth, json);
         len = lua_array_length(l, cfg, json);
@@ -1221,13 +1224,7 @@ static void json_parse_array_context(lua_State *l, json_parse_t *json)
     /* Handle empty arrays */
     if (token.type == T_ARR_END) {
         // Mark as array:
-        luaL_getmetatable(l, "cjson.array");
-        if ( lua_isnil(l, -1) ) {
-            lua_pop(l, 1);
-            luaL_newmetatable(l, "cjson.array");
-            lua_pushboolean(l, 1);
-            lua_setfield(l, -2, "__is_cjson_array");
-        }
+        luaL_getmetatable(l, "array");
         lua_setmetatable(l, -2);
 
         json_decode_ascend(json);
@@ -1274,9 +1271,9 @@ static void json_process_value(lua_State *l, json_parse_t *json,
         break;;
     case T_NULL:
         /* In Lua, setting "t[k] = nil" will delete k from the table.
-         * Hence a NULL pointer lightuserdata object is used instead */
-        lua_pushlightuserdata(l, NULL);
-        break;;
+         * Hence a NULL userdata object is used instead */
+        luaL_getmetatable(l, "null");
+        break;
     default:
         json_throw_parse_error(l, json, "value", token);
     }
@@ -1372,6 +1369,12 @@ static int json_protect_conversion(lua_State *l)
     return luaL_error(l, "Memory allocation error in CJSON protected call");
 }
 
+static int json_null_tostring(lua_State *l)
+{
+    lua_pushliteral(l, "null");
+    return 1;
+}
+
 /* Return cjson module table */
 static int lua_cjson_new(lua_State *l)
 {
@@ -1392,6 +1395,24 @@ static int lua_cjson_new(lua_State *l)
     /* Initialise number conversions */
     fpconv_init();
 
+    /* Initialize "null" */
+    if ( luaL_newmetatable(l, "null") ) {
+        lua_createtable(l, 0, 2);
+        lua_pushliteral(l, "null");
+        lua_setfield(l, -2, "__name");
+        lua_pushcfunction(l, json_null_tostring);
+        lua_setfield(l, -2, "__tostring");
+        lua_setmetatable(l, -2);
+    }
+    lua_pop(l, 1);
+
+    /* Initialize "array" metatable */
+    if ( luaL_newmetatable(l, "array") ) {
+        lua_pushliteral(l, "array");
+        lua_setfield(l, -2, "__name");
+    }
+    lua_pop(l, 1);
+
     /* cjson module table */
     lua_newtable(l);
 
@@ -1400,7 +1421,7 @@ static int lua_cjson_new(lua_State *l)
     luaL_setfuncs(l, reg, 1);
 
     /* Set cjson.null */
-    lua_pushlightuserdata(l, NULL);
+    luaL_getmetatable(l, "null");
     lua_setfield(l, -2, "null");
 
     /* Set module name / version fields */
-- 
2.6.3


From 438a71d35417453e2207f64f706d02f199525c7d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timo=20Ter=C3=A4s?= <timo.teras@iki.fi>
Date: Tue, 8 Dec 2015 09:16:18 +0200
Subject: [PATCH 3/3] fix empty array default encoding, and add meta __name for
 objects

---
 lua_cjson.c | 52 +++++++++++++++++++++++++++++++++-------------------
 1 file changed, 33 insertions(+), 19 deletions(-)

diff --git a/lua_cjson.c b/lua_cjson.c
index 2140743..25952ec 100644
--- a/lua_cjson.c
+++ b/lua_cjson.c
@@ -493,17 +493,20 @@ static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json)
     double k;
     int max;
     int items;
+    int is_array = 0;
+    const char *mt_name;
 
-    max = -1;
+    max = 0;
     items = 0;
 
     if (lua_getmetatable(l, -1)) {
         lua_pushliteral(l, "__name");
         lua_rawget(l, -2);
-        if (lua_isstring(l, -1) && strcmp("array", lua_tostring(l, -1)) == 0) {
-            max = 0;
-        }
+        mt_name = lua_isstring(l, -1) ? lua_tostring(l, -1) : "";
         lua_pop(l, 2);
+        if (strcmp("object", mt_name) == 0)
+            return -1;
+        is_array = strcmp("array", mt_name) == 0;
     }
 
     lua_pushnil(l);
@@ -521,6 +524,10 @@ static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json)
                 continue;
             }
         }
+        if (is_array) {
+            lua_pop(l, 1);
+            continue;
+        }
 
         /* Must not be an array (non integer key) */
         lua_pop(l, 2);
@@ -537,6 +544,10 @@ static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json)
         return -1;
     }
 
+    /* Backwards compatibility - encode untyped zero length table as object */
+    if (!is_array && max == 0)
+        return -1;
+
     return max;
 }
 
@@ -1166,6 +1177,8 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json)
     json_decode_descend(l, json, 3);
 
     lua_newtable(l);
+    luaL_getmetatable(l, "cjson.object");
+    lua_setmetatable(l, -2);
 
     json_next_token(json, &token);
 
@@ -1218,15 +1231,13 @@ static void json_parse_array_context(lua_State *l, json_parse_t *json)
     json_decode_descend(l, json, 2);
 
     lua_newtable(l);
+    luaL_getmetatable(l, "cjson.array");
+    lua_setmetatable(l, -2);
 
     json_next_token(json, &token);
 
     /* Handle empty arrays */
     if (token.type == T_ARR_END) {
-        // Mark as array:
-        luaL_getmetatable(l, "array");
-        lua_setmetatable(l, -2);
-
         json_decode_ascend(json);
         return;
     }
@@ -1272,7 +1283,7 @@ static void json_process_value(lua_State *l, json_parse_t *json,
     case T_NULL:
         /* In Lua, setting "t[k] = nil" will delete k from the table.
          * Hence a NULL userdata object is used instead */
-        luaL_getmetatable(l, "null");
+        luaL_getmetatable(l, "cjson.null");
         break;
     default:
         json_throw_parse_error(l, json, "value", token);
@@ -1395,8 +1406,11 @@ static int lua_cjson_new(lua_State *l)
     /* Initialise number conversions */
     fpconv_init();
 
+    /* cjson module table */
+    lua_newtable(l);
+
     /* Initialize "null" */
-    if ( luaL_newmetatable(l, "null") ) {
+    if ( luaL_newmetatable(l, "cjson.null") ) {
         lua_createtable(l, 0, 2);
         lua_pushliteral(l, "null");
         lua_setfield(l, -2, "__name");
@@ -1404,26 +1418,26 @@ static int lua_cjson_new(lua_State *l)
         lua_setfield(l, -2, "__tostring");
         lua_setmetatable(l, -2);
     }
-    lua_pop(l, 1);
+    lua_setfield(l, -2, "null");
 
     /* Initialize "array" metatable */
-    if ( luaL_newmetatable(l, "array") ) {
+    if ( luaL_newmetatable(l, "cjson.array") ) {
         lua_pushliteral(l, "array");
         lua_setfield(l, -2, "__name");
     }
-    lua_pop(l, 1);
+    lua_setfield(l, -2, "array");
 
-    /* cjson module table */
-    lua_newtable(l);
+    /* Initialize "object" metatable */
+    if ( luaL_newmetatable(l, "cjson.object") ) {
+        lua_pushliteral(l, "object");
+        lua_setfield(l, -2, "__name");
+    }
+    lua_setfield(l, -2, "object");
 
     /* Register functions with config data as upvalue */
     json_create_config(l);
     luaL_setfuncs(l, reg, 1);
 
-    /* Set cjson.null */
-    luaL_getmetatable(l, "null");
-    lua_setfield(l, -2, "null");
-
     /* Set module name / version fields */
     lua_pushliteral(l, CJSON_MODNAME);
     lua_setfield(l, -2, "_NAME");
-- 
2.6.3

