0Pricing
DevOps Bootcamp · Lesson

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 DevOps Bootcamp lesson on CoddyKit — 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 DevOps Bootcamp learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

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_entries
  • to_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_case
  • map(.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 value
  • with_entries(select(.value != null)) — drop null-valued keys
  • with_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:

  1. Filter the input array with map(select(...))
  2. Reshape each element with object construction
  3. Add computed fields
  4. 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 and select() for filtering
  • to_entries / from_entries — convert an object to an array of {key, value} pairs, enabling array operations on keys and values, then convert back
  • with_entries(expr) — the concise shorthand for the full to_entries → map → from_entries pipeline
  • Merging objects with + and --argjson for 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.

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, and the DevOps Bootcamp course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the DevOps Bootcamp course, upgrade to CoddyKit PRO.

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 DevOps Bootcamp 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 DevOps Bootcamp?

No prior experience is required. DevOps Bootcamp on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

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 DevOps Bootcamp lesson?

Yes. Every DevOps Bootcamp 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

  1. Filtering and Selecting JSON with jq Pipelines
  2. Transforming and Building JSON Objects with jq
  3. Consuming REST APIs with curl and jq Together
  4. Editing YAML Configuration Files with yq
← Back to DevOps Bootcamp