Desk Clarity

Everyday document & browser help

Jq Key Contains a Hyphen: Pass Dynamic Names as Data With --arg

By ON Lab · Sources checked November 8, 2026

For a JSON key containing a hyphen, use a quoted key lookup such as .["build-version"]. If the key comes from a shell variable, pass it through --arg and index with .[$key]. Inserting the raw variable into the jq program can turn punctuation in the key into program syntax.

Reproduce the boundary with a small object

The public user question behind this guide concerned a dynamic key in a Bash script on a Mac. The unquoted key worked until it contained hyphens. Our local jq 1.7.1 fixture used this smaller object:

{"root":{"parent":{"build-version":"v1",
                   "key.with.dot":"literal",
                   "key":{"with":{"dot":"nested"}}}}}

Save it as sample.json. The literal dotted key and the nested object deliberately coexist: they let you check whether your selector names a key or traverses a path.

jq '.root.parent.build-version' sample.json
jq '.root.parent["build-version"]' sample.json

The first command failed in our fixture because the bare expression did not describe that literal key. The bracket lookup returned the JSON string "v1". Those output quotation marks belong to JSON serialization; they are not part of the key or evidence that selection failed.

Keep the dynamic key out of the jq source

key='build-version'
jq --arg key "$key" '.root.parent[$key]' sample.json

The shell expands the variable only in the argument supplied to --arg. The jq program remains single-quoted and unchanged. jq receives a string value named $key and uses it as an object index. This also handles a literal dot in a key:

key='key.with.dot'
jq --arg key "$key" '.root.parent[$key]' sample.json
jq '.root.parent.key.with.dot' sample.json

Our first result was "literal"; the second was "nested". Treating a dotted string as a single key does not interpret it as a jq path. If your application actually stores structured paths, represent and validate that contract explicitly instead of splitting every key on punctuation.

Do not repair output by stripping quotes with sed

The original question also piped the output through text replacements. Once selection is correct, choose jq’s output mode for the consumer. -r produces raw text for string results, whereas the default retains JSON string escaping. Removing the first and last character of arbitrary JSON output breaks non-string values and can corrupt escaped characters. The separate raw-output guide explains that serialization boundary.

Passing a variable with --arg is not the same as quoting a variable’s name inside the jq expression. "$key" within a single-quoted jq program is a literal string containing the dollar sign, not the jq variable’s value. In contrast, [$key] evaluates the variable as the index.

Check missing fields separately

A valid key expression can still return null when the key is absent. That differs from a syntax or undefined-filter error. If absence is unacceptable, inspect the keys at the intended parent and validate presence before consuming a value:

jq '.root.parent | keys' sample.json
jq --arg key "$key" '.root.parent | has($key)' sample.json

Neither command changes the input. Do not replace all missing values with one default unless that matches the data contract: a legitimate false or explicit null may need different treatment. See the separate missing/false/null check for that decision.

Shell and tool limits

These commands use Bash-style quoting on macOS. PowerShell and Windows cmd have different argument-parsing rules. Keep the jq program concept and adapt the shell quoting using the official manual rather than adding backslashes until one example appears to work. A wrapper can also invoke another jq binary, so record jq --version when results differ.

The official manual and original public answer already provide free solutions. Our added checks distinguish a dynamic literal key, punctuation interpreted as syntax, a dotted path, a missing key and output formatting. No paid JSON editor or API request is needed.

Method: jq 1.7 official manual, an inspected public user question, and five local jq 1.7.1 cases on macOS. The examples were read-only and synthetic. Windows shell quoting, live API data and arbitrary path-expression evaluation were not tested.

Sources