Add support for JSON encoding to redbean

This commit is contained in:
Paul Kulchenko 2021-11-10 20:50:41 -08:00
parent 777d08a839
commit 0793d9cf80
2 changed files with 94 additions and 0 deletions

View file

@ -507,6 +507,14 @@ FUNCTIONS
URIs that do things like embed a PNG file in a web page. See
encodebase64.c.
EncodeJson(value[,options:table]) → json:str
Turns passed Lua value into a JSON string. The following options
can be used:
- useoutput: (bool=false) encodes the result directly to the
output buffer and returns `nil` value.
- numformat: (string="%.14g") sets numeric format to be used.
- maxdepth: (number=64) sets the max number of nested tables.
EncodeLatin1(utf-8:str[,flags:int]) → iso-8859-1:str
Turns UTF-8 into ISO-8859-1 string.

View file

@ -4697,6 +4697,91 @@ static int LuaGetHttpReason(lua_State *L) {
return 1;
}
static int EncodeJsonData(lua_State *L, char **buf, int level, char *numformat) {
size_t idx = -1;
size_t tbllen, buflen;
bool isarray;
int t = lua_type(L, idx);
if (level < 0) return luaL_argerror(L, 1, "too many nested tables");
if (LUA_TSTRING == t) {
appendf(buf, "\"%s\"", gc(EscapeJsStringLiteral(lua_tostring(L, idx), -1, 0)));
} else if (LUA_TNUMBER == t) {
// TODO: what if int64_t isn't representable as double
appendf(buf, numformat, lua_tonumber(L, idx));
} else if (LUA_TBOOLEAN == t) {
appends(buf, lua_toboolean(L, idx) ? "true" : "false");
} else if (LUA_TTABLE == t) {
tbllen = lua_rawlen(L, idx);
// encode tables with numeric indices and empty tables as arrays
isarray = tbllen > 0 || // integer keys present
(lua_pushnil(L), lua_next(L, -2) == 0) || // no non-integer keys
(lua_pop(L, 2), false); // pop kay/value pushed by lua_next
appendd(buf, isarray ? "[" : "{", 1);
if (isarray) {
for (int i = 1; i <= tbllen; i++) {
if (i > 1) appendd(buf, ",", 1);
lua_rawgeti(L, -1, i); // table/-2, value/-1
EncodeJsonData(L, buf, level-1, numformat);
lua_pop(L, 1);
}
} else {
int i = 1;
lua_pushnil(L); // push the first key
while (lua_next(L, -2) != 0) {
if (!lua_isstring(L, -2))
return luaL_argerror(L, 1, "expected number or string as key value");
if (i++ > 1) appendd(buf, ",", 1);
if (lua_type(L, -2) == LUA_TSTRING) {
appendf(buf, "\"%s\":",
gc(EscapeJsStringLiteral(lua_tostring(L, -2), -1, 0)));
} else {
// we'd still prefer to use lua_tostring on a numeric index, but can't
// use it in-place, as it breaks lua_next (changes numeric key to a string)
lua_pushvalue(L, -2); // table/-4, key/-3, value/-2, key/-1
appendf(buf, "\"%s\":", lua_tostring(L, -1)); // use the copy
lua_remove(L, -1); // remove copied key: table/-3, key/-2, value/-1
}
EncodeJsonData(L, buf, level-1, numformat);
lua_pop(L, 1); // table/-2, key/-1
}
// stack: table/-1, as the key was popped by lua_next
}
appendd(buf, isarray ? "]" : "}", 1);
} else if (LUA_TNIL == t)
appendd(buf, "null", 4);
else {
return luaL_argerror(L, 1, "can't serialize value of this type");
}
return 0;
}
static int LuaEncodeJson(lua_State *L) {
int useoutput = false;
int maxdepth = 64;
char *numformat = "%.14g";
char *p = 0;
if (lua_istable(L, 2)) {
lua_settop(L, 2); // discard any extra arguments
lua_getfield(L, 2, "useoutput");
if (lua_isboolean(L, -1)) useoutput = lua_toboolean(L, -1);
lua_getfield(L, 2, "maxdepth");
maxdepth = luaL_optinteger(L, -1, maxdepth);
lua_getfield(L, 2, "numformat");
numformat = luaL_optstring(L, -1, numformat);
}
lua_settop(L, 1); // keep the passed argument on top
EncodeJsonData(L, useoutput ? &outbuf : &p, maxdepth, numformat);
if (useoutput) {
lua_pushnil(L);
} else {
lua_pushstring(L, p);
free(p);
}
return 1;
}
static int LuaEncodeLatin1(lua_State *L) {
int f;
char *p;
@ -5359,6 +5444,7 @@ static const luaL_Reg kLuaFuncs[] = {
{"EncodeBase64", LuaEncodeBase64}, //
{"EncodeLatin1", LuaEncodeLatin1}, //
{"EncodeUrl", LuaEncodeUrl}, //
{"EncodeJson", LuaEncodeJson}, //
{"EscapeFragment", LuaEscapeFragment}, //
{"EscapeHost", LuaEscapeHost}, //
{"EscapeHtml", LuaEscapeHtml}, //