Back to Blog
    October 14, 20268 min read

    jq Tutorial: Query and Transform JSON from the Command Line

    CLI
    jq
    Tutorial

    jq is a lightweight command-line tool that lets you slice, filter, map and transform JSON data the same way sed and awk handle text. It comes pre-installed on most Linux distributions, runs on macOS and Windows, and is the fastest way to query an API response without opening a browser.

    Installing jq

    # macOS
    brew install jq
    
    # Ubuntu / Debian
    sudo apt-get install jq
    
    # Windows (winget)
    winget install jqlang.jq
    
    # Verify
    jq --version
    # jq-1.7.1

    The Identity Filter — Your First jq Command

    The simplest jq filter is ., which means "the whole input". It pretty-prints JSON just like our JSON Formatter:

    echo '{"name":"Alice","age":30}' | jq '.'
    # Output:
    {
      "name": "Alice",
      "age": 30
    }

    Selecting Fields

    Use .fieldName to extract a single value:

    echo '{"name":"Alice","age":30,"city":"London"}' | jq '.name'
    # "Alice"
    
    # Multiple fields — build a new object
    echo '{"name":"Alice","age":30,"city":"London"}' | jq '{name: .name, city: .city}'
    # { "name": "Alice", "city": "London" }

    Working with Arrays

    Use .[] to iterate over an array, and .[0] to get a specific index:

    # Sample data
    export DATA='[
      {"id":1,"name":"Alice","score":92},
      {"id":2,"name":"Bob","score":78},
      {"id":3,"name":"Carol","score":95}
    ]'
    
    # Get all names
    echo $DATA | jq '.[].name'
    # "Alice"
    # "Bob"
    # "Carol"
    
    # Get first item
    echo $DATA | jq '.[0]'
    # { "id": 1, "name": "Alice", "score": 92 }
    
    # Get array length
    echo $DATA | jq 'length'
    # 3

    Filtering with select()

    select() keeps only the items that match a condition — the equivalent of SQL's WHERE clause:

    # Students who scored above 80
    echo $DATA | jq '[.[] | select(.score > 80)]'
    # [
    #   { "id": 1, "name": "Alice", "score": 92 },
    #   { "id": 3, "name": "Carol", "score": 95 }
    # ]
    
    # Exact string match
    echo $DATA | jq '.[] | select(.name == "Bob")'
    # { "id": 2, "name": "Bob", "score": 78 }

    Transforming Data with map()

    map() applies a filter to every element and returns a new array — like JavaScript's Array.map():

    # Extract just the names as an array
    echo $DATA | jq '[.[] | .name]'
    # ["Alice", "Bob", "Carol"]
    
    # Shorthand with map()
    echo $DATA | jq 'map(.name)'
    # ["Alice", "Bob", "Carol"]
    
    # Add a field to every object
    echo $DATA | jq 'map(. + {passed: (.score >= 80)})'
    # [
    #   { "id": 1, "name": "Alice", "score": 92, "passed": true },
    #   { "id": 2, "name": "Bob",   "score": 78, "passed": false },
    #   { "id": 3, "name": "Carol", "score": 95, "passed": true }
    # ]

    Piping Filters

    Chain filters with | just like shell pipes. Output of one filter becomes input to the next:

    # Get names of students who passed, sorted
    echo $DATA | jq '[.[] | select(.score >= 80) | .name] | sort'
    # ["Alice", "Carol"]
    
    # Average score
    echo $DATA | jq '[.[].score] | add / length'
    # 88.33333333333333

    Real-World: Querying an API Response

    jq shines when combined with curl. Here's querying the GitHub API for a repo's open issues:

    curl -s https://api.github.com/repos/curl/curl/issues \
      | jq '[.[] | {title: .title, user: .user.login, labels: [.labels[].name]}] | .[0:3]'

    Tip: Validate your JSON first

    If jq throws parse error, the input isn't valid JSON. Paste it into the JSON Validator to find and fix the exact line before running jq.

    Useful jq Flags

    FlagWhat it does
    -rRaw output — strips quotes from strings
    -cCompact output — one line, no whitespace
    -eExit 1 if output is false/null (useful in scripts)
    -sSlurp — read all inputs into a single array
    --arg k vPass a shell variable into jq as a string
    -f file.jqRead filter from a file instead of inline

    Common jq Patterns Cheatsheet

    # Keys of an object
    echo '{"a":1,"b":2}' | jq 'keys'           # ["a","b"]
    
    # Check if key exists
    echo '{"a":1}' | jq 'has("a")'             # true
    
    # Delete a key
    echo '{"a":1,"b":2}' | jq 'del(.b)'        # {"a":1}
    
    # Flatten nested array
    echo '[[1,2],[3,[4,5]]]' | jq 'flatten'    # [1,2,3,4,5]
    
    # Group by field
    echo $DATA | jq 'group_by(.score > 80)'
    
    # Sort by field
    echo $DATA | jq 'sort_by(.score)'
    
    # Min / max
    echo $DATA | jq 'min_by(.score).name'      # "Bob"
    echo $DATA | jq 'max_by(.score).name'      # "Carol"
    
    # String interpolation
    echo '{"name":"Alice"}' | jq -r '"Hello, (.name)!"'
    # Hello, Alice!

    Optional object identifier

    Use .field? instead of .field when the field might not exist. Without ?, accessing a missing field on null throws an error that stops the pipeline.

    Try it in the Browser

    Don't have jq installed yet? Paste your JSON into the JSON Formatter to pretty-print and inspect it, or use the JSON Validator to make sure your data is clean before piping it through jq filters.