Both tools filter structured data with a similar language, so the question looks like a choice. It usually isn't. jq reads JSON; yq reads YAML and four other formats, and writes them back with your comments intact. Most people end up with both installed. This compares them where they actually differ: file formats, syntax compatibility, in-place editing, and speed. Everything below was run with jq 1.8.2 and yq v4.53.6 (the Go one) on the same files. ## The short answer **Reach for jq** For JSON from APIs, logs, and cloud CLIs. It's faster on large files, it's already installed almost everywhere, and its filter language is the one every other tool imitates. --card-- **Reach for yq** For YAML: Kubernetes manifests, CI pipelines, Docker Compose, Helm values. It edits files in place and keeps comments, which jq cannot do at all. --card-- **Use both** `yq -o=json` converts, then jq does the heavy filtering. This is the practical answer when a YAML file needs jq's full function set. If you install one, install jq: more of the data you meet is JSON, and more documentation assumes it. Add yq the first time you need to change a YAML file without mangling it. ## The contenders **jq** is a JSON processor with a filter language built for streaming transformation. It reads and writes JSON only. **yq** is a YAML processor that borrows jq's syntax. It reads and writes YAML, JSON, XML, TOML, properties, and CSV, and it can modify a file in place. One trap before anything else: **there are two different tools named yq.** The one here is [mikefarah/yq][yq-repo], written in Go, installed by Homebrew as `yq`. The other is [kislyuk/yq][yq-python], a Python wrapper that converts YAML and shells out to jq, so it takes real jq syntax. Check which one you have: ```bash yq --version # yq (https://github.com/mikefarah/yq/) version v4.53.6 ``` Advice written for one is often wrong for the other. Everything below is the Go one. ## Where they differ ### jq cannot read YAML This is the whole reason yq exists: ```bash jq '.image' app.yaml ``` ```text jq: parse error: Invalid numeric literal at line 1, column 2 ``` ```bash yq '.image' app.yaml ``` ```text ghcr.io/acme/payments:1.4.2 ``` ### yq edits in place and keeps your comments Given a file with comments: ```yaml # Deployment settings for the payments API. name: payments-api replicas: 2 # bumped for Black Friday ``` ```bash yq -i '.replicas = 5' app.yaml head -3 app.yaml ``` ```text # Deployment settings for the payments API. name: payments-api replicas: 5 # bumped for Black Friday ``` The value changed, both comments survived, and no temporary file was involved. jq has no `-i`, and a round trip through jq would discard every comment because JSON has none. ### The syntax overlaps, until it doesn't Most filters are identical. `select`, `map`, pipes, and object construction all behave the same way, which is why yq feels familiar. Then you hit a jq builtin yq doesn't have: ```bash yq '[.a[]] | add' data.json ``` ```text Error: 1:10: lexer: invalid input text "add" ``` yq has no `add`. The equivalent needs a reduce: ```bash yq '.a[] as $i ireduce(0; . + $i)' data.json ``` ```text 6 ``` Treat the languages as similar dialects, not drop-in replacements. Filters you copy from a jq answer on Stack Overflow may need translation. ### jq is faster on large JSON Same filter, same 17 MB file of 200,000 records, best of three runs: ```bash # jq: 0.31s jq '[.users[] | select(.dept == "eng" and .active) | .salary] | add' big.json # yq: 1.04s yq '[.users[] | select(.dept == "eng" and .active) | .salary] | .[] as $i ireduce(0; . + $i)' big.json ``` Both printed `5266732729`. jq finished about 3.4 times sooner. At this size the difference is a second; in a loop over hundreds of files it stops being trivia. jq also streams, so it can work through a file larger than memory: ```bash jq --stream -n 'first(inputs | select(length==2 and .[0][2]=="name") | .[1])' big.json ``` yq has no streaming equivalent: it builds the document in memory. ### yq reads formats jq has never heard of ```bash printf '8080' | yq -p=xml -o=json ``` ```json { "config": { "port": "8080" } } ``` It does the same for TOML, properties files, and CSV, and writes any of them back out with `-o=`. For jq, anything that isn't JSON needs a converter first. ### Multi-document YAML Kubernetes manifests separated by `---` are one file with several documents. yq handles that natively: ```bash yq '.name' multi.yaml ``` ```text a --- b --- c ``` Converting to JSON flattens them into a stream that jq reads one at a time: ```bash yq -o=json multi.yaml | jq -r '.name' ``` ```text a b c ``` ### Error messages ```bash echo '{}' | jq '.a |' ``` ```text jq: error: syntax error, unexpected end of file at , line 1, column 4: .a | ^ ``` ```bash echo '{}' | yq '.a |' ``` ```text Error: '|' expects 2 args but there is 1 ``` jq points at the character. yq describes the problem. Neither is bad, though jq's caret is quicker to act on in a long filter. ## Trade-offs **jq** {{< procon >}} - Installed almost everywhere, including most CI images - Fastest on large JSON, and the only one that streams - The reference filter language: most examples and answers use it - Precise syntax errors with a caret under the fault --cons-- - JSON only: no YAML, XML, or TOML - No in-place editing; write to a temp file and move it - Comments cannot survive, because JSON has none {{< /procon >}} **yq** {{< procon >}} - Reads and writes YAML, JSON, XML, TOML, properties, and CSV - Edits files in place and preserves comments and key order - Handles multi-document YAML, the Kubernetes default - Familiar syntax if you already know jq --cons-- - Slower on large JSON, roughly 3x in the test above - Missing jq builtins such as `add`, so filters need translating - Two unrelated tools share the name, and advice for one misleads for the other - Another dependency to install on servers and CI images {{< /procon >}} ## Pick by the job **API responses, logs, cloud CLI output** jq. It's JSON, jq is installed, and it's the fastest option. --card-- **Change a value in a Kubernetes manifest or CI file** yq, with `-i`. It's the only one that edits in place and keeps comments. --card-- **Complex query over a YAML file** `yq -o=json` piped into jq, so you get yq's reader and jq's full function set. --card-- **Convert between formats** yq. XML or TOML to JSON is one flag; jq would need a separate converter. ## Related Content * [jq Cookbook][jq-cookbook] has the filters both tools mostly share. * [How to Use jq With kubectl][jq-kubectl] covers querying a cluster, where `kubectl -o json | jq` and `kubectl -o yaml | yq` both apply. * [What Is Nushell?][nushell] is the other answer to this problem: a shell where structured data never becomes text in the first place. ## References * [mikefarah/yq][yq-repo] and its [documentation][yq-docs], the Go implementation used here. * [kislyuk/yq][yq-python], the Python wrapper that shares the name and takes jq syntax. * [jq manual][jq-manual], for the builtins yq may not have. [jq-cookbook]: https://jeffbailey.us/learn-jq/ [jq-kubectl]: https://jeffbailey.us/how-do-i-use-jq-with-kubectl/ [nushell]: https://jeffbailey.us/what-is-nushell/ [yq-repo]: https://github.com/mikefarah/yq [yq-docs]: https://mikefarah.gitbook.io/yq/ [yq-python]: https://github.com/kislyuk/yq [jq-manual]: https://jqlang.org/manual/