Brodgar client

hafen.json: Parsing and Encoding JSON

Convert between JSON strings and Lua values: parse a string from hafen.store or anywhere else, encode a table to send or save. Pure computation, no I/O, unprotected.

local encoded = hafen.json():encode({ name = "ore", qty = 5, tags = { "raw", "metal" } })
-- encoded == '{"name":"ore","qty":5,"tags":["raw","metal"]}'
local decoded = hafen.json():parse(encoded)
hafen.log():write(decoded.name .. " x" .. decoded.qty)        -- ore x5

Method Returns Permission Description
hafen.json():parse(text) Lua value Unprotected Parse a JSON document.
hafen.json():encode(value) string Unprotected Serialise a Lua value to compact, single-line JSON.

parse(text)

JSON Lua
Object {…} A table with string keys.
Array […] A table, 1-indexed and sequential.
String A string.
true / false A boolean.
Number An integral value fitting a 32-bit integer comes back as one ({"n":5} is 5, not 5.0). Anything wider (2147483648, 1e18) stays a float, the widest integer this Lua holds.
null nil: inside an object the key is absent (parse('{"a":1,"b":null}').b is nil). Inside an array it leaves a hole (parse('[1,null,3]') is {[1]=1, [3]=3}, whose # is undefined). No sentinel stands in for null.
{"gridId": …, "x": …, "y": …} A Position, the one object this parser rebuilds.
Rule Detail
A key written twice is the last one parse('{"a":1,"a":2}').a is 2, silently. Validate a document you did not write if a repeated key would matter.
Malformed input raises JSON: <message> at offset <n>. Malformed means JSON’s own grammar. 01, +5, .5, 5. and 1e are refused. So is a \u escape that is not exactly four hex digits (\uZZZZ, \u-123). Wrap untrusted input in pcall.
Caps Input longer than 8 million characters, or nested deeper than 256 levels, raises. Both caps hold for both directions and are set at launch with -Dhaven.addon.json.maxlen=<chars> and -Dhaven.addon.json.maxdepth=<levels>. A value outside what the client can hold is clamped.
local untrusted = '{"name": "Bob", "level": }'          -- text from the network, a file, another addon
local ok, result = pcall(function() return hafen.json():parse(untrusted) end)
if not ok then hafen.log():write("bad JSON: " .. tostring(result)); return end

encode(value)

Lua JSON
A table whose keys are exactly 1..#t An array. An empty table satisfies the rule and is []. A document needing {} there is one you assemble as a string.
Any other table An object, keys stringified. A number key spells a string name. Any other kind of key raises ({[{}] = 1} has no JSON name). Two keys spelling one name raise ({[1] = "a", ["1"] = "b"}).
An integral number Written without a trailing .0, however large.
A non-integral number Its decimal form.
Booleans, strings With the usual escaping. A lone surrogate is escaped as \uXXXX.
A Position The same three-key object, so a place survives a file or a message. One that is not :durable() raises.
Rule Detail
Strict Raises rather than emitting a placeholder for a value JSON cannot carry. A function, userdata or thread. A reference cycle, or a table past the depth cap. A non-finite number. A key of a kind JSON cannot name, or two keys spelling one name. A document past the size cap. Wrap it in pcall if the value might hold one.
Round trip Encode then parse round-trips scalars, arrays and objects, apart from the null rule and the integer normalisation. Object key order is not preserved.
local value = { name = "Bob", tags = { "kin", "online" } }
local ok, encoded = pcall(function() return hafen.json():encode(value) end)
if not ok then hafen.log():write("cannot encode: " .. tostring(encoded)); return end

What hafen.store writes is not strict. The timer’s write puts the literal string "<cycle>" or "<too deep>" where encode would raise. It logs the path it degraded. A saved table can come back as text. hafen.store():flush() refuses instead, naming the path.


See Also