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.