Transforming and Building JSON Objects with jq
Reshape data with map, to_entries, and object construction to produce new JSON payloads.
Transforming and Building JSON Objects with jq is a free Linux Command Line & Bash Scripting Mastery lesson on CoddyKit. This is lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Linux Command Line & Bash Scripting Mastery learning path, and your progress syncs across the web and the CoddyKit app. The Linux Command Line & Bash Scripting Mastery course includes 4 lessons in total.
Why Transform JSON?
Raw JSON from APIs or log files rarely has the exact shape you need. You might receive a large object but only want specific fields, or you need to rename keys, flatten nested structures, or build a completely new payload to send to another service.
jq is a lightweight, powerful command-line JSON processor that makes these transformations possible in a single pipeline. In this lesson you will learn the three core techniques for reshaping data:
- Object construction — build a new JSON object from scratch
- map — apply a transformation to every element of an array
- to_entries / from_entries — treat an object's key-value pairs as an array so you can filter and rebuild them
All examples assume jq is installed (apt install jq / brew install jq).
Object Construction Basics
The most fundamental jq feature is object construction: wrapping expressions in {} to build a new JSON object. You choose which fields to include and what to call them.
Syntax:
{ newKey: .existingField }— rename a field{ name, age }— shorthand when the new key matches the field name{ total: (.price * .qty) }— compute a value inline
The snippet below reads a product JSON and produces a leaner shape with a computed subtotal field.
#!/usr/bin/env bash
# Object construction: pick and rename fields
product='{
"id": 42,
"name": "Widget Pro",
"price": 9.99,
"qty": 3,
"warehouse": "EU-West"
}'
echo "$product" | jq '{
productId: .id,
name,
subtotal: (.price * .qty)
}'Constructing Objects from Nested Data
Real-world JSON is often nested. jq lets you reach into nested paths inside an object constructor, flattening the structure at the same time.
Use dot-path notation inside the constructor value expression:
{ city: .address.city }{ lat: .location.coords.lat }
The example below takes a deeply nested user record and produces a flat summary suitable for a CSV header row or an API request body.
#!/usr/bin/env bash
user='{
"id": "u-001",
"profile": {
"displayName": "Ada Lovelace",
"contact": { "email": "ada@example.com", "phone": "+44-700" }
},
"plan": "pro"
}'
echo "$user" | jq '{
id,
name: .profile.displayName,
email: .profile.contact.email,
plan
}'Transforming Arrays with map
map(expr) is jq's equivalent of a for-each: it applies expr to every element of an input array and returns a new array of the same length.
Key points:
map(expr)is sugar for[.[] | expr]- The expression inside can be any jq filter — including object construction
- Chain with
select()to filter before transforming
The snippet processes a list of orders, keeping only the fields needed for a shipping manifest.
#!/usr/bin/env bash
orders='[
{"orderId": 1, "customer": "Alice", "total": 42.50, "status": "shipped"},
{"orderId": 2, "customer": "Bob", "total": 18.00, "status": "pending"},
{"orderId": 3, "customer": "Carol", "total": 99.99, "status": "shipped"}
]'
# Produce a shipping manifest: only shipped orders, slim fields
echo "$orders" | jq '[
.[] | select(.status == "shipped") | {
id: .orderId,
recipient: .customer,
amount: .total
}
]'map with Computed Fields
Inside map you can compute new values, convert types, and combine fields — not just copy them. Common patterns include:
- String interpolation:
"\(.first) \(.last)" - Arithmetic:
(.price * 1.2 | round)for a 20% markup - Conditionals:
if .score >= 90 then "A" else "B" end
The example below enriches a list of employees by adding a computed fullName and a seniority label based on years of experience.
#!/usr/bin/env bash
staff='[
{"first": "Grace", "last": "Hopper", "years": 15},
{"first": "Alan", "last": "Turing", "years": 4},
{"first": "Linus", "last": "Torvalds","years": 9}
]'
echo "$staff" | jq 'map({
fullName: "\(.first) \(.last)",
years,
seniority: (if .years >= 10 then "senior" elif .years >= 5 then "mid" else "junior" end)
})'Understanding to_entries
to_entries converts a JSON object into an array of {key, value} pairs. This unlocks array operations (map, select, sort) on an object's fields — something you cannot do directly on an object.
Example transformation:
- Input:
{"a": 1, "b": 2} - Output:
[{"key": "a", "value": 1}, {"key": "b", "value": 2}]
The reverse operation is from_entries, which turns that array back into an object. Together they form the to_entries | map(...) | from_entries idiom for object-level transformations.
#!/usr/bin/env bash
# Demonstrate to_entries and from_entries
config='{"host": "db.local", "port": 5432, "ssl": true}'
echo "--- to_entries output ---"
echo "$config" | jq 'to_entries'
echo "--- round-trip back to object ---"
echo "$config" | jq 'to_entries | from_entries'Filtering Keys with to_entries
One of the most practical uses of to_entries is dynamically filtering which keys to keep or drop based on the key name itself — something object construction cannot do when you do not know the key names in advance.
Pattern:
to_entries | map(select(.key | test("regex"))) | from_entriesto_entries | map(select(.key != "secret")) | from_entries
The snippet below strips all keys that start with an underscore (internal/private fields) before forwarding a config object to an external service.
#!/usr/bin/env bash
raw_config='{
"endpoint": "https://api.example.com",
"timeout": 30,
"_internalToken": "s3cr3t",
"_debugMode": true,
"retries": 3
}'
# Remove any key starting with underscore
echo "$raw_config" | jq '
to_entries
| map(select(.key | startswith("_") | not))
| from_entries
'Renaming Keys Dynamically with to_entries
Object construction renames keys when you know their names at write time. to_entries lets you rename keys programmatically — for example converting camelCase to snake_case, or adding a prefix.
Inside map you update the .key field of each entry, then pipe to from_entries:
map(.key |= gsub("(?<=[a-z])(?=[A-Z])"; "_") | .key |= ascii_downcase)— camelCase to snake_casemap(.key |= "app_" + .)— add a prefix to every key
The example prefixes all environment variable names with APP_ to namespace them before injection into a container.
#!/usr/bin/env bash
env_vars='{"host": "localhost", "port": "8080", "debug": "false"}'
# Add APP_ prefix and uppercase all keys
echo "$env_vars" | jq '
to_entries
| map({ key: ("APP_" + (.key | ascii_upcase)), value })
| from_entries
'with_entries: The Convenient Shorthand
The pattern to_entries | map(...) | from_entries is so common that jq provides a shorthand: with_entries(expr).
It is exactly equivalent but more concise:
with_entries(.value |= . * 2)— double every numeric valuewith_entries(select(.value != null))— drop null-valued keyswith_entries(.key |= ascii_upcase)— uppercase all keys
The snippet removes all keys whose value is null or an empty string — a common cleanup step before sending a PATCH request to a REST API.
#!/usr/bin/env bash
patch_body='{
"name": "Mehmet",
"email": "",
"phone": null,
"city": "Istanbul"
}'
# Drop empty/null fields before PATCH
cleaned=$(echo "$patch_body" | jq '
with_entries(select(.value != null and .value != ""))
')
echo "Cleaned payload:"
echo "$cleaned"
# In practice you would pipe to curl:
# curl -s -X PATCH https://api.example.com/users/1 \
# -H "Content-Type: application/json" \
# -d "$cleaned"Combining map and Object Construction in a Pipeline
Real transformations chain multiple jq operations together. A typical pipeline for preparing an API payload might:
- Filter the input array with
map(select(...)) - Reshape each element with object construction
- Add computed fields
- Sort the result
The example below reads a list of server metrics, keeps only servers with high CPU usage, and produces a compact alert payload ready to POST to a webhook.
#!/usr/bin/env bash
metrics='[
{"host": "web-01", "cpu": 23, "mem": 60, "region": "eu"},
{"host": "web-02", "cpu": 91, "mem": 88, "region": "eu"},
{"host": "db-01", "cpu": 78, "mem": 95, "region": "us"},
{"host": "db-02", "cpu": 12, "mem": 40, "region": "us"}
]'
alerts=$(echo "$metrics" | jq '[
.[] | select(.cpu > 75 or .mem > 85) | {
server: .host,
region,
severity: (if .cpu > 90 or .mem > 90 then "critical" else "warning" end),
metrics: { cpu: .cpu, mem: .mem }
}
] | sort_by(.severity)')
echo "$alerts"Building a New JSON Object from Multiple Sources
jq can merge inputs and construct objects that draw from multiple JSON sources using the addition operator + and variable binding with as $var.
Useful patterns:
obj1 + obj2— merge two objects (right-side wins on key conflicts)--argjson— pass a second JSON document as a variable$ENV— read environment variables directly inside jq
The snippet merges a base configuration with environment-specific overrides — a common pattern for 12-factor application config management in shell scripts.
#!/usr/bin/env bash
base_config='{
"logLevel": "info",
"timeout": 30,
"retries": 3,
"database": "postgres://db.local/app"
}'
env_overrides='{
"logLevel": "debug",
"database": "postgres://db.staging/app_staging"
}'
# Merge: overrides win on conflicts
merged=$(echo "$base_config" | jq --argjson overrides "$env_overrides" '. + $overrides')
echo "Merged config:"
echo "$merged"Knowledge Check: to_entries vs map
You have the following JSON object and need to remove all keys whose value is less than 0, producing a new object with only non-negative values. Which jq expression correctly accomplishes this?
Input: {"a": 10, "b": -3, "c": 0, "d": 5}
Lesson Recap: Transforming JSON with jq
You have covered the essential techniques for reshaping JSON with jq:
- Object construction
{}— build new objects by picking, renaming, and computing fields from an input map(expr)— apply any transformation to every element of an array, including nested object construction andselect()for filteringto_entries/from_entries— convert an object to an array of{key, value}pairs, enabling array operations on keys and values, then convert backwith_entries(expr)— the concise shorthand for the full to_entries → map → from_entries pipeline- Merging objects with
+and--argjsonfor multi-source payloads
These building blocks compose: filter with select, reshape with object construction, enrich with computed fields, and chain it all in a single readable jq expression. Mastering these patterns means you can wrangle virtually any JSON payload directly in the shell without writing a dedicated script in Python or Node.
Learn Bash with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 22
- Lessons
- 88
Frequently Asked Questions
Is the “Transforming and Building JSON Objects with jq” lesson free?
Yes — the full text of “Transforming and Building JSON Objects with jq” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Linux Command Line & Bash Scripting Mastery course, upgrade to CoddyKit PRO. The Linux Command Line & Bash Scripting Mastery course includes 4 lessons in total.
What will I learn in “Transforming and Building JSON Objects with jq”?
Reshape data with map, to_entries, and object construction to produce new JSON payloads. You practise Linux Command Line & Bash Scripting Mastery with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Linux Command Line & Bash Scripting Mastery?
No prior experience is required. Linux Command Line & Bash Scripting Mastery on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 2 of 4.
How long does the “Transforming and Building JSON Objects with jq” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Linux Command Line & Bash Scripting Mastery lesson?
Yes. Every Linux Command Line & Bash Scripting Mastery lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Filtering and Selecting JSON with jq Pipelines
- Transforming and Building JSON Objects with jq
- Consuming REST APIs with curl and jq Together
- Editing YAML Configuration Files with yq