#!/usr/bin/env bash # # Author: Georg Voell - georg.voell@standby.cloud # Version: @(#)browse-json 3.3.1 16.03.2026 (c)2026 Standby.cloud # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ # # This script can be used free of charge. Use it as is or customize as needed. It is not guaranteed to be # error free and you will not be reimbursed for any damage it may cause. # #@ Browse through JSON items and print all or single values. #@ #@Usage: browse-json [options] [key] #@ Options: #@ -h, --help : Displays helptext. #@ -v, --version : Displays the version of the script. #@ -q, --quiet : Just write error message to LOGFILE. #@ -r, --raw : Don't convert hyphens e.g. lifecycle-state to lifecycleState #@ -o, --output : Output format: can be "json", "keys" or "plain" (default). #@ -i, --import : Read from a file ( = filename) - if not specified, read from stdin. #@ -s, --select : Select only the nth JSON item (starting with 1 for the first record). #@ Key: This script can be called with an optional parameter "key" (otherwise it prints out all keys). #@ #@Examples: #@ get-platform --output json | browse-json --raw creator #@ get-platform --output json | browse-json --raw content --output json #@ get-platform --output json | browse-json --raw content/processor #@ get-platform --output json | browse-json --select 1 os #@ get-platform --output json | browse-json os #@ get-platform --output json | browse-json [0]/os # # Exit codes: # 01: Unknown or wrong parameter. # 02: No **jq** or wrong version of **jq**. # 03: Error in JSON file or no input. # 04: Key not found. # 99: User interrupt. # # Update history: # # V 3.0.0 29.04.2020 New version # V 3.1.0 05.06.2023 New copyright # V 3.2.0 17.08.2024 New minor version # V 3.2.1 14.10.2024 Output format: json, keys and plain # V 3.3.0 19.01.2026 Revised with support of Claude Code # V 3.3.1 16.03.2026 Optimized: replaced backticks with $(); [ ] with [[ ]]; # echo|grep with [[ == */* ]] etc.; replaced let with (( )); # UUOC removed; replaced [ -a ] with && # # Find executable bash library and source it lib=$(command -v lib.bash 2>/dev/null) if [[ -n "$lib" ]]; then source "$lib" else progdir=$(dirname "$0") if [[ -r "${progdir}/lib.bash" ]]; then source "${progdir}/lib.bash" else echo "Unexpected error: Unable to locate bash library 'lib.bash'." exit 1 fi fi # Do extra cleanup function ExtraCleanup() { filecheck -rm "${scratchfile}.keys" filecheck -rm "${scratchfile}.out" } # Presets qopt="" ropt="" nopt="" recursive="" param="" number="" filename="" formatstr="" # Loop until all parameters are used up while [[ $# -gt 0 ]]; do pname="${1}" case "$pname" in -i | --import) shift if [[ -n "$1" ]]; then if [[ -z "$filename" ]]; then filename="$1" if [[ ! -r "$filename" ]]; then errstr="Can't read from filename '$filename'." fi else errstr="Option '$pname' used more then once." fi shift else errstr="Please specify a filename after parameter '$pname'." fi ;; -o | --output) shift if [[ -n "$1" ]]; then if [[ -z "$formatstr" ]]; then formatstr=$(ToLower "$1") case "$formatstr" in json|keys|plain) ;; *) errstr="Unknown format '$formatstr' after parameter '$pname'. Please choose from 'json', 'keys' or 'plain'." ;; esac else errstr="Option '$pname' used more then once." fi shift else errstr="Please specify a format ('json', 'keys' or 'plain') after parameter '$pname'." fi ;; --recursive) shift if [[ -n "$1" ]]; then recursive="$1" shift else errstr="Please specify a searchbase (e.g. .attributes) after parameter '$pname'." fi ;; -s | --select) shift if [[ -n "$1" ]]; then if [[ -z "$number" ]]; then number="$1" if [[ ! "$number" =~ ^[0-9]+$ ]]; then errstr="Invalid number after option '$pname'." else nopt="$pname $number" fi else errstr="Option '$pname' used more then once." fi shift else errstr="Please specify a number after option '$pname'." fi ;; -r | --raw) shift ropt="$pname" ;; -q | --quiet) shift qopt="$pname" ;; -v | --version) shift showversion=true ;; -h | --help) shift showhelp=true ;; *) shift if [[ "$pname" == -* ]]; then errstr="Unknown option '$pname'." else if [[ -z "$errstr" ]]; then if [[ -z "$param" ]]; then # Clean up param: remove spaces and leading slashes, squeeze slashes param="${pname// /}" param="${param#/}" # Squeeze double slashes using bash built-in while [[ "$param" == *//* ]]; do param="${param//\/\//\/}"; done else errstr="Key was already specified '$param'. Unknown additional parameter: '$pname'." fi fi fi ;; esac done # Display help or error message DisplayHelp # Set defaults if [[ -z "$nopt" ]]; then if [[ -n "$param" ]]; then fc="${param:0:1}" else fc="" fi # Prefer the first record in array if not selected individually if [[ "$fc" != "[" ]]; then nopt="--select 1" fi fi # Set output to plain if not specified if [[ -z "$formatstr" ]]; then formatstr="plain" fi if [[ -n "$recursive" ]]; then lparam="" rparam="" sbase="$recursive" else arr="" # Check if parameter contains a '/' using bash pattern match if [[ "$param" == *"/"* ]]; then lparam=$(dirname "$param") rparam=$(basename "$param") lparam=$(printf '/%s/' "$lparam" | sed 's|\(\[[0-9]*\]\)*/|"\1."|g') sbase=$(printf '%s' "$lparam" | sed 's|^"||' | sed 's|\."$||' | sed 's|""||') else lparam="" if [[ "$param" != "["* ]]; then rparam="$param" sbase="." else rparam="" sbase=".${param}" fi fi # Check if rparam contains '[' if [[ "$rparam" == *"["* ]]; then arr="${rparam#*[}" arr="[${arr}" rparam="${rparam%%[*}" fi fi if [[ "$DEBUG_BROWSE_JSON" == true ]]; then ShowVariable "script" "$script" ShowVariable "nopt" "$nopt" ShowVariable "param" "$param" ShowVariable "lparam" "$lparam" ShowVariable "rparam" "$rparam" ShowVariable "arr" "$arr" ShowVariable "sbase" "$sbase" ShowVariable "recursive" "$recursive" ShowVariable "formatstr" "$formatstr" fi # Check if we have jq in path and right version jqversok=$(check-version jq --min 1.5 | cut -d' ' -f3) if [[ "$jqversok" != "ok" ]]; then exitcode=2 errormsg $qopt $exitcode "($progstr) No 'jq' in '$PATH' or version is less than '1.5'." ClearSTDIN exit $exitcode else # Read from stdin or file if [[ -n "$recursive" ]]; then cat "$filename" > "$scratchfile" 2>&1 stat=$? else if [[ -n "$filename" ]]; then norm-json --import "$filename" $nopt $qopt $ropt > "$scratchfile" 2>&1 stat=$? else if [[ ! -t 0 ]]; then norm-json $nopt $qopt $ropt > "$scratchfile" 2>&1 stat=$? else exitcode=3 errormsg $qopt $exitcode "($progstr) No filename specified and stdin seems to be empty." exit $exitcode fi fi fi if [[ $stat -gt 0 ]]; then exitcode=3 if [[ -z "$qopt" ]]; then errrsn=$(head -n 2 "$scratchfile") printf '%s\n' "$errrsn" fi Cleanup exit $exitcode else if [[ "$formatstr" == "json" && -z "$rparam" ]]; then # Print the whole JSON cat "$scratchfile" else # Create file with keys and types jq "${sbase} | keys_unsorted, map(type), map(.)" < "$scratchfile" > "${scratchfile}.keys" 2>&1 stat=$? if [[ $stat -gt 0 ]]; then if [[ "$DEBUG_BROWSE_JSON" == true ]]; then ShowVariable "Print Keys - stat" "$stat" fi exitcode=4 errormsg $qopt $exitcode "($progstr) Key '$param' not found in JSON input." Cleanup exit $exitcode fi # Print keys with type markers elkey="" eltype="" elvalue="" i=0 printf "" > "${scratchfile}.out" while [[ "$elkey" != "null" ]]; do # Avoid UUOC: redirect directly element=$(jq -M "nth(${i})" < "${scratchfile}.keys" | tr -d '"' | tr '\n' '\t') elkey="${element%%$'\t'*}" rest="${element#*$'\t'}" eltype="${rest%%$'\t'*}" elvalue="${rest#*$'\t'}" elvalue="${elvalue%%$'\t'*}" if [[ -z "$rparam" || "$elkey" == "$rparam" ]]; then if [[ "$elkey" != "null" ]]; then if [[ "$eltype" == "array" || "$eltype" == "object" ]]; then if [[ -n "$rparam" ]]; then # Recursion needed if [[ -z "$recursive" ]]; then if [[ "$sbase" != "." ]]; then if [[ -z "$rparam" ]]; then recursive="${sbase}.${arr}" else recursive="${sbase}.\"${rparam}\"${arr}" fi else if [[ -z "$rparam" ]]; then recursive=".${arr}" else recursive=".\"${rparam}\"${arr}" fi fi if [[ "$formatstr" == "json" ]]; then printf '{\n "%s": ' "$rparam" > "${scratchfile}.out" jq "$recursive" < "$scratchfile" >> "${scratchfile}.out" printf '}\n' >> "${scratchfile}.out" jq -M . < "${scratchfile}.out" else if [[ "$DEBUG_BROWSE_JSON" == true ]]; then printf 'Recursive Call\n' >/dev/stderr ShowVariable "recursive" "$recursive" fi "$script" --import "$scratchfile" --output "$formatstr" --recursive "$recursive" "$param" fi Cleanup exit fi else if [[ "$formatstr" == "keys" ]]; then if [[ "$eltype" == "array" ]]; then printf '%s:[]\n' "$elkey" >> "${scratchfile}.out" else printf '%s:{}\n' "$elkey" >> "${scratchfile}.out" fi elif [[ "$formatstr" == "plain" ]]; then if [[ "$elkey" == "$rparam" ]]; then printf '%s\n' "$elvalue" >> "${scratchfile}.out" else printf '%s\n' "$elkey" >> "${scratchfile}.out" fi fi fi else if [[ "$formatstr" == "keys" ]]; then printf '%s:%s\n' "$elkey" "$elvalue" >> "${scratchfile}.out" elif [[ "$formatstr" == "plain" ]]; then if [[ "$elkey" == "$rparam" ]]; then printf '%s\n' "$elvalue" >> "${scratchfile}.out" else printf '%s\n' "$elkey" >> "${scratchfile}.out" fi else # JSON output printf '{\n "%s": "%s"\n}\n' "$elkey" "$elvalue" >> "${scratchfile}.out" fi fi fi fi (( i++ )) done result=$(filecheck -s "${scratchfile}.out") if [[ "$result" == "${scratchfile}.out" ]]; then cat "${scratchfile}.out" else if [[ "$DEBUG_BROWSE_JSON" == true ]]; then ShowVariable "Result - stat" "$stat" fi exitcode=4 errormsg $qopt $exitcode "($progstr) Key '$param' not found in JSON input." Cleanup exit $exitcode fi fi fi fi # Cleanup and exit Cleanup exit $exitcode