diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 9f92d0d66d..b130b3bcce 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -108,6 +108,8 @@ + + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index 92c61f80ae..cf1592ffa2 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -411,6 +411,13 @@ save yourself some time, copy this for undocumented things: [02 Utilities.lua] Joins a table, splitting each item with delimiter, returning a string of the results. + + Decodes JSON encoded data and returns the result. + + + Returns data as JSON. Empty tables are represented as arrays in the output.
+ minify is optional and defaults to false. If true, all unnecessary whitespace is omitted from the output. +
[02 Colors.lua] diff --git a/src/RageUtil.cpp b/src/RageUtil.cpp index 6a821c45dd..7c1e1fc810 100644 --- a/src/RageUtil.cpp +++ b/src/RageUtil.cpp @@ -9,6 +9,8 @@ #include "LuaManager.h" #include +#include + #include #include #include @@ -2515,7 +2517,205 @@ int LuaFunc_commify(lua_State* L) } LUAFUNC_REGISTER_COMMON(commify); -void luafunc_approach_internal(lua_State* L, int valind, int goalind, int speedind, const float mult); +int LuaFunc_JsonEncode(lua_State* L); +int LuaFunc_JsonEncode(lua_State* L) +{ + int argc = lua_gettop(L); + bool minified = false; + + if (argc < 1 || argc > 2) + { + luaL_error(L, "JsonEncode must be called with one or two arguments"); + } + + if (argc == 2) + { + minified = lua_toboolean(L, 2); + } + + std::function convert = [&L, &convert](int index) -> Json::Value + { + if (lua_isboolean(L, index)) + { + return Json::Value(static_cast(lua_toboolean(L, index))); + } + else if (lua_isnil(L, index)) + { + return Json::Value(Json::nullValue); + } + else if (lua_isnumber(L, index)) + { + double val = lua_tonumber(L, index); + + if (val == static_cast(val)) + { + return Json::Value(static_cast(val)); + } + else if (val == static_cast(val)) + { + return Json::Value(static_cast(val)); + } + return Json::Value(val); + } + else if (lua_isstring(L, index)) + { + size_t len; + const char *s = lua_tolstring(L, index, &len); + + return Json::Value(std::string(s, len)); + } + else if (lua_istable(L, index)) + { + // if the index is relative to the top of the stack, + // then calculate the absolute index, so we have a + // stable reference + if (index < 0) + { + index = lua_gettop(L) + index + 1; + } + + size_t len = lua_objlen(L, index); + + if (len > 0) + { + // array + Json::Value array(Json::arrayValue); + array.resize(len); + + for (size_t i = 0; i < len; i++) + { + lua_rawgeti(L, index, i + 1); + array[i] = convert(-1); + lua_pop(L, 1); + } + + return array; + } + else + { + // object + Json::Value obj(Json::objectValue); + + lua_pushnil(L); + while (lua_next(L, index) != 0) + { + if (!lua_isstring(L, -2)) + { + luaL_error(L, "object keys must be strings"); + } + + size_t keylen; + const char *key = lua_tolstring(L, -2, &keylen); + obj[std::string(key, keylen)] = convert(-1); + lua_pop(L, 1); + } + + if (obj.size() < 1) + { + return Json::Value(Json::arrayValue); + } + return obj; + } + + } + + int tp = lua_type(L, index); + luaL_error(L, "%s is not JSON serializable", lua_typename(L, tp)); + return Json::Value(Json::nullValue); /* not reached */ + }; + + Json::Value root = convert(1); + + std::string data; + if(!minified) + { + Json::StyledWriter writer; + data = writer.write(root); + } + else + { + Json::FastWriter writer; + data = writer.write(root); + } + + lua_pushlstring(L, data.c_str(), data.length()); + return 1; +} +LUAFUNC_REGISTER_COMMON(JsonEncode); + +int LuaFunc_JsonDecode(lua_State* L); +int LuaFunc_JsonDecode(lua_State* L) +{ + int argc = lua_gettop(L); + + if (argc < 1) + { + luaL_error(L, "JsonDecode requires an argument"); + } + + size_t datalen; + const char *data = lua_tolstring(L, 1, &datalen); + + Json::Reader reader; + Json::Value root; + + bool ok = reader.parse(std::string(data, datalen), root, true); + if (!ok) + { + std::string error = reader.getFormatedErrorMessages(); + luaL_error(L, "failed to parse JSON: %s", error.c_str()); + } + + std::function convert = [&L, &convert](const Json::Value& val) + { + if (val.isNull()) + { + lua_pushnil(L); + } + else if (val.isInt() || val.isUInt() || val.isDouble()) + { + lua_pushnumber(L, val.asDouble()); + } + else if (val.isString()) + { + std::string s = val.asString(); + lua_pushlstring(L, s.c_str(), s.length()); + } + else if (val.isBool()) + { + lua_pushboolean(L, val.asBool()); + } + else if (val.isArray()) + { + lua_createtable(L, val.size(), 0); + for (int i = 0; i < val.size(); i++) + { + convert(val[i]); + lua_rawseti(L, -2, i + 1); + } + } + else if (val.isObject()) + { + lua_createtable(L, 0, val.size()); + for (const std::string& member : val.getMemberNames()) + { + lua_pushlstring(L, member.c_str(), member.length()); + convert(val[member]); + lua_rawset(L, -3); + } + } + else + { + luaL_error(L, "failed to parse JSON: invalid type"); + } + }; + + convert(root); + return 1; +} +LUAFUNC_REGISTER_COMMON(JsonDecode); + +void luafunc_approach_internal(lua_State* L, int valind, int goalind, int speedind, const float mult, int process_index); void luafunc_approach_internal(lua_State* L, int valind, int goalind, int speedind, const float mult, int process_index) { #define TONUMBER_NICE(dest, num_name, index) \