Json Module
JSON encode/decode library for Luau. Encodes Lua values to JSON
strings and decodes JSON strings back to Lua values. Used for
communication with the Rust side of the engine, the VFS read/write
bridge, and any wire-format that needs JSON.
Pure Luau, no engine dependencies. Compact and pretty-printed
encoders, plus a hand-rolled decoder that streams the input by
position so it works under WASM as well as native.
Consumers:
local Json = require("modules.json")
local s = Json.encode({ type = "button", text = "Click Me" })
-- → '{"text":"Click Me","type":"button"}'
local v = Json.decode(s)
escapeString(s: ?) → void
Internal: escape a Luau string for embedding inside a JSON string literal
(RFC 8259). Escapes `"`, `\`, and EVERY control character U+0000–U+001F:
the five with shorthands use them, the rest become \u00XX. Bytes >= 0x20 —
including multi-byte UTF-8 sequences — pass through unchanged (JSON strings
are UTF-8; escaping a continuation byte would corrupt the character). The
`\1-\31` range is matched by explicit byte value (locale-independent, unlike
`%c` which can misclassify UTF-8 continuation bytes under some locales); NUL
is matched via `%z`.
The previous escaper only covered the shorthand set above and left TAB and
the other C0 controls raw, producing JSON the ZeroMind bridge / IDE plugin
could not parse — wedging every affected tool call (gh#3835).
codepointToUtf8(cp: ?) → void
Internal: encode a Unicode code point as UTF-8 bytes (1–4 bytes). Decimal
arithmetic keeps Luau/WASM parity with the rest of the decoder. Used when
decoding \uXXXX escapes, including astral-plane code points reconstructed
from a UTF-16 surrogate pair.
encodeScalar(v: ?, valueType: ?) → void
Internal: the JSON text for a scalar, or nil when the value is a table and
has to go back through `Json.encode`.
The encode loops call this instead of recursing per element. A recursive
`Json.encode` call costs ~1.9µs, and on a numeric payload that call alone is
74% of the encode: a 16,057-number array spends 31ms of its 41.9ms there,
against 9.0ms actually formatting the numbers. Handling scalars where they
are iterated leaves only tables recursing.
| arg | type | description |
|---|
| v | ? | |
| valueType | ? | |
isArray(t: ?) → void
Internal: predicate — does this table look like a JSON array?
(sequential integer keys starting at 1).
encode(value: any, indent: string?, currentIndent: string?) → string
Encode a Lua value to a compact JSON string. Functions and unknown types serialise to `null`; NaN/Inf serialise to `null` (JSON has no representation for them).
| arg | type | description |
|---|
| value | any | Any Lua value (nil, boolean, number, string, table). |
| indent | string? | Optional indent string. Reserved — the compact encoder ignores it; use `encodePretty` for indented output. |
| currentIndent | string? | Optional current-depth indent string. Reserved. |
examples
local s = Json.encode({ type = "button", text = "Click Me" })encodePretty(value: any, indentStr: string?) → string
Encode a Lua value to a pretty-printed JSON string. Indents nested values and sorts object keys for diff-friendly output.
| arg | type | description |
|---|
| value | any | Any Lua value. |
| indentStr | string? | Indent string per level (default `" "`). |
examples
local s = Json.encodePretty({ a = 1, b = { c = 2 } })encodePrettyInner(val: ?, depth: ?) → void
| arg | type | description |
|---|
| val | ? | |
| depth | ? | |
encodeArgs(...: any) → string
Encode a list of arguments as a JSON array string. Useful when forwarding varargs to a JSON-based bridge.
| arg | type | description |
|---|
| ... | any | Any number of values to encode. |
examples
local s = Json.encodeArgs("foo", 1, true) -- '["foo",1,true]'decode_scanWhitespace(str: ?, pos: ?) → void
Internal: skip whitespace and return the position of the next non-whitespace
character. Jumps to the first non-whitespace byte in a single C-side
`string.find` scan rather than testing one character at a time — the whole
decoder follows this rule (scan runs in C, never loop per character in Luau,
which is where the naive scanner spent its time).
| arg | type | description |
|---|
| str | ? | |
| pos | ? | |
decode_scanString(str: ?, pos: ?) → void
Internal: scan a quoted string at `pos`; returns (value, nextPos) or (nil, err).
Copies whole unescaped runs with ONE `string.sub` per run — locating the next
`"` or `\` with a single C-side `string.find` — instead of appending one
character at a time (`result = result .. c`), which reallocates the whole
result string every character and is O(n^2). Only escape sequences are handled
character-by-character, and they are rare, so an escape-free string (the common
case) is a single find + a single sub. Parts accumulate in a table and join
once via `table.concat` when escapes are present.
| arg | type | description |
|---|
| str | ? | |
| pos | ? | |
decode_scanNumber(str: ?, pos: ?) → void
Internal: scan a JSON number at `pos`; returns (number, nextPos) or (nil, err).
Grabs the maximal numeric token in one C-side `string.find` — the character
class stops at any JSON delimiter (`,` `}` `]` whitespace) — then lets
`tonumber` judge validity, matching the previous scanner which accepted the
same run and delegated to `tonumber`. One `string.sub`, no per-digit loop.
| arg | type | description |
|---|
| str | ? | |
| pos | ? | |
decode_scanObject(str: ?, pos: ?) → void
Internal: scan a JSON object at `pos`; returns (table, nextPos) or (nil, err).
Structural bytes (`{` `}` `:` `,`) are compared via `string.byte` — a plain
byte read, no 1-character `string.sub` allocation per delimiter.
| arg | type | description |
|---|
| str | ? | |
| pos | ? | |
decode_scanArray(str: ?, pos: ?) → void
Internal: scan a JSON array at `pos`; returns (table, nextPos) or (nil, err).
Structural bytes (`[` `]` `,`) compared via `string.byte`, no per-delimiter
`string.sub` allocation.
| arg | type | description |
|---|
| str | ? | |
| pos | ? | |
decode_scanLiteral(str: ?, pos: ?) → void
Internal: scan one of the JSON literals (`true`, `false`, `null`).
| arg | type | description |
|---|
| str | ? | |
| pos | ? | |
decode_scanValue(str: ?, pos: ?) → void
Internal: top-level value scanner — dispatches on the leading byte.
| arg | type | description |
|---|
| str | ? | |
| pos | ? | |
decode(str: string) → any
Decode a JSON string to a Lua value. Returns the decoded value, or `nil` + error message on failure.
| arg | type | description |
|---|
| str | string | The JSON string to decode. |
examples
local v = Json.decode('{"a":1,"b":"hi"}') -- → { a = 1, b = "hi" }local v, err = Json.decode("bad") -- → nil, "Invalid literal..."