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
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, written in Go, installed by Homebrew as yq. The other is kislyuk/yq, a Python wrapper that converts YAML and shells out to jq, so it takes real jq syntax. Check which one you have:
yq --version
# yq (https://github.com/mikefarah/yq/) version v4.53.6Advice 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:
jq '.image' app.yamljq: parse error: Invalid numeric literal at line 1, column 2yq '.image' app.yamlghcr.io/acme/payments:1.4.2yq edits in place and keeps your comments
Given a file with comments:
# Deployment settings for the payments API.
name: payments-api
replicas: 2 # bumped for Black Fridayyq -i '.replicas = 5' app.yaml
head -3 app.yaml# Deployment settings for the payments API.
name: payments-api
replicas: 5 # bumped for Black FridayThe 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:
yq '[.a[]] | add' data.jsonError: 1:10: lexer: invalid input text "add"yq has no add. The equivalent needs a reduce:
yq '.a[] as $i ireduce(0; . + $i)' data.json6Treat 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:
# 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.jsonBoth 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:
jq --stream -n 'first(inputs | select(length==2 and .[0][2]=="name") | .[1])' big.jsonyq has no streaming equivalent: it builds the document in memory.
yq reads formats jq has never heard of
printf '<config><port>8080</port></config>' | yq -p=xml -o=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:
yq '.name' multi.yamla
---
b
---
cConverting to JSON flattens them into a stream that jq reads one at a time:
yq -o=json multi.yaml | jq -r '.name'a
b
cError messages
echo '{}' | jq '.a |'jq: error: syntax error, unexpected end of file at <top-level>, line 1, column 4:
.a |
^echo '{}' | yq '.a |'Error: '|' expects 2 args but there is 1jq 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
- 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
- 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
yq
- 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
- 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
Pick by the job
-i. It’s the only one that edits in place and keeps comments.yq -o=json piped into jq, so you get yq’s reader and jq’s full function set.Related Content
- jq Cookbook has the filters both tools mostly share.
- How to Use jq With kubectl covers querying a cluster, where
kubectl -o json | jqandkubectl -o yaml | yqboth apply. - What Is Nushell? is the other answer to this problem: a shell where structured data never becomes text in the first place.
References
- mikefarah/yq and its documentation, the Go implementation used here.
- kislyuk/yq, the Python wrapper that shares the name and takes jq syntax.
- jq manual, for the builtins yq may not have.

Comments #