# `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`. ```lua 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. ```lua 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`](#utilsnull), **not** Lua `nil` — so a `null` inside an array does not create a gap in the sequence. ```lua 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": ```lua 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`. ```lua 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`. ```lua 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` `nil`s followed by the error. ```lua -- 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 `` rather than looping forever, and nesting is capped at 20 levels. ```lua 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.