Practical guide

JQ recipes: sort keys and extract nested values

Pick a task, copy its jq expression, and compare the result with the complete example below. Each recipe works on the root JSON value; there is no hidden wrapper to add or remove.

The key-sorting recipes match JSON All-in-One's built-in aliases. The email and URL recipes are custom aliases used in our welcome walkthrough, not preinstalled buttons. For field selection, record filtering, and grouping, start with jq filter examples.

Sort object keys ascending, at every depth

Use this when two documents have the same structure but their object fields appear in different orders. walk visits nested values, including objects inside arrays. Each object becomes a list of entries, sorted by key, then an object again.

This is the same expression as the built-in ascending key-sort alias for both object and array roots, with added line breaks. It sorts object keys, not array records by a field: B-200 still comes before A-100 in the result. Scalar values and array positions stay unchanged.

Key comparison uses jq's string ordering, not a locale-aware or natural-number sort. Object member order is a presentation choice, not a change to the JSON data's meaning; another consumer may display those keys differently.

jq: sort object keys ascendingjq
walk(
  if type == "object" then
    (
      to_entries
      | sort_by(.key)
      | from_entries
    )
  else
    .
  end
)
Input JSONjson
{
  "products": [
    {"stock": 8, "details": {"weight": 2, "color": "blue"}, "sku": "B-200"},
    {"stock": 5, "details": {"weight": 1, "color": "amber"}, "sku": "A-100"}
  ],
  "active": true,
  "warehouse": {"zone": "east", "aisle": 3}
}
Output JSONjson
{
  "active": true,
  "products": [
    {"details": {"color": "blue", "weight": 2}, "sku": "B-200", "stock": 8},
    {"details": {"color": "amber", "weight": 1}, "sku": "A-100", "stock": 5}
  ],
  "warehouse": {"aisle": 3, "zone": "east"}
}

Sort object keys descending, at every depth

The descending built-in alias reverses each object's sorted entry list before rebuilding the object. It applies the same rule recursively, including the details objects below.

The reverse here acts on object entries, not on the products array. It neither reverses product rows nor sorts them by SKU, stock, or another field. B-200 remains the first product.

jq: sort object keys descendingjq
walk(
  if type == "object" then
    (
      to_entries
      | sort_by(.key)
      | reverse
      | from_entries
    )
  else
    .
  end
)
Input JSONjson
{
  "products": [
    {"stock": 8, "details": {"weight": 2, "color": "blue"}, "sku": "B-200"},
    {"stock": 5, "details": {"weight": 1, "color": "amber"}, "sku": "A-100"}
  ],
  "active": true,
  "warehouse": {"zone": "east", "aisle": 3}
}
Output JSONjson
{
  "warehouse": {"zone": "east", "aisle": 3},
  "products": [
    {"stock": 8, "sku": "B-200", "details": {"weight": 2, "color": "blue"}},
    {"stock": 5, "sku": "A-100", "details": {"weight": 1, "color": "amber"}}
  ],
  "active": true
}

Collect nested email-shaped string values

.. visits values at every depth, and strings keeps only strings. The anchored pattern looks for an email-shaped value rather than an address inside prose. One regex detail matters: jq's $ anchor also allows a final newline, which remains in the returned value. The surrounding brackets collect matches into one array.

This practical ASCII email pattern is not RFC email validation and does not check whether an address exists. It can accept malformed addresses and reject valid ones. Object keys are not scanned, and values are neither trimmed nor lowercased.

unique removes exact duplicates and sorts the result. It does not preserve discovery order or merge differently cased addresses. Here, the repeated sales address appears once; the sentence in notes and the email-shaped object key do not appear.

jq: extract nested emailsjq
[
  .. | strings
  | select(test("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"))
]
| unique
Input JSONjson
{
  "supplier": {
    "email": "sales@supply.example.com",
    "contacts": [
      {"email": "support@supply.example.com"},
      {"email": "sales@supply.example.com"},
      {"email": "bad@localhost"}
    ]
  },
  "warehouse": {"contact": {"email": "receiving@example.org"}},
  "notes": "Ask sales@supply.example.com for a quote.",
  "support@example.com": "This is an object key, not an email value.",
  "inactive": null
}
Output JSONjson
[
  "receiving@example.org",
  "sales@supply.example.com",
  "support@supply.example.com"
]

Collect nested HTTP and HTTPS string values

This recipe collects string values beginning with http:// or https://, including values inside nested arrays. The i flag makes the prefix test case-insensitive, so an uppercase HTTPS:// prefix matches too.

A matching prefix is not URL validation: even https:// without a hostname matches. The recipe returns the whole original string value, not an extracted URL substring. Text following the prefix is kept, leading whitespace prevents a match, and a link in the middle of prose is not extracted. Other schemes and object keys are ignored; no link is fetched.

unique sorts and removes exact duplicates without normalizing URL casing, trailing slashes, or query parameters. The uppercase URL sorts first below, and the repeated product URL appears once. No matches produce an empty array.

jq: extract nested HTTP URLsjq
[
  .. | strings
  | select(test("^https?://"; "i"))
]
| unique
Input JSONjson
{
  "links": [
    "https://shop.example.com/products/b-200",
    {"manual": "http://docs.example.org/b-200"},
    ["https://shop.example.com/products/b-200", "HTTPS://STATUS.EXAMPLE.NET"]
  ],
  "note": "Visit https://shop.example.com/products/b-200 today.",
  "other": "ftp://files.example.org/catalog.json",
  "http://keys.example.com": "Object keys are not scanned.",
  "enabled": true
}
Output JSONjson
[
  "HTTPS://STATUS.EXAMPLE.NET",
  "http://docs.example.org/b-200",
  "https://shop.example.com/products/b-200"
]

Run a recipe and keep it for later

In JSON All-in-One, open the input document, choose JQ, paste the recipe, and press Ctrl+Enter on Windows or Linux, or Cmd+Enter on macOS, to open the transformed result. Each recipe above emits one complete JSON value. The original source remains available separately.

These are standard jq expressions, not extension-only commands. In a shell, put the expression in recipe.jq and run jq -f recipe.jq input.json to avoid shell-quoting differences.

For repeated work, add the expression under Settings > JQ aliases. Pick the scope matching your document root: object or array. The recipes themselves handle either root shape, but a saved alias button is shown only for its configured scope. Scalar roots can still use the JQ input directly.

Recursive traversal touches the document's nested values, and unique collects and sorts matches. These are not constant-memory streaming recipes. Try them on a representative sample before running them on a large file.