# HG changeset patch # User jbe # Date 1406911945 -7200 # Node ID f417c4b607ed2dae4254799fee4dba4187e233b4 # Parent a8316439035518b6ad75438a09d76bd85af1df41 Added json.set function to set deep values in a JSON document diff -r a83164390355 -r f417c4b607ed libraries/json/json.c --- a/libraries/json/json.c Fri Aug 01 17:11:59 2014 +0200 +++ b/libraries/json/json.c Fri Aug 01 18:52:25 2014 +0200 @@ -596,6 +596,8 @@ static int json_path(lua_State *L, int type_mode) { int stacktop; // stack index of top of stack (after shifting) int idx = 2 + json_path_idxshift; // stack index of current argument to process + // require at least one argument: + luaL_checkany(L, 1); // insert shadowtbl into stack at position 1 (shifting the arguments): json_regfetch(L, shadowtbl); lua_insert(L, 1); @@ -686,6 +688,129 @@ return json_path(L, 1); } +// special Lua stack indicies for json_set function: +#define json_set_shadowtbl_idx 1 +#define json_set_objectmt_idx 2 +#define json_set_arraymt_idx 3 + +// stack offset of arguments to json_set function: +#define json_set_idxshift 3 + +// sets a value (passed as second argument) in a JSON document (passed as first argument) +// using a path (variable number of keys starting at third argument): +static int json_set(lua_State *L) { + int stacktop; // stack index of top of stack (after shifting) + int idx = 3; // stack index of current argument to process + // require at least two arguments: + luaL_checkany(L, 1); + luaL_checkany(L, 2); + // insert shadowtbl into stack at position 1 (shifting the arguments): + json_regfetch(L, shadowtbl); + lua_insert(L, 1); + // insert objectmt into stack at position 2 (shifting the arguments): + json_regfetch(L, objectmt); + lua_insert(L, 2); + // insert arraymt into stack at position 3 (shifting the arguments): + json_regfetch(L, arraymt); + lua_insert(L, 3); + // store stack index of top of stack: + stacktop = lua_gettop(L); + // use nil as initial "parent value": + lua_pushnil(L); + // use first argument as "current value": + lua_pushvalue(L, 1 + json_set_idxshift); + // set all necessary values in path: + for (idx = 3 + json_set_idxshift; idx<=stacktop; idx++) { + // push metatable of "current value" onto stack: + if (!lua_getmetatable(L, -1)) lua_pushnil(L); + // distinguish according to type of path key: + switch (lua_type(L, idx)) { + case LUA_TSTRING: + // if path key is a string, + // check if "current value" is a JSON object (or table without metatable): + if ( + lua_rawequal(L, -1, json_set_objectmt_idx) || + (lua_isnil(L, -1) && lua_type(L, -2) == LUA_TTABLE) + ) { + // if "current value" is acceptable, + // pop metatable and leave "current value" on top of stack: + lua_pop(L, 1); + } else { + // if "current value" is not acceptable: + // pop metatable and "current value": + lua_pop(L, 2); + // throw error if parent element does not exist: + if (lua_isnil(L, -1)) return luaL_error(L, "Root element is not a JSON object"); + // push new JSON object as "current value" onto stack: + lua_newtable(L); + // create and register shadow table: + lua_pushvalue(L, -1); + lua_newtable(L); + lua_rawset(L, json_set_shadowtbl_idx); + // set metatable of JSON object: + lua_pushvalue(L, json_set_objectmt_idx); + lua_setmetatable(L, -2); + // set entry in "parent value": + lua_pushvalue(L, idx-1); + lua_pushvalue(L, -2); + lua_settable(L, -4); + } + break; + case LUA_TNUMBER: + // if path key is a number, + // check if "current value" is a JSON array (or table without metatable): + if ( + lua_rawequal(L, -1, json_set_arraymt_idx) || + (lua_isnil(L, -1) && lua_type(L, -2) == LUA_TTABLE) + ) { + // if "current value" is acceptable, + // pop metatable and leave "current value" on top of stack: + lua_pop(L, 1); + } else { + // if "current value" is not acceptable: + // pop metatable and "current value": + lua_pop(L, 2); + // throw error if parent element does not exist: + if (lua_isnil(L, -1)) return luaL_error(L, "Root element is not a JSON array"); + // push new JSON array as "current value" onto stack: + lua_newtable(L); + // create and register shadow table: + lua_pushvalue(L, -1); + lua_newtable(L); + lua_rawset(L, json_set_shadowtbl_idx); + // set metatable of JSON array: + lua_pushvalue(L, json_set_arraymt_idx); + lua_setmetatable(L, -2); + // set entry in "parent value": + lua_pushvalue(L, idx-1); + lua_pushvalue(L, -2); + lua_settable(L, -4); + } + break; + default: + return luaL_error(L, "Invalid path key of type %s", lua_typename(L, lua_type(L, idx))); + } + // check if last path element is being processed: + if (idx == stacktop) { + // if the last path element is being processed, + // set last path value in "current value" container: + lua_pushvalue(L, idx); + lua_pushvalue(L, 2 + json_set_idxshift); + lua_settable(L, -3); + } else { + // if the processed path element is not the last, + // use old "current value" as new "parent value" + lua_remove(L, -2); + // push new "current value" onto stack by performing a lookup: + lua_pushvalue(L, idx); + lua_gettable(L, -2); + } + } + // return first argument for convenience: + lua_settop(L, 1 + json_set_idxshift); + return 1; +} + // returns the length of a JSON array (or zero for a table without numeric keys): static int json_len(lua_State *L) { // stack shall contain one function argument: @@ -1079,12 +1204,13 @@ // functions in library module: static const struct luaL_Reg json_module_functions[] = { {"object", json_object}, - {"array", json_array}, + {"array", json_array}, {"import", json_import}, {"export", json_export}, {"pretty", json_pretty}, - {"get", json_get}, - {"type", json_type}, + {"get", json_get}, + {"type", json_type}, + {"set", json_set}, {NULL, NULL} };