#!/bin/bash # # Author: Georg Voell - georg.voell@standby.cloud # Version: @(#)norm-json 3.2.0 11.08.2024 (c)2024 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. # #@ Normalize JSON items and pretty print them. #@ #@Usage: norm-json [options] #@ 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 #@ -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 item). # # Exit codes: # 01: Unknown or wrong parameter. # 02: No **jq** or wrong version of **jq**. This script needs **jq** to format "json". # 03: No input or empty file. # 04: JSON file contains less then 2 lines. # 05: Error in JSON file. # 06: JSON file doesn't contain specified item. # 99: User interrupt. # # Update history: # # V 3.0.0 10.01.2021 New version # V 3.0.1 21.03.2021 New paramater "--quiet" # V 3.0.2 24.03.2021 Strip comments beginning lines with // # V 3.1.0 05.06.2023 New copyright # V 3.1.1 07.09.2023 Check if sed is capable of writing Camel Case # V 3.1.2 03.08.2024 Supporting Identity Domains # V 3.2.0 11.08.2024 New minor version: New normalized JSON format # # Find executable bash library and source it lib=`which lib.bash 2>/dev/null | sed 's|^no 'lib.bash' in .*||'` if [ "$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}.pre } # Presets raw=false qopt="" number="" param="" filename="" result="" items=0 lines=0 # Loop until all parameters are used up while [ $# -gt 0 ]; do pname=${1} case "$1" in -i | --import) shift if [ "$1" != "" ]; then if [ "$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 ;; -s | --select) shift if [ "$1" != "" ]; then if [ "$number" = "" ]; then number="$1" range=`echo "$number" | grep '^[0123456789]*$'` if [ "$range" = "" ]; then errstr="Invalid number after option '$pname'." fi else errstr="Option '$pname' used more then once." fi shift else errstr="Please specify a number after option '$pname'." fi ;; -r | --raw) shift raw=true ;; -v | --version) shift showversion=true ;; -h | --help) shift showhelp=true ;; -q | --quiet) shift qopt="$pname" ;; *) shift errstr="Unknown parameter: '$pname'." esac done # Display help or error message DisplayHelp # Check if he have jq in path and the right version result=`check-version jq --min 1.5` jqvers=`echo "$result" | cut -d' ' -f2` jqok=`echo "$result" | cut -d' ' -f3` # Check if we can convert dashes to Camel Case sedok=`echo "my-test" | sed 's|-\(.\)|\U\1|g'` if [ "$sedok" = "myTest" ]; then sedok="ok" fi if [ "$jqok" != "ok" -o "$sedok" != "ok" ]; then # At least jq version 1.5 is needed for 'first(inputs)', 'keys_unsorted' and 'nth(0)' and we need gnu sed result="" exitcode=2 if [ "$jqok" != "ok" ]; then jqok="jq" if [ "$OS" = "Darwin" ]; then result="Install 'jq' with 'brew install jq'" fi else jqok="sed" if [ "$OS" = "Darwin" ]; then result="Install 'gsed' with 'brew install gnu-sed'" fi fi errormsg $qopt $exitcode "($progstr) No '$jqok' in '$PATH' or not the required version." "$result" ClearSTDIN exit $exitcode else # Read from stdin or file if [ "$filename" != "" ]; then # Strip first char in stream and last char in stream: sed '1s|^\[||' | sed '$s|\]$||' # Strip lines beginning with // and empty lines from file and check JSON syntax with jq cat $filename | sed 's|^//.*$||' | grep . > $scratchfile else if [ ! -t 0 ]; then # Strip lines beginning with // and empty lines from stdin and check JSON syntax with jq cat /dev/stdin | sed 's|^//.*$||' | grep . > $scratchfile else exitcode=3 errormsg $qopt $exitcode "($progstr) No filename specified and stdin seems to be empty." exit $exitcode fi fi # Check JSON syntax with jq result=`head -c 1 $scratchfile` # Read the first char in file if [ "$result" = "[" ]; then cat $scratchfile | jq '.[]' > ${scratchfile}.pre 2>&1 stat=$? else cat $scratchfile | jq '.' > ${scratchfile}.pre 2>&1 stat=$? fi if [ $stat -gt 0 ]; then errrsn=`head -n 1 ${scratchfile}.pre` exitcode=4 errormsg $qopt $exitcode "($progstr) Tool 'jq' detected an error in JSON input." "$errrsn" Cleanup exit $exitcode else # Check for at least 2 lines in JSON result=`filecheck -sl ${scratchfile}.pre` if [ "$result" = "" ]; then exitcode=4 result=`cat ${scratchfile}.pre` if [ "$result" = "" ]; then errormsg $qopt $exitcode "($progstr) JSON file is empty." else errormsg $qopt $exitcode "($progstr) JSON file contains only one line: '$result'." fi Cleanup exit $exitcode else if [ "$raw" = true ]; then # Do not change anything mv -f ${scratchfile}.pre $scratchfile stat=$? else # Analyze JSON format result=`head -n 2 ${scratchfile}.pre | tr -d '\n' | tr -d ' ' | cut -d':' -f1` # Convert known tools format case "$result" in '{"content"') # "content" is used by normed JSON raw=true cat ${scratchfile}.pre | jq '.content' > $scratchfile stat=$? ;; '{"items"') # "items" is used by REST API e.g. query raw=true # We are assuming that we already use camel cat ${scratchfile}.pre | jq '.items' > $scratchfile stat=$? ;; '{"data"') # "data" is being used by "oci cli" # Check for identity domain resources result=`cat ${scratchfile}.pre | grep '^ "resources":' | head -n 10` if [ "$result" != "" ]; then cat ${scratchfile}.pre | jq '.data.resources' > $scratchfile stat=$? else cat ${scratchfile}.pre | jq '.data' > $scratchfile stat=$? fi ;; '{"result"') # "result" is used by "opc cli" cat ${scratchfile}.pre | jq '.result' > $scratchfile stat=$? ;; *) # Do not change anything mv -f ${scratchfile}.pre $scratchfile stat=$? esac fi # Check for JSON errors if [ $stat -gt 0 ]; then exitcode=5 result=`cat $scratchfile` if [ "$result" = "" ]; then errormsg $qopt $exitcode "($progstr) JSON file is empty." else errormsg $qopt $exitcode "($progstr) JSON file contains only one line: '$result'." fi Cleanup exit $exitcode else # Check for array result=`head -c 1 $scratchfile` # Read the first char in file if [ "$result" = "[" ]; then # Use array jq --slurp add $scratchfile > ${scratchfile}.pre 2>/dev/null stat=$? else # Create array result=`compare-version "1.6" "$jqvers"` if [ "$result" = "older" ]; then # Workaround for older jq version - hope this works everytime echo '[' > ${scratchfile}.pre cat $scratchfile | tr -d '\n' | sed 's|}{|},{|g' >> ${scratchfile}.pre echo ']' >> ${scratchfile}.pre mv -f ${scratchfile}.pre $scratchfile jq --slurp add $scratchfile > ${scratchfile}.pre 2>/dev/null stat=$? else cat $scratchfile | jq --slurp > ${scratchfile}.pre 2>/dev/null stat=$? fi fi if [ $stat -gt 0 ]; then exitcode=4 errormsg $qopt $exitcode "($progstr) Tool 'jq' detected an error in JSON input." "Slurp conversion" Cleanup exit $exitcode else # If specified - select item if [ "$number" != "" ]; then items=`cat ${scratchfile}.pre | jq -r '. | length'` if [ $number -lt 1 -o $number -gt $items ]; then if [ $items -eq 1 ]; then result="There is only item '1'" else result="Please select between '1' and '${items}'" fi exitcode=6 errormsg $qopt $exitcode "($progstr) JSON file doesn't contain specified item: '$number'." "$result" Cleanup exit $exitcode else let number-- cat ${scratchfile}.pre | jq -M 'nth('$number')' > $scratchfile lines=`cat $scratchfile | wc -l` if [ $lines -gt 1 ]; then mv -f $scratchfile ${scratchfile}.pre else # Only one line -> We assume a text -> don't convert to camel cat $scratchfile | jq -r > ${scratchfile}.pre raw=true fi fi fi # No conversion to Camel in raw mode if [ "$raw" = true ]; then cat ${scratchfile}.pre else while IFS=':' read -r key value; do if [ "$key" != "" -a "$value" != "" ]; then key=`echo "$key" | sed 's|-\([a-z]\)|\U\1|g' | sed 's|\([MGT]\)b\([s]*\)"|\1B\2"|g'` printf "%s:%s\n" "$key" "$value" else printf "%s\n" "$key" fi done < ${scratchfile}.pre fi fi fi fi fi fi # Cleanup and exit Cleanup exit $exitcode