$:/litapp/blog

A blog about nothing, coding, photography, and generative art

jq fzf powered filtering

2023/11/18 09:39

When needing to explore a JSON file for a long time I just reached into my history and looked for a oneliner that I copied from a github gist echo '' | fzf --print-query --preview "cat *.json | jq {q}". This worked quite well, but I have now decided to improve it leveraging the fzf features.

Features

  • Supports reading from stdin (pipe or -) and path
  • Keeps history of selected filters and presenting them as options for fzf.
  • Prints the result of applying the filter the input

The script

function jqz () {
    if [[ -z $1 ]] || [[ $1 == "-" ]]; then
        input=$(mktemp)
        trap "rm -f '$input'" EXIT
        cat /dev/stdin > "$input"
    else
        input="$1"
    fi

    local q=$(<$HOME/.jqz-history  \
    | fzf \
        --history=$HOME/.jqz-history \
        --query '.' \
        --prompt 'jq filter> ' \
        --bind "tab:replace-query"\
        --bind "return:print-query" \
        --bind "alt-up:preview-page-up,alt-down:preview-page-down" \
        --bind 'focus:transform-preview-label:echo [ {} ]' \
        --header 'TAB: select filter' \
        --preview "<$input | jq --color-output -r  {q}")

    jq -r "$q" "$input"
}

fzf options

  • --history saves the selected filters to this file, it enable going to the previous selection with CTRL-N and CTRL-P. The history file is also used to provide options to pick from.
  • --prompt shows a customized prompt
  • --bind binds an event or a keybinding to an action, check fzf documentation for complete list. Here is used to scroll the preview, to update the preview title, and to select a filter.
  • --header text that is presented above the query
  • --query the default query

Supporting both stdin and paths

I got this part from Implementing a jq REPL with fzf it allows to pipe in json {"a":42} | jqz or to read from a file jqz /tmp/some-file.json

    if [[ -z $1 ]] || [[ $1 == "-" ]]; then
        input=$(mktemp)
        trap "rm -f '$input'" EXIT
        cat /dev/stdin > "$input"
    else
        input="$1"
    fi