6.7 KiB
json-ls
A JSON/JSONC language server.
Features
- Diagnostics. Parse errors from the error-tolerant parser, plus validation findings from any bound JSON Schema, anchored to the offending node.
- Completion. Schema-aware proposals for property names and for
enum,const, and boolean values. - Hover. Titles, descriptions, types, and constraints from the schema at the cursor.
- Go to definition. On an internal
$ref(#/...), jumps to the node the JSON Pointer names in the same document. On a property key in a schema-bound document, jumps to where the schema declares that property. - Document symbols. An outline of the object and array structure.
- Formatting. Re-emits the syntax tree, preserving comments.
- JSONC. Line and block comments and trailing commas are part of the
syntax, not an error. The document's
languageIddecides how strictly they are reported: documents opened asjsonget a warning for each comment and trailing comma, whilejsoncdocuments (and any other language id) accept them silently. Editors set thelanguageIdfrom their own file associations, so.jsonfiles are held to strict JSON and.jsoncfiles are not, with no server configuration involved.
Building
Requires a recent stable Rust toolchain (edition 2024).
cargo build --release
The binary lands at target/release/json-ls. It communicates over
stdin/stdout, so point your editor's LSP client at it.
Usage
json-ls [OPTIONS]
| Option | Effect |
|---|---|
--cache-dir <DIR> |
Directory for the on-disk schema cache. Defaults to the location below. |
--offline |
Refuse every network fetch, including the json-ls.downloadSchema command. |
--stdio |
Accepted for editor-client compatibility. Has no effect, since stdio is the only transport. |
-h, --help |
Print help. |
-V, --version |
Print version. |
Schemas
A document is bound to a schema through its $schema member:
{
"$schema": "https://example.com/schemas/config.json",
"name": "hello"
}
Relative and file:// URLs resolve against the document. Workspace-local
schema files load automatically. Remote schemas are served from the on-disk
cache when present, and are otherwise offered as a code lens and code action.
Once a schema is loaded, the same lens and action offer to update it, which
re-fetches the schema and overwrites the cached copy. Opening or editing a
document never touches the network: only accepting a download or update
offer does, via the json-ls.downloadSchema command.
Downloads are guarded by default:
- Plaintext
http://URLs are refused. - Hosts must resolve to public addresses. Loopback, private, and link-local ranges are refused, and connections are pinned to the vetted addresses.
- Same-origin redirects are followed silently. A cross-origin redirect pauses the download and asks for confirmation in the editor.
- Redirect chains are capped at 3 hops and bodies at 8 MiB.
Each guard can be relaxed through configuration. Downloaded schemas are
cached under $XDG_CACHE_HOME/json-ls/schemas (or ~/.cache/json-ls/schemas).
Language Server Protocol
- Lifecycle:
initialize(UTF-8 or UTF-16 position encoding negotiation),initialized,shutdown,exit - Text sync:
textDocument/didOpen,didChange(incremental),didClose textDocument/publishDiagnostics(push model)textDocument/completion, snippet-based. A client without snippet support gets no proposals. Item documentation is built on demand throughcompletionItem/resolve.textDocument/hovertextDocument/definition, resolving an internal$refpointer against the document's own tree, or a property key against the bound schema's source (a remote schema resolves to its cached file so the target is openable)textDocument/documentSymboltextDocument/formattingtextDocument/codeLens,textDocument/codeAction, andworkspace/codeLens/refreshfor the schema download offerworkspace/executeCommandwith thejson-ls.downloadSchemacommandworkspace/didChangeConfigurationandworkspace/configuration, as described under Configurationwindow/showMessageandwindow/showMessageRequest
Unknown requests receive a method-not-found error and unknown notifications are ignored. There is no dynamic capability registration and no pull diagnostics.
Configuration
All settings live under a schemaFetch object:
| Setting | Type | Default | Effect |
|---|---|---|---|
allowInsecureHttp |
boolean | false |
Permit plaintext http:// schema URLs. |
allowPrivateNetwork |
boolean | false |
Permit hosts that resolve to loopback, private, or link-local addresses. |
redirects |
string | "prompt" |
Cross-origin redirect handling: "prompt", "same-origin" (refuse), or "always" (follow silently). |
maxRedirects |
number | 3 |
Redirect chain budget. 0 disables redirect following. |
maxBytes |
number | 8388608 |
Maximum schema body size in bytes. 0 means no limit. |
The server reads them from three places:
initializationOptionsat startup.workspace/didChangeConfigurationnotifications, under ajson-lssection (a payload that is the bare settings object also works).- A
workspace/configurationpull for thejson-lssection, used when the client advertises support and a change notification carries no usable payload.
Runtime settings layer over the initializationOptions base, so removing a
setting at runtime reverts it to its startup value.
Editor setup
Neovim (0.11+)
vim.lsp.config['json-ls'] = {
cmd = { 'json-ls' },
filetypes = { 'json', 'jsonc' },
settings = {
['json-ls'] = {
schemaFetch = {
redirects = 'same-origin',
maxRedirects = 1,
},
},
},
}
vim.lsp.enable('json-ls')
License
Apache-2.0, see LICENSE for more information.