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.
179 lines
5.9 KiB
179 lines
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`.
|
|
|
|
```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
|
|
`<circular>` 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.
|
|
|