Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

btv.json

btv.json.encode(value, opts)

btv.json.encode(value[, opts]) -> string. opts.pretty (default false) emits a 2-space-indented, multi-line document for human-readable / diff-friendly files; omit it for the compact one-liner.

Defined in stdlib.lua.

btv.json.decode(str)

btv.json.decode(str) -> value. Parses a JSON document (objects -> string-keyed tables, arrays -> sequences, null -> nil); raises on malformed input.

An EMPTY object decodes to btv.json.empty_object() rather than a bare {}, so decode -> edit -> encode (how a plugin rewrites a JSON file it owns) preserves what the document said: without the mark {"pylsp":{}} came back out as {"pylsp":[]}, since a bare empty table is equally an empty array. It reads like any other empty table (next(t) == nil, fill it in and it is an object with those keys); the one place the mark shows is btv.tbl.deep_extend, where — like every JSON sentinel — it is a VALUE that replaces, so a merged-in {} means {}.

Defined in stdlib.lua.

btv.json.null

btv.json.null: the value that encodes as JSON null.

Lua has no way to say null: storing nil under a key simply removes the key, so { token = nil } encodes as {}, not {"token": null}. To a protocol peer those are different messages — “unset, use your default” versus “explicitly nothing” — and LSP servers do read them differently (snyk_ls expects a null token when there is none). Put this in the table instead:

init_options = { token = btv.env.get("SNYK_TOKEN") or btv.json.null }

Identify it on the way back out with btv.json.is_null(value), not value == btv.json.null: every path that stores it copies it (btv.tbl.deepcopy, and so the btv.tbl.deep_extend behind btv.lsp.config), and a copy carries the mark but is a different table.

btv.json.decode still turns an incoming null into nil (there is nowhere else for it to go in a Lua table), so this is a write-side value.

Defined in stdlib.lua.

btv.json.empty_object()

btv.json.empty_object() -> a table that encodes as JSON {} rather than [].

The other thing Lua can’t say: an empty table is both an empty array and an empty object, and bemtvi’s codec — like neovim’s — has to pick one, so it picks []. A server given [] where its schema says object either rejects the message or silently ignores the field, and the resulting “the server started but does nothing” is exactly the kind of quiet wrongness worth a sentinel.

init_options = { memory = { file_store = btv.json.empty_object() } }

A fresh table each call, and the mark answers “array or object?” rather than standing in for the contents: fill it in afterwards and it is still an object, with everything you put in it (integer keys become the string keys JSON objects take).

btv.json.decode hands back this same shape for an empty object it read, which is what makes decode -> encode round-trip a {} in a file rather than flattening it to [].

Its sibling is the value btv.json.null, which encodes as JSON null — the other thing a Lua table cannot carry, since a nil value simply removes the key.

Defined in stdlib.lua.

btv.json.is_null(value)

btv.json.is_null(value) -> is value the JSON-null sentinel?

The read side of btv.json.null. It answers on a copy of the sentinel as well as on the sentinel itself, which is what makes it the right test: a config’s value has almost always been through btv.tbl.deep_extend by the time anyone reads it back, and that copies. Everything else — including btv.json.empty_object(), a bare {}, and nil — is false.

if btv.json.is_null(cfg.init_options.token) then
  -- the user said "explicitly no token", not "I forgot to set one"
end

Defined in stdlib.lua.