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.
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.
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, 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.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:

jq '.image' app.yaml
jq: parse error: Invalid numeric literal at line 1, column 2
yq '.image' app.yaml
ghcr.io/acme/payments:1.4.2

yq 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 Friday
yq -i '.replicas = 5' app.yaml
head -3 app.yaml
# 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:

yq '[.a[]] | add' data.json
Error: 1:10: lexer: invalid input text "add"

yq has no add. The equivalent needs a reduce:

yq '.a[] as $i ireduce(0; . + $i)' data.json
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:

# 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:

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

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.yaml
a
---
b
---
c

Converting to JSON flattens them into a stream that jq reads one at a time:

yq -o=json multi.yaml | jq -r '.name'
a
b
c

Error 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 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

Pros
  • 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

yq

Pros
  • 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

Pick by the job

API responses, logs, cloud CLI output jq. It’s JSON, jq is installed, and it’s the fastest option.
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.
Complex query over a YAML file yq -o=json piped into jq, so you get yq’s reader and jq’s full function set.
Convert between formats yq. XML or TOML to JSON is one flag; jq would need a separate converter.
  • jq Cookbook has the filters both tools mostly share.
  • How to Use jq With kubectl covers querying a cluster, where kubectl -o json | jq and kubectl -o yaml | yq both apply.
  • What Is Nushell? is the other answer to this problem: a shell where structured data never becomes text in the first place.

References