json
json is a top-level Lua global. Two methods, both pure host-side functions (no VM round-trip).
json.encode(value) #
Serialise a Lua value to a JSON string. Tables, numbers, booleans, strings, and nil round-trip naturally.
json. -- '{"a":1,"b":"x"}'
json. -- '[10,20,30]'
json. -- 'true'
json. -- 'null'
json. -- '{"a":1}' (b isn't in the table — Lua semantics)
json. -- '[1,null,3]' (array padded to max int key)
Array vs object detection: a table whose keys are positive integers (1..=N, possibly with gaps) encodes as a JSON array of length max(key); gaps emit null. Anything else encodes as an object. Mixed tables ({1, 2, name = "x"}) fall into the object case, with integer keys stringified.
Encoding errors (cycles, function/userdata values, non-string-coercible keys) raise a Lua error.
json.decode(string) #
Parse a JSON string into a Lua value.
local t = json.
print -- 42
local t = json.
print -- "d"
Number precision #
JSON integers in [i64::MIN, i64::MAX] decode to Lua integers exactly — full 64-bit precision, no rounding through f64. Integers above i64::MAX (i.e. u64-only values, up to 0xFFFFFFFFFFFFFFFF) preserve their bit pattern via a wrap-cast: 0xFFFFFFFFFFFFFFFF decodes to -1, exactly matching Lua 5.4's own tonumber("0xFFFFFFFFFFFFFFFF"). Bitwise ops still work correctly on the result (the bits are the bits).
Non-integer JSON (3.14, 1e10) decodes to Lua's float type as expected.
You can drop the "emit as hex string and tonumber() it" workaround that older code used — large unsigned values now round-trip through json.decode directly.
Null handling #
Lua tables can't hold nil as a value — assigning nil to a key removes it. We follow the standard Lua JSON-library convention (dkjson, lua-cjson):
| Input | Lua result | Notes |
|---|---|---|
null | nil | Top-level scalar. |
{"k": null} | A table with no k key. | t.k == nil, next(t) doesn't yield k. |
[1, null, 3] | {1, nil, 3} — array-with-hole. | t[2] == nil; #t is implementation-defined per Lua. |
This is lossy versus the original "key exists but is null": round-tripping decode → encode drops null fields. Tests that need to distinguish null from absent should keep the source string and re-check.
Decoding errors raise a Lua error with the parse position.
Typical use #
Reading config the guest emitted:
local body = vm:
local state = json.
t:
Building a request body for a test client:
local body = json.
vm::
Performance #
encode and decode are pure-host serde_json calls — microseconds for typical config-sized inputs. No VM round-trip, no agent involvement. Safe to call from inside tight test loops.