jq is a command-line tool designed to read, filter and transform data
in JSON (in French) format. In a world
where APIs (in French) and configuration files rely
massively on this format, jq becomes essential for any system administrator
or DevOps developer.
Its main advantage: complex queries on nested data structures, straight from a terminal, without writing a Python script (in French) or JavaScript.
What you will learn
- Read and format JSON from a file or a stream (stdin)
- Extract precise fields with the filters
.[],.keyand indexes - Filter and transform with
select,mapanddel - Combine conditions (
and/or,contains) and sort (sort_by) - Fit jq into scripts and CI/CD pipelines
Install jq
jq is in the repositories of every distribution. For the latest version (jq 1.8.2), which fixes several security flaws, fetch the verified official binary.
# From the distribution repositories (often jq 1.7.x)sudo apt install -y jq # Debian / Ubuntusudo dnf install -y jq # Fedora / RHEL / AlmaLinuxbrew install jq # macOS
# Latest version as a binary (jq 1.8.2), verified through its SLSA attestationJQ_VERSION=1.8.2sudo curl -sSfL "https://github.com/jqlang/jq/releases/download/jq-${JQ_VERSION}/jq-linux-amd64" -o /usr/local/bin/jqgh attestation verify /usr/local/bin/jq --repo jqlang/jqsudo chmod +x /usr/local/bin/jqjq --version # jq-1.8.2Version 1.8.2 fixes more than a dozen CVEs (buffer and stack overflows triggered by malicious JSON input). On a script that processes untrusted JSON, prefer it to the 1.7.x versions still shipped by some repositories.
The API used in this guide
To illustrate each jq command concretely, we use the public JSONPlaceholder API. It is a free API that simulates the features of a REST service, ideal to practise without having to set up a backend.
Why this API?
- It returns realistic JSON data: users, posts, comments, etc.
- It is accessible without authentication.
- The responses are consistently structured, perfect to learn how to handle JSON objects and arrays.
We will rely in particular on these two endpoints:
https://jsonplaceholder.typicode.com/users: to handle a list of users.https://jsonplaceholder.typicode.com/posts: to work with posts and filter content.
Every request is made with curl and
processed with jq, straight on the command line. This way, you can
reproduce each command in your own terminal, without a complex installation.
Basic syntax
jq is mainly used in two ways: by reading a JSON file or by processing a data stream on standard input (stdin). Both methods are interchangeable depending on the context.
Read a JSON file
The classic syntax passes a JSON file as an argument:
jq '.' fichier.jsonHere, the . filter means "print everything". This command prints the JSON
content with a clean indentation, which makes it easier to read.
Read from standard input (stdin)
jq can also read directly from a stream, such as the one produced by a
curl command:
curl -s https://jsonplaceholder.typicode.com/users | jq '.'
[ { "id": 1, "name": "Leanne Graham", "username": "Bret", "email": "Sincere@april.biz", "address": { "street": "Kulas Light", "suite": "Apt. 556", "city": "Gwenborough", "zipcode": "92998-3874", "geo": { "lat": "-37.3159", "lng": "81.1496" } }, "phone": "1-770-736-8031 x56442", "website": "hildegard.org", "company": { "name": "Romaguera-Crona", "catchPhrase": "Multi-layered client-server neural-net", "bs": "harness real-time e-markets" } }, ...]In this case, jq receives the JSON data through the | pipe, interprets
it and formats it on screen.
Extracting specific data
With jq, you can extract precisely the elements you are interested in from a JSON structure. Here is how to do it from the JSONPlaceholder API, which returns a list of users.
Extract a simple key
Let us print every user's name with the .[].name filter:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[].name'"Leanne Graham""Ervin Howell""Clementine Bauch""Patricia Lebsack""Chelsey Dietrich""Mrs. Dennis Schulist""Kurtis Weissnat""Nicholas Runolfsdottir V""Glenna Reichert""Clementina DuBuque"The .[].name filter means: "in each element of the main array, print the
name key".
Extract several keys
For each user, you can extract several pieces of information at once by building a new object:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | {id: .id, nom: .name, email: .email}'{ "id": 1, "nom": "Leanne Graham", "email": "Sincere@april.biz"}{ "id": 2, "nom": "Ervin Howell", "email": "Shanna@melissa.tv"}{ "id": 3, "nom": "Clementine Bauch", "email": "Nathan@yesenia.net"}{ "id": 4, "nom": "Patricia Lebsack", "email": "Julianne.OConner@kory.org"}{ "id": 5, "nom": "Chelsey Dietrich", "email": "Lucio_Hettinger@annie.ca"}{ "id": 6, "nom": "Mrs. Dennis Schulist", "email": "Karley_Dach@jasper.info"}{ "id": 7, "nom": "Kurtis Weissnat", "email": "Telly.Hoeger@billy.biz"}{ "id": 8, "nom": "Nicholas Runolfsdottir V", "email": "Sherwood@rosamond.me"}{ "id": 9, "nom": "Glenna Reichert", "email": "Chaim_McDermott@dana.io"}{ "id": 10, "nom": "Clementina DuBuque", "email": "Rey.Padberg@karina.biz"}Here is what the command does in detail:
-
curl -s https://jsonplaceholder.typicode.com/usersThis command sends an HTTP GET request to the JSONPlaceholder API. The-sflag means "silent", socurldoes not show the progress bar. The result is a JSON array containing 10 users. -
jq '.[]'This filter tells jq: "Walk through each element of the array". It means that jq processes each user one by one. -
| {id: .id, nom: .name, email: .email}For each user, jq creates a new JSON object made only of three keys:id→ the value of the.idfield (user identifier)nom→ the value of the.namefieldemail→ the value of the.emailfield
This kind of transformation is perfect to simplify the display, extract targeted data, or prepare an export to another tool.
Extract a nested key
Let us print the name and the city of each user, a nested field:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | {nom: .name, ville: .address.city}'{ "nom": "Leanne Graham", "ville": "Gwenborough"}{ "nom": "Ervin Howell", "ville": "Wisokyburgh"}{ "nom": "Clementine Bauch", "ville": "McKenziehaven"}{ "nom": "Patricia Lebsack", "ville": "South Elvis"}{ "nom": "Chelsey Dietrich", "ville": "Roscoeview"}{ "nom": "Mrs. Dennis Schulist", "ville": "South Christy"}{ "nom": "Kurtis Weissnat", "ville": "Howemouth"}{ "nom": "Nicholas Runolfsdottir V", "ville": "Aliyaview"}{ "nom": "Glenna Reichert", "ville": "Bartholomebury"}{ "nom": "Clementina DuBuque", "ville": "Lebsackbury"}.address.city gives access to a property nested in an "address" object.
Extract an element by its index
To get the data of the 3rd user, use an index between brackets:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[2]'
{ "id": 3, "name": "Clementine Bauch", "username": "Samantha", "email": "Nathan@yesenia.net", "address": { "street": "Douglas Extension", "suite": "Suite 847", "city": "McKenziehaven", "zipcode": "59590-4157", "geo": { "lat": "-68.6102", "lng": "-47.0653" } }, "phone": "1-463-123-4447", "website": "ramiro.info", "company": { "name": "Romaguera-Jacobson", "catchPhrase": "Face to face bifurcated interface", "bs": "e-enable strategic applications" }}JSON arrays start at index 0. .[2] therefore targets the 3rd element.
Access a field of a specific element
Extract only the email of the 5th user by combining index and key:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[4].email'You can combine indexes with key names to navigate the structure.
Tip for field names with dashes
If a field contains a special character, use double quotes after the .
operator, otherwise jq reads the dash as a subtraction:
jq '."nom-du-champ"' fichier.jsonEven if this case does not occur in JSONPlaceholder, it is good to know for other APIs.
Filters and transformations
The https://jsonplaceholder.typicode.com/users endpoint returns a list of 10
users, each with fields such as id, name, email, and an address object
containing city, zipcode, etc. Here is how to transform this data with
jq.
Select some users with select
Let us filter the users living in the city of "McKenziehaven" with
select, which keeps the matching objects:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | select(.address.city == "McKenziehaven")'The select(.address.city == "McKenziehaven") filter only prints the users
matching this criterion.
Delete a field with del
Let us remove the full addresses of the users with del, which drops a
key:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | del(.address)'This command deletes the address field from each user object.
Rename and reorder fields
Let us build a simplified view with custom field names:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | {identifiant: .id, nom: .name, ville: .address.city}'Here, we create a new object with renamed keys and an extracted sub-field.
Add a static field
Let us add a statut key to every user with the + operator, which
merges two objects:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | . + {statut: "actif"}'Each user object now contains a statut key with the value "actif".
Count the number of users
To get the total number of entries in the JSON response, use length:
curl -s https://jsonplaceholder.typicode.com/users | jq 'length'The length filter counts the objects of the array.
Extract an array of values with map
Let us fetch a list of the emails with map, which returns an
array:
curl -s https://jsonplaceholder.typicode.com/users | jq 'map(.email)'This filter returns an array containing only the email addresses of the users.
Advanced use of jq
Once the basics are mastered, jq goes much further thanks to advanced
filters, logical combinations and built-in functions. Here are a few
powerful uses, still with the https://jsonplaceholder.typicode.com/users
endpoint.
Combine several conditions with and / or
Let us filter the users whose ID is greater than 5 and who live in the city of "Howemouth":
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | select(.id > 5 and .address.city == "Howemouth")'With and, jq evaluates several conditions on the same object. You can
also use or:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | select(.id < 3 or .address.city == "Lebsackbury")'Apply a function to a string or a number
Get every name in uppercase with ascii_upcase, a string function:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | {nom_majuscule: (.name | ascii_upcase)}'Here, ascii_upcase converts the value of .name to uppercase.
Filter on a contained value (with contains)
Find the users whose name contains "Glen" with contains inside a
select:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | select(.name | contains("Glen"))'contains is case-sensitive. To ignore case, combine it with
ascii_downcase, which normalises the string first:
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | select((.name | ascii_downcase) | contains("glen"))'Sort the data (local sort)
You can sort the users by name with sort_by, applied to the whole
array:
curl -s https://jsonplaceholder.typicode.com/users | jq 'sort_by(.name)[] | {id, nom: .name}'sort_by(.name) sorts the array in alphabetical order on .name.
List of built-in functions
jq offers a rich collection of built-in functions to manipulate JSON data on the command line. These functions cover operations on strings, arrays, objects, numbers, types, and much more. Here is an overview of the main categories of functions available:
String functions
These functions work on text values, the most frequent case in an API response.
length: returns the length of a string.ascii_upcase/ascii_downcase: converts a string to uppercase or lowercase.split/join: splits a string into an array or joins an array into a string.startswith/endswith: checks whether a string starts or ends with a given substring.contains: checks whether a string contains a specific substring.
Array functions
These functions take an array as input, typically the list returned by an endpoint.
map: applies a transformation to each element of an array.select: filters the elements of an array on a condition.add: adds up the elements of a numeric array.sort/sort_by: sorts the elements of an array.unique/unique_by: removes duplicates from an array.reverse: reverses the order of the elements of an array.
Object functions
These functions work on the keys of an object rather than on its values.
keys: returns the keys of an object.has: checks for the presence of a key in an object.del: deletes a key from an object.to_entries/from_entries: converts an object into an array of key/value pairs and back.with_entries: transforms the key/value pairs of an object
Mathematical functions
These functions apply to numbers, or turn a string into one.
floor/ceil/round: rounds a number down, up or to the nearest integer.sqrt: computes the square root of a number.tonumber: converts a string into a number.
Type and conversion functions
These functions tell or change the JSON type of a value.
type: returns the JSON type of a value (string,number,object, etc.).tostring: converts a value into a string.isfinite/isnan/isinfinite: checks properties of a number.
Traversal and recursion functions
These functions walk a structure at any depth, without knowing its shape.
recurse: walks a JSON structure recursively.walk: applies a function to each element of a JSON structure.paths/getpath/setpath: works with the paths in a JSON structure.
Control-flow functions
These constructs drive the flow of a filter: condition, error, empty output.
if/then/else/end: conditional structure.try/catch: error handling.empty: produces no output, useful to filter elements out.
Miscellaneous functions
These functions generate values or read additional inputs.
range: generates a sequence of numbers.error: raises a custom error.input/inputs: reads additional inputs.
For a complete and detailed list of jq's built-in functions, check the official documentation: https://jqlang.org/manual/
Here are some new concrete examples of advanced built-in functions of
jq, still from the https://jsonplaceholder.typicode.com/users endpoint, and
not covered above:
A few advanced examples
Each example below is followed by what it produces, on the first user or on the whole list.
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | {id_texte: (.id | tostring)}'→ Converts the numeric id field into text, useful for concatenations or
specific formats.
curl -s https://jsonplaceholder.typicode.com/users | jq '.[] | {nom: (.name | type), adresse: (.address | type)}'→ Prints the data types (string, object, etc.) of given fields.
curl -s https://jsonplaceholder.typicode.com/users | jq '.[0] | with_entries(.key |= ascii_upcase)'→ Puts every key of the first user in uppercase.
curl -s https://jsonplaceholder.typicode.com/users | jq '.[0] | to_entries | map(.key += "_mod") | from_entries'→ Renames every key of the first user by adding a _mod suffix.
curl -s https://jsonplaceholder.typicode.com/users | jq '.[0] | recurse(.[]? | select(type == "object"))'→ Explores every nested object of the first user recursively.
curl -s https://jsonplaceholder.typicode.com/users | jq '.[0] | walk(if type == "string" then ascii_downcase else . end)'→ Puts every string in lowercase in the first user, whatever its depth.
Key points
- jq reads JSON from a file or a stream (
curl ... | jq '.') - Extract:
.[],.key, nested field.address.city, index.[2] - Filter:
select(...),contains,and/orconditions - Transform:
map,del, objects{...},sort_bysort - jq is compact, fast and dependency-free: ideal in scripts and CI/CD pipelines
Knowledge check
Check that the essentials of this guide are acquired. The questions only cover what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: sed: replace and delete text
A new full quiz with random questions
Work again only on the questions you missed
Useful links
The official manual lists every filter, and the online playground lets you test a filter without installing anything:
- Official jq documentation
- jqlang: use jq online to test your commands.
FAQ: frequently asked questions
The answers below go further than the page, with DevOps examples on Kubernetes, AWS and Terraform outputs.
Definition
jq is a command-line JSON processor that filters, transforms and manipulates JSON data with a declarative syntax.| Feature | Description |
|---|---|
| Type | JSON parser/transformer |
| Syntax | Filter language (pipes) |
| Input | JSON (file, stdin, API) |
| Output | JSON, text, CSV |
| Operations | Select, map, reduce, sort, group |
| Performance | Streaming (large files) |
Main uses
# 1. Pretty-print JSON
curl -s https://api.github.com/users/octocat | jq '.'
# 2. Extract a field
echo '{"name":"Alice","age":30}' | jq '.name'
# "Alice"
# 3. Filter an array
echo '[{"id":1},{"id":2}]' | jq '.[0]'
# {"id":1}
# 4. Transform a structure
jq '.users[] | {name: .username, email}' data.json
DevOps cases
- APIs: parse Kubernetes, Terraform, AWS responses
- CI/CD: extract artifacts, manifests
- Configs: manipulate package.json, manifests
- Logs: filter JSON logs (CloudWatch, ELK)
- Secrets: extract Vault, AWS tokens
Basic reading
# Print everything (pretty-print)
jq '.' fichier.json
# From stdin
cat fichier.json | jq '.'
# From an API
curl -s https://api.example.com/data | jq '.'
The . filter (identity)
# Compact JSON → indented
echo '{"a":1,"b":2}' | jq '.'
# {
# "a": 1,
# "b": 2
# }
# JSON validation
jq '.' data.json > /dev/null && echo "JSON valide"
Reading options
| Option | Effect | Example |
|---|---|---|
-r |
Raw output (no quotes) | jq -r '.name' data.json |
-c |
Compact (1 line) | jq -c '.' data.json |
-M |
Monochrome (no colours) | jq -M '.' data.json |
-s |
Slurp (global array) | jq -s '.' *.json |
-e |
Exit code based on the result | jq -e '.error' data.json |
Practical examples
# Kubernetes pods (excerpt)
kubectl get pods -o json | jq '.items[].metadata.name'
# package.json version
jq -r '.version' package.json
# 1.2.3
# AWS EC2 instance IDs
aws ec2 describe-instances | jq -r '.Reservations[].Instances[].InstanceId'
Tip: -r (raw) removes the quotes for use in scripts.curl + jq pipeline
# Standard pattern
curl -s URL | jq '.'
# Extract fields
curl -s https://api.github.com/users/octocat | jq '.name, .bio'
# With authentication
curl -s -H "Authorization: Bearer TOKEN" \
https://api.example.com/data | jq '.results[]'
Examples with common APIs
# GitHub repos
curl -s https://api.github.com/users/microsoft/repos | \
jq '.[] | {name, stars: .stargazers_count}'
# JSONPlaceholder
curl -s https://jsonplaceholder.typicode.com/users | \
jq '.[].email'
# Kubernetes API
curl -sk -H "Authorization: Bearer $TOKEN" \
https://kubernetes:6443/api/v1/pods | \
jq '.items[] | select(.status.phase=="Running") | .metadata.name'
Filtering and transformation
# Terraform Cloud workspaces
curl -s -H "Authorization: Bearer $TF_TOKEN" \
https://app.terraform.io/api/v2/organizations/my-org/workspaces | \
jq -r '.data[] | select(.attributes.locked == false) | .attributes.name'
# AWS API (through the CLI)
aws ec2 describe-instances | \
jq -r '.Reservations[].Instances[] |
select(.State.Name=="running") |
"\(.InstanceId): \(.PrivateIpAddress)"'
Error handling
# Check for an API error
RESPONSE=$(curl -s https://api.com/endpoint)
ERROR=$(echo "$RESPONSE" | jq -r '.error // empty')
if [ -n "$ERROR" ]; then
echo "API Error: $ERROR"
exit 1
fi
echo "$RESPONSE" | jq '.data'
DevOps pattern: curl -sf URL | jq '.field' (fails on an HTTP error).Dot notation
# Sample JSON
echo '{
"user": {
"address": {
"city": "Paris",
"zip": "75001"
}
}
}' | jq '.user.address.city'
# "Paris"
# Without quotes (-r)
jq -r '.user.address.city' data.json
# Paris
Navigating arrays
# Array index
echo '[{"name":"Alice"},{"name":"Bob"}]' | jq '.[0].name'
# "Alice"
# Every element
echo '{"users":[{"id":1},{"id":2}]}' | jq '.users[].id'
# 1
# 2
# Range
jq '.items[2:5]' data.json # Indexes 2,3,4
Optional keys
# With ? (no error if missing)
jq '.user.email?' data.json
# Default value
jq '.config.timeout // 30' data.json # 30 if missing
# Alternative operator
jq '.primary_email // .secondary_email' data.json
Complex cases
# Kubernetes pod IP
kubectl get pod nginx -o json | \
jq -r '.status.podIP'
# AWS EC2 tags
aws ec2 describe-instances | \
jq -r '.Reservations[].Instances[].Tags[] |
select(.Key=="Name") | .Value'
# Terraform state
terraform show -json | \
jq -r '.values.root_module.resources[] |
select(.type=="aws_instance") |
.values.private_ip'
Handling null
# Ignore null
jq '.users[] | select(.email != null) | .email' data.json
# Replace null
jq '.items[] | .description // "N/A"' data.json
The select() function
# Filter on a condition
jq '.users[] | select(.age > 18)' data.json
# Multiple conditions (AND)
jq '.[] | select(.active == true and .role == "admin")' data.json
# OR conditions
jq '.[] | select(.status == "running" or .status == "pending")' data.json
Comparison operators
| Operator | Meaning | Example |
|---|---|---|
== |
Equal | select(.id == 5) |
!= |
Different | select(.status != "deleted") |
>, < |
Greater/lower | select(.age > 21) |
>=, <= |
Greater/lower or equal | select(.score >= 80) |
contains |
Contains | select(.tags | contains(["prod"])) |
startswith |
Starts with | select(.name | startswith("app-")) |
Practical examples
# Running Kubernetes pods
kubectl get pods -o json | \
jq '.items[] | select(.status.phase=="Running") | .metadata.name'
# Docker containers in error
docker inspect $(docker ps -aq) | \
jq '.[] | select(.State.Status != "running") | .Name'
# Open GitLab MRs
curl -s https://gitlab.com/api/v4/projects/123/merge_requests | \
jq '.[] | select(.state == "opened") | {title, author: .author.name}'
Combined filters
# Logical AND
jq '.instances[] |
select(.type == "t3.micro" and .state == "running")' aws.json
# Regex
jq '.[] | select(.email | test("@company\\.com$"))' users.json
# Non-null
jq '.[] | select(.description) | .name' data.json
Pattern: select() returns the object if true, nothing otherwise (a natural filter).The del() function
# Delete 1 field
echo '{"name":"Alice","age":30,"temp":"x"}' | jq 'del(.temp)'
# {"name":"Alice","age":30}
# Delete several fields
jq 'del(.password, .secret, .token)' data.json
# Delete a nested key
jq 'del(.user.metadata.internal)' data.json
Deleting in arrays
# Delete an element by index
jq 'del(.items[2])' data.json
# Delete based on a condition
jq '.users |= map(del(.password))' data.json
# Delete a whole array
jq 'del(.tags)' data.json
DevOps examples
# Clean Kubernetes secrets
kubectl get secret mysecret -o json | \
jq 'del(.metadata.managedFields, .metadata.uid)' | \
kubectl apply -f -
# package.json without devDependencies
jq 'del(.devDependencies)' package.json > package-prod.json
# Terraform state without metadata
terraform show -json | \
jq 'del(.lineage, .serial, .terraform_version)' > state-clean.json
The opposite: keeping
# Keep only some fields
jq '{name, email}' data.json # New object
# Array: keep fields
jq '.users[] | {id, username}' data.json
Conditional deletion
# Delete if null
jq 'del(.[] | select(. == null))' data.json
# Delete empty fields
jq 'del(..|select(. == ""))' data.json
Difference: del() modifies the object, {name,email} creates a new one.Building a new object
# Rename through reconstruction
echo '{"old_name":"Alice","age":30}' | jq '{new_name: .old_name, age}'
# {"new_name":"Alice","age":30}
# Several renamings
jq '{id: .user_id, email: .user_email, active: .is_active}' data.json
Renaming with an operator
# with_entries (dynamic keys)
jq '.users | with_entries(.key |= gsub("_"; "-"))' data.json
# user_name → user-name
# Rename a single key
jq '.user as $u | del(.user) | .profile = $u' data.json
Arrays of objects
# Rename in an array
jq '.items[] | {identifier: .id, label: .name}' data.json
# Map over an array
jq '.users | map({userId: .id, fullName: .name})' data.json
Practical cases
# Kubernetes labels (snake → camel)
kubectl get pods -o json | \
jq '.items[] |
{podName: .metadata.name,
namespace: .metadata.namespace,
hostIP: .status.hostIP}'
# AWS → custom format
aws ec2 describe-instances | \
jq '.Reservations[].Instances[] |
{instanceId: .InstanceId,
privateIP: .PrivateIpAddress,
state: .State.Name}'
# GitLab CI → simple format
curl -s https://gitlab.com/api/v4/projects/123/pipelines | \
jq '.[] | {pipelineId: .id, status, ref}'
Complex transformation
# Flatten a nested structure
jq '.user | {name, city: .address.city, country: .address.country}' data.json
# Concatenate fields
jq '{fullAddress: "\(.address.street), \(.address.city)"}' data.json
Pattern: {new: .old} creates an object with renamed keys.The + operator (merge)
# Add a fixed field
echo '{"name":"Alice"}' | jq '. + {status: "active"}'
# {"name":"Alice","status":"active"}
# Several fields
jq '. + {env: "production", version: "1.0"}' data.json
Arrays of objects
# Add a field to every element
jq '.users[] | . + {role: "user"}' data.json
# Map over an array
jq '.items | map(. + {processed: true})' data.json
Dynamic values
# Current timestamp
jq '. + {timestamp: now | todate}' data.json
# Shell variable
ENV="prod"
jq --arg env "$ENV" '. + {environment: $env}' data.json
# Computed from an existing field
jq '. + {total: (.price * .quantity)}' data.json
DevOps cases
# Kubernetes annotations
kubectl get deploy nginx -o json | \
jq '.metadata.annotations += {"deployed-by": "ci-cd",
"version": "v1.2.3"}' | \
kubectl apply -f -
# Docker labels
docker inspect nginx | \
jq '.[0].Config.Labels + {"app.version": "2.0",
"app.env": "staging"}'
# package.json scripts
jq '.scripts += {"deploy": "npm run build && scp dist/* server:/"}' \
package.json > package.json.tmp && mv package.json.tmp package.json
Conditional addition
# Add if missing
jq '.config.timeout //= 30' data.json # 30 if null/missing
# Add based on a condition
jq 'if .env == "prod" then . + {ssl: true} else . end' data.json
Difference: + merges, |= updates in place.The sort_by() function
# Sort by field
echo '[{"name":"Bob","age":25},{"name":"Alice","age":30}]' | \
jq 'sort_by(.name)'
# Alice then Bob (alphabetical order)
# Numeric sort
jq 'sort_by(.age)' users.json
# Descending sort (reverse)
jq 'sort_by(.age) | reverse' users.json
Multi-criteria sort
# Sort on 2 fields (priority)
jq 'sort_by(.department, .name)' employees.json
# Sort with a transformation
jq 'sort_by(.name | ascii_downcase)' data.json # Ignores case
# Sort by length
jq 'sort_by(.description | length)' data.json
Sorting primitive values
# Simple array
echo '[3,1,4,1,5]' | jq 'sort'
# [1,1,3,4,5]
# Strings
echo '["banana","apple","cherry"]' | jq 'sort'
# ["apple","banana","cherry"]
DevOps cases
# Kubernetes pods by timestamp
kubectl get pods -o json | \
jq '.items | sort_by(.metadata.creationTimestamp) |
.[] | .metadata.name'
# Docker containers by CPU usage
docker stats --no-stream --format json | \
jq -s 'sort_by(.CPUPerc | rtrimstr("%") | tonumber) | reverse'
# AWS EC2 by cost (tag)
aws ec2 describe-instances | \
jq '.Reservations[].Instances |
sort_by(.Tags[] | select(.Key=="Cost") | .Value | tonumber)'
Sorting with null
# Null first
jq 'sort_by(.optional_field // "")' data.json
# Ignore null
jq '[.[] | select(.field)] | sort_by(.field)' data.json
Group + Sort
# Group then sort
jq 'group_by(.category) |
map({category: .[0].category,
items: sort_by(.name)})' data.json
Behaviour: sort_by() is stable (order preserved on ties).Top 10 essential functions
| Function | Use | Example |
|---|---|---|
| map() | Transform an array | map(.name) → list of names |
| select() | Filter | select(.active == true) |
| sort_by() | Sort | sort_by(.age) |
| group_by() | Group | group_by(.category) |
| unique | Deduplicate | unique_by(.id) |
| length | Count | .items | length |
| keys | List keys | keys → array of keys |
| has() | Test presence | has("email") |
| contains() | Inclusion | contains({"status":"ok"}) |
| add | Sum/concat | [1,2,3] | add → 6 |
String manipulation
# Case
jq '.name | ascii_upcase' data.json # UPPERCASE
jq '.name | ascii_downcase' data.json # lowercase
# Split/join
jq '.path | split("/")' data.json # String → array
jq '.tags | join(", ")' data.json # Array → string
# Regex
jq '.email | test("@gmail.com$")' data.json # Boolean
jq '.text | gsub("old"; "new")' data.json # Replace
Aggregation functions
# Statistics
jq '[.items[].price] | add' data.json # Sum
jq '[.items[].price] | max' data.json # Maximum
jq '[.items[].price] | min' data.json # Minimum
jq '[.items[].price] | add / length' data.json # Average
# Group and count
jq 'group_by(.status) | map({status: .[0].status, count: length})' data.json
Advanced transformations
# Flatten
echo '[[1,2],[3,4]]' | jq 'flatten'
# [1,2,3,4]
# Transpose
jq 'transpose' matrix.json
# Recursion
jq '.. | select(type == "string")' data.json # Every string
DevOps cases
# Kubernetes: pods per node
kubectl get pods -o json | \
jq '.items | group_by(.spec.nodeName) |
map({node: .[0].spec.nodeName, count: length})'
# GitLab: MRs per author
curl -s https://gitlab.com/api/v4/projects/123/merge_requests | \
jq 'group_by(.author.username) |
map({author: .[0].author.username, mrs: map(.title)})'
# AWS: total cost by tags
aws ce get-cost-and-usage ... | \
jq '[.ResultsByTime[].Groups[].Metrics.UnblendedCost.Amount |
tonumber] | add'
Cheat sheet: map() transforms, select() filters, group_by() groups.