Wrapping jq select
In working with large and deeply nested json structures, I had to extract or delete selected elements from the structure based on variable criteria. In jq
the path()
operator supports this type of operation well. Calling path(..)
expands all paths in the input json. Selecting paths is straightforward, but what if you want to select based on the value at a path?
One way is to make the original structure available to the selector, and apply getpath()
to a selected path. Turning this into a set of shell wrappers is not difficult. Here are three such wrappers for selecting paths, selecting values at paths, and deleting values at paths.
#_# select_paths selector < json-file
#_# Select { path: p, value: v } for paths that match selector
#_# In the selector '$path' is a path, and '$root' is the original input
#_#
do_select_paths() {
jq '. as $root | $root | path(..) as $path | $path | select('"$1"')'
}
#_# select_from_paths selector < json-file
#_# Select { path: p, value: v } for paths that match selector
#_# In the selector '$path' is a path, and '$root' is the original input
#_#
do_select_from_paths() {
jq '. as $root | $root | path(..) as $path | $path | select('"$1"') | . as $p | { path: $p, value: ($root | getpath($p)) }'
}
#_# delete_from_paths selector < json-file
#_# Select { path: p, value: v } for paths that match selector
#_# In the selector '$path' is a path, and '$root' is the original input
#_#
do_delete_from_paths() {
jq '. as $root | [$root | path(..) as $path | $path | select('"$1"')] as $paths | $root | delpaths($paths)'
}
Each wrapper sets two jq
variables: $root
holds the original structure and $path
holds the current path. So the selector can apply an arbitrary jq
expression using each of these.
My use case was finding required deletion of paths ending in /XObject
and those with a stream subtype of /Image
:
do_delete_from_paths '$path[-1]=="/XObject"'
do_delete_from_paths '$root | getpath($path) | objects | .stream.dict["/Subtype"]=="/Image"'
In this case I used the path selection and path value selection only to test before using deletion.
Not being a jq
expert it took me a while to work this out, but the result is easily reusable. Maybe you can use it, too.