# HG changeset patch # User jbe # Date 1406768513 -7200 # Node ID c8669dde9ce200c7b890083b6463c290dfebec53 # Parent c8c91216255f01d682f30cf22e31759c43d10ad7 Added JSON export function diff -r c8c91216255f -r c8669dde9ce2 libraries/json/json.c --- a/libraries/json/json.c Thu Jul 31 01:21:33 2014 +0200 +++ b/libraries/json/json.c Thu Jul 31 03:01:53 2014 +0200 @@ -2,6 +2,7 @@ #include #include #include +#include // maximum number of nested JSON values (objects and arrays): // NOTE: The Lua reference states that the stack may typically contain at least @@ -17,6 +18,7 @@ #define JSON_REGENT char #define JSON_REGPOINTER void * #define json_pushlightref(L, x) lua_pushlightuserdata((L), &json_reference.x) +#define json_islightref(L, i, x) (lua_touserdata((L), (i)) == &json_reference.x) #define json_regpointer(x) (&json_registry.x) #define json_regfetchpointer(L, x) lua_rawgetp((L), LUA_REGISTRYINDEX, (x)) #define json_regfetch(L, x) json_regfetchpointer(L, json_regpointer(x)) @@ -27,7 +29,6 @@ JSON_REGENT nullmark; // magic value to indicate JSON null value in shadow table } json_reference; - // generate dummy memory addresses that represent Lua objects // via lightuserdata keys and LUA_REGISTRYINDEX: static struct { @@ -751,11 +752,156 @@ return 3; } +#define JSON_TABLETYPE_UNKNOWN 0 +#define JSON_TABLETYPE_OBJECT 1 +#define JSON_TABLETYPE_ARRAY 2 + +static int json_export(lua_State *L) { + lua_Number num; + const char *str; + unsigned char c; + size_t strlen; + size_t pos = 0; + luaL_Buffer buf; + char hexcode[7]; // backslash, character 'u', 4 hex digits, and terminating NULL byte + int luatype; + int tabletype = JSON_TABLETYPE_UNKNOWN; + int needsep = 0; + lua_Integer idx; + lua_settop(L, 1); + switch (lua_type(L, 1)) { + case LUA_TNIL: + lua_pushliteral(L, "null"); + return 1; + case LUA_TNUMBER: + num = lua_tonumber(L, 1); + if (isnan(num)) return luaL_error(L, "JSON export not possible for NaN value"); + if (isinf(num)) return luaL_error(L, "JSON export not possible for infinite numbers"); + lua_tostring(L, 1); + return 1; + case LUA_TBOOLEAN: + if (lua_toboolean(L, 1)) lua_pushliteral(L, "true"); + else lua_pushliteral(L, "false"); + return 1; + case LUA_TSTRING: + str = lua_tolstring(L, 1, &strlen); + luaL_buffinit(L, &buf); + luaL_addchar(&buf, '"'); + while (pos < strlen) { + c = str[pos++]; + if (c == '"') luaL_addstring(&buf, "\\\""); + else if (c == '\\') luaL_addstring(&buf, "\\\\"); + else if (c == 127) luaL_addstring(&buf, "\\u007F"); + else if (c >= 32) luaL_addchar(&buf, c); + else if (c == '\b') luaL_addstring(&buf, "\\b"); + else if (c == '\f') luaL_addstring(&buf, "\\f"); + else if (c == '\n') luaL_addstring(&buf, "\\n"); + else if (c == '\r') luaL_addstring(&buf, "\\r"); + else if (c == '\t') luaL_addstring(&buf, "\\t"); + else if (c == '\v') luaL_addstring(&buf, "\\v"); + else { + sprintf(hexcode, "\\u%04X", c); + luaL_addstring(&buf, hexcode); + } + } + luaL_addchar(&buf, '"'); + luaL_pushresult(&buf); + return 1; + case LUA_TTABLE: + if (lua_getmetatable(L, 1)) { + json_regfetch(L, objectmt); + if (lua_rawequal(L, -2, -1)) { + tabletype = JSON_TABLETYPE_OBJECT; + } else { + json_regfetch(L, arraymt); + if (lua_rawequal(L, -3, -1)) tabletype = JSON_TABLETYPE_ARRAY; + } + } + json_regfetch(L, shadowtbl); + lua_pushvalue(L, 1); + lua_rawget(L, -2); + if (!lua_isnil(L, -1)) lua_replace(L, 1); + lua_settop(L, 1); + if (tabletype == JSON_TABLETYPE_UNKNOWN) { + for (lua_pushnil(L); lua_next(L, 1); lua_pop(L, 1)) { + luatype = lua_type(L, -2); + if (tabletype == JSON_TABLETYPE_UNKNOWN) { + if (luatype == LUA_TSTRING) tabletype = JSON_TABLETYPE_OBJECT; + else if (luatype == LUA_TNUMBER) tabletype = JSON_TABLETYPE_ARRAY; + } else if ( + (tabletype == JSON_TABLETYPE_OBJECT && luatype == LUA_TNUMBER) || + (tabletype == JSON_TABLETYPE_ARRAY && luatype == LUA_TSTRING) + ) { + goto json_export_tabletype_error; + } + } + } + switch (tabletype) { + case JSON_TABLETYPE_OBJECT: + lua_settop(L, 3); + luaL_buffinit(L, &buf); + luaL_addchar(&buf, '{'); + for (lua_pushnil(L); lua_next(L, 1); ) { + if (lua_type(L, -2) == LUA_TSTRING) { + lua_replace(L, 3); + lua_replace(L, 2); + if (needsep) luaL_addchar(&buf, ','); + else needsep = 1; + lua_pushcfunction(L, json_export); + lua_pushvalue(L, 2); + lua_call(L, 1, 1); + luaL_addvalue(&buf); + luaL_addchar(&buf, ':'); + if (json_islightref(L, 3, nullmark)) { + luaL_addstring(&buf, "null"); + } else { + lua_pushcfunction(L, json_export); + lua_pushvalue(L, 3); + lua_call(L, 1, 1); + luaL_addvalue(&buf); + } + lua_pushvalue(L, 2); + } else { + lua_pop(L, 1); + } + } + luaL_addchar(&buf, '}'); + luaL_pushresult(&buf); + return 1; + case JSON_TABLETYPE_ARRAY: + lua_settop(L, 2); + luaL_buffinit(L, &buf); + luaL_addchar(&buf, '['); + for (idx = 1; ; idx++) { + lua_rawgeti(L, 1, idx); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + break; + } + lua_replace(L, 2); + if (needsep) luaL_addchar(&buf, ','); + else needsep = 1; + lua_pushcfunction(L, json_export); + lua_pushvalue(L, 2); + lua_call(L, 1, 1); + luaL_addvalue(&buf); + } + luaL_addchar(&buf, ']'); + luaL_pushresult(&buf); + return 1; + } + json_export_tabletype_error: + return luaL_error(L, "JSON export not possible for ambiguous table (cannot decide whether it is an object or array)"); + } + return luaL_error(L, "JSON export not possible for values of type \"%s\"", lua_typename(L, lua_type(L, 1))); +} + // functions in library module: static const struct luaL_Reg json_module_functions[] = { {"object", json_object}, {"array", json_array}, {"import", json_import}, + {"export", json_export}, {"get", json_get}, {"type", json_type}, {"isnull", json_isnull},