Acquis 6 - JSON
The fromjson and tojson global functions provide RFC 8259 compliant JSON parsing and serialization.
Parsing with fromjson
Parse a JSON string into a Lus value:
local data = fromjson('{"name": "Alice", "age": 30}')
assert(data.name == "Alice")
assert(data.age == 30)
All JSON types map to Lus types:
fromjson("null") -- nil
fromjson("true") -- true
fromjson("false") -- false
fromjson("42") -- 42 (integer)
fromjson("3.14") -- 3.14 (number)
fromjson('"hello"') -- "hello"
fromjson("[1, 2, 3]") -- {1, 2, 3}
fromjson('{"a": 1}') -- {a = 1}
Error handling
Invalid JSON throws an error with position information:
local ok, err = catch fromjson('{"invalid}')
assert(not ok)
assert(err:match("position")) -- "JSON parse error at position 10: ..."
Serializing with tojson
Serialize a Lus value to a JSON string:
local json = tojson({name = "Bob", scores = {95, 87, 92}})
-- '{"name":"Bob","scores":[95,87,92]}'
Array vs object detection
Tables with contiguous integer keys 1..n become JSON arrays:
tojson({10, 20, 30}) -- "[10,20,30]"
tojson({a = 1, b = 2}) -- '{"a":1,"b":2}'
tojson({}) -- "[]"
Special values
Non-finite numbers become null:
tojson(math.huge) -- "null"
tojson(-math.huge) -- "null"
tojson(0/0) -- "null" (NaN)
Filtering with tojson
Pass a filter function to transform or omit values:
local data = {
name = "Alice",
password = "secret",
age = 30
}
-- Omit sensitive fields by returning nil
local json = tojson(data, function(key, value)
if key == "password" then return nil end
return value
end)
-- '{"name":"Alice","age":30}'
Transform values:
local data = {price = 100, tax = 0.08}
local json = tojson(data, function(key, value)
if key == "price" then return value * (1 + data.tax) end
if key == "tax" then return nil end
return value
end)
-- '{"price":108}'
Custom serialization with __json
Tables with a __json metamethod use it for serialization:
local Point = {}
Point.__index = Point
Point.__json = function(self)
return {x = self.x, y = self.y}
end
local p = setmetatable({x = 10, y = 20, internal = "data"}, Point)
local json = tojson(p)
-- '{"x":10,"y":20}' (internal field excluded)
The metamethod receives self and returns the value to serialize:
local wrapper = setmetatable({value = 42}, {
__json = function(self) return self.value end
})
tojson(wrapper) -- "42"
Unsupported types
Functions, coroutines, userdata, and enums are skipped during serialization (unless they have __json):
local data = {
name = "test",
callback = function() end, -- skipped
thread = coroutine.create(function() end) -- skipped
}
tojson(data) -- '{"name":"test"}'
Circular reference detection
Circular references throw an error:
local t = {}
t.self = t
local ok = catch tojson(t)
assert(not ok) -- "circular reference detected"
Roundtrip preservation
Values survive serialization and parsing:
local original = {
users = {
{name = "Alice", active = true},
{name = "Bob", active = false}
},
count = 2
}
local roundtrip = fromjson(tojson(original))
assert(roundtrip.count == 2)
assert(roundtrip.users[1].name == "Alice")
assert(roundtrip.users[2].active == false)
Motivation
Lua lacks built-in JSON support. Developers must use external libraries or write parsing code, adding dependencies for what should otherwise be a built-in feature.