Lua runner with rich builtin stdlib
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
a/docs/utils.md

5.9 KiB

utils

The utils module is a grab-bag of helpers that the rest of the standard library leans on: JSON encoding and decoding, a null marker for round-tripping JSON and SQL, error-trapping wrappers, and a value pretty-printer. It is always available as the global utils.

local data = utils.fromJSON('{"name":"Alice","age":30}')
print(data.name, data.age)            --> Alice   30

print(utils.toJSON({1, 2, 3}))        --> [1,2,3]
print(utils.dump({a = 1, b = {2, 3}})) --> {a=1, b={2, 3}}

JSON

utils.toJSON(value [, pretty])

Serialize a Lua value to a JSON string. Pass pretty = true for indented, multi-line output; the default is compact.

utils.toJSON({ name = "test", count = 5 })       --> {"count":5,"name":"test"}
utils.toJSON({ name = "test" }, true)            --> pretty-printed over several lines

How values are converted:

Lua JSON
nil null
utils.NULL null
boolean true / false
integer number
float number
string string
table (sequence) array
table (other) object

A table is encoded as a JSON array when its keys are exactly the integers 1..#t with no gaps, and as a JSON object otherwise. Object keys must be strings or integers (an integer key becomes its decimal string); any other key type is an error.

These cases raise an error rather than producing invalid JSON:

  • NaN or infinity (JSON has no representation for them),
  • a value that has no JSON form (function, coroutine, userdata other than utils.NULL),
  • nesting deeper than 64 levels.

utils.fromJSON(string)

Parse a JSON string into a Lua value. The inverse of toJSON:

JSON Lua
null utils.NULL
true / false boolean
integer number integer
fractional number float
string string
array sequence table
object table

Invalid JSON raises an error. Note that JSON null decodes to utils.NULL, not Lua nil — so a null inside an array does not create a gap in the sequence.

local arr = utils.fromJSON('[1, null, 3]')
print(#arr)                  --> 3
print(utils.isNull(arr[2]))  --> true

null

JSON and SQL both have a null/NULL value distinct from "absent". Lua's nil cannot fill that role: a table cannot store nil (assigning nil deletes the key), so a literal nil can never survive inside a table to reach a JSON array slot or a SQL parameter. utils.NULL is a sentinel that can.

utils.NULL

A unique marker standing for an explicit null. Store it in a table where you mean "null, not missing":

http.postJSON(url, { nickname = utils.NULL })  -- sends  {"nickname":null}
con:execute("UPDATE u SET nickname = ?", { utils.NULL })  -- binds SQL NULL

It is the value fromJSON produces for JSON null, the value toJSON and the http/sqlite modules turn back into null/NULL. Comparable by identity, so value == utils.NULL works, but prefer utils.isNull for clarity.

utils.isNull(value)

Return true if value is utils.NULL. Everything else, including Lua nil, returns false.

utils.isNull(utils.NULL)  --> true
utils.isNull(nil)         --> false
utils.isNull(0)           --> false

Error handling

utils.try(fn [, ...])

Call fn and trap any error, returning (result, nil) on success or (nil, err) on failure — a more ergonomic pcall for the common single-return-value case. Extra arguments are forwarded to fn.

local data, err = utils.try(function()
    return utils.fromJSON(input)
end)
if not data then
    log.error("bad JSON: " .. tostring(err))
    return
end
-- use data

If fn errors with a nil value, err is the string "unspecified error", so a falsy first return reliably means failure.

utils.tryn(n, fn [, ...])

Like try, but for a function that returns several values. n is how many values fn returns on success. On success it returns those n values followed by a trailing nil (the error slot); on failure it returns n nils followed by the error.

-- fn returns two values
local x, y, err = utils.tryn(2, function()
    return parsePoint(s)   -- returns x, y
end)
if err then
    log.error("parse failed: " .. tostring(err))
else
    print(x, y)
end

n must be an integer between 0 and 64. Use try when fn returns at most one value; reach for tryn only when you genuinely need to capture several.

Debugging

utils.dump(value)

Render any Lua value as a readable string — handy for logging and quick inspection. Strings are quoted, sequences print as { a, b, c }, other tables as { key = value, ... }, and utils.NULL prints as null. Cycles are shown as <circular> rather than looping forever, and nesting is capped at 20 levels.

print(utils.dump({ id = 1, tags = {"a", "b"}, parent = utils.NULL }))
--> { id = 1, parent = null, tags = { "a", "b" } }

This is for human eyes, not machine parsing — use toJSON when you need output you can read back.

Notes

  • utils is always loaded. No require; the global is present in every script.
  • NULL bridges three worlds. The same marker represents null for toJSON, fromJSON, the http JSON helpers, and SQL NULL binding in sqlite, so a null value can round-trip through any of them.
  • toJSON/fromJSON are not symmetric for nil. nil encodes to null, but null decodes to utils.NULL. This is deliberate, so decoding never silently drops array elements.