feat(json): JSON for apps through vendored lua-cjson

lua-cjson decodes straight onto the Lua stack, so a response costs its
text plus the table it becomes rather than a document in between, and it
brings the encode half that a C tokenizer would have left to write here.

It is a module rather than a global: a global namespace is a contract a
firmware implements, and nothing about this needs a provider. Registering
into package.preload also puts it ahead of the SD-card searcher, so an
implementation cannot be shadowed, and an app that never requires it
never pays for the module.

Depth is capped at 32 through the module's own knobs rather than by
patching the vendored source. Decoding recurses on the C stack and
upstream defaults to 1000, which assumes a server rather than a FreeRTOS
task.
This commit is contained in:
2026-08-05 16:41:51 -04:00
parent b9f7c9347c
commit 772618ef89
13 changed files with 2560 additions and 4 deletions
+16
View File
@@ -93,6 +93,22 @@ int main() {
bench.http.headers[0].name == "Accept");
expectError(state, "http.get('https://example.test', {maxBytes = 999999})");
// cjson is required rather than global, because the library provides it and no
// firmware implements it. Decoding is lua-cjson's; what is asserted here is
// the wiring and the depth limit the panel's C stack needs.
run(state, "local cjson = require 'cjson'\n"
"assert(rawget(_G, 'cjson') == nil)\n"
"local value = cjson.decode('{\"a\":[1,2.5,true],\"b\":\"x\\\\u00e9\"}')\n"
"assert(math.type(value.a[1]) == 'integer' and value.a[2] == 2.5)\n"
"assert(value.a[3] == true and value.b == 'x\\u{e9}')\n"
"assert(cjson.decode('null') == cjson.null)\n"
"assert(cjson.encode({1, 2, 3}) == '[1,2,3]')\n"
"assert(not pcall(cjson.decode, '{'))\n"
"assert(not pcall(cjson.encode, print))\n"
"local deep = {}; for _ = 1, 40 do deep = {deep} end\n"
"assert(not pcall(cjson.encode, deep))\n"
"assert(not pcall(cjson.decode, string.rep('[', 40)))");
run(state, "assert(wifi.scan()[1].ssid == 'home')\n"
"assert(wifi.isConnected())\n"
"assert(wifi.getLocalIP() == '192.168.1.5')\n"