#!/usr/bin/env tcsh # # Author: Georg Voell - georg.voell@standby.cloud # Version: @(#)convert-json 3.2.0 12.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. # #@ Reads a JSON file and converts it to a human readable file. #@ #@Usage: convert-json [options] [keys] #@ Options: #@ -h, --help : Displays helptext. #@ -v, --version : Displays the version of the script. #@ -n, --noheader : Don't write a header (needed for concatenating files) in output "tsv". #@ -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 "line" (default), "json", "tsv" or "table". #@ -i, --import : JSON file to read from - if not specified, read from stdin. #@ Keys: Optional - Select the keys you want to convert. Use a comma (or tab) separated string for keys. #@ If empty - display all keys in JSON. # # Exit codes: # 01: Unknown or wrong parameter. # 02: **jq** not found. This script needs **jq** to perform. # 03: JSON error. # 04: Key in JSON contains dash ("-") or blank. **jq** does not work here. - this is now obsolete # 05: JSON does not start with "{". # 06: JSON contains less then 2 lines. # 07: **jq** join error. # 99: User interrupt. # # See also: # **print-table**(1), **install-scripts**(1) # # Update history: # # V 1.0.0 17.10.2017 New version # V 2.0.0 22.06.2019 Some fixes # V 3.0.0 02.06.2020 Redesign (was an include) # V 3.0.1 11.06.2020 Using library # V 3.0.2 11.01.2021 Using script "norm-jon" and removed option "strict" # V 3.0.3 27.03.2021 New option --filename (V 3.2.0: Renamed to --import) # V 3.0.4 07.04.2021 Also accept tab separated fields string # 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 28.07.2024 Trapping ctrl-c # V 3.1.3 09.08.2024 Also use print-table with output format "json" (instead of just cat) # V 3.2.0 12.08.2024 New minor version # set script = ${0} # Name of this script set progstr = `basename "$script"` # Basename of the script set progdir = `dirname "$script"` # Dirname of the script source "${progdir}/lib.tcsh" # Include default csh library setenv PATH "$WORKPATH" # Set PATH to something useful # If script terminates - do some cleanup onintr Catch # Preset set qopt = '' set ropt = '' set filename = '' set fieldsstr = '' # List of fields we have to convert from json set formatstr = 'line' # Line oriented vertical list # Check parameters while ($#argv >= 1) set pname = "${1}" switch ("$1") case '-h': case '--help': set showhelp shift breaksw case '-v': case '--version': set showversion shift breaksw case '-n': case '--noheader': set noheader shift breaksw case '-q': case '--quiet': set qopt = "$pname" shift breaksw case '-r': case '--raw': set ropt = "$pname" shift breaksw case '-o': case '--output': shift if ("$1" != "") then set formatstr = `echo $1 | tolower` # Check if we have a valid format if (("$formatstr" != "line") && ("$formatstr" != "json") && ("$formatstr" != "tsv") && ("$formatstr" != "table")) then set errstr = "Please use values 'line', 'json', 'tsv' or 'table' with option '$pname'." endif shift else set errstr = "Please specify a format ('tsv', 'json', 'table' or 'line') after parameter '$pname'." endif breaksw case '-i': case '--import': shift if ("$1" != "") then set filename = $1 if (! -r "$filename") then set errstr = "Can't read filename '$filename'." endif shift else set errstr = "Please select the fields you want to convert after parameter '$pname'." endif breaksw default: set paramck = `echo "\\$1" | grep '^\\-'` # Types don't begin with '-' if (("$fieldsstr" == "") && ("$paramck" == "")) then set result = `\echo "$1" | grep ':'` if ("$result" != "") then # Fields are separated by colon set fieldsstr = `\echo "$1" | tr -d ' ' | tr -s ':' ' '` else set result = `\echo "$1" | grep ' '` if ("$result" != "") then # Fields are separated by tab set fieldsstr = `\echo "$1" | tr -d ' ' | tr -s '\t' ' '` else # Fields are separated by comma (we assume) set fieldsstr = `\echo "$1" | tr -d ' ' | tr -s ',' ' '` endif endif else set errstr = "Unknown parameter '$1'." endif shift endsw end # Display help or error message if ($?showversion || $?showhelp || "$errstr" != "") then # Get the version of the script set commentvers = `head -n 5 $script | grep '^# Version: @(' | cut -d')' -f2-` set namestr = `echo "$commentvers" | cut -d' ' -f1` # Name from comment set cversion = `echo "$commentvers" | cut -d' ' -f2` # Version from comment set cright = `echo "$commentvers" | cut -d' ' -f4` # Copyright set cowner = `echo "$commentvers" | cut -d' ' -f5-` # Copyright owner if ($?showversion) then printf "%s\n" "$cversion" else # Build version string set versstr = "$namestr ${cversion}, ${cright} $cowner" printf "\n$versstr\n" grep '^#@' $script | sed 's|^#@||' printf "\n" # Display error message and exit with return code 1 if (! $?showhelp && "$errstr" != "") then set exitcode = 1 errormsg $exitcode "$errstr" endif endif if (! -t 0) then # Clear stdin cat /dev/stdin >& /dev/null endif # Exit with exitcode exit $exitcode endif # Check if we can convert dashes to Camel Case set sedok = `echo "my-test" | sed 's|-\(.\)|\U\1|g'` if ("$sedok" == "myTest") then set sedok = "ok" endif # Check if jq is in PATH set jqinstalled = `filecheck -x jq` if ("$jqinstalled" == "" || "$sedok" != "ok") then set exitcode = 2 errormsg $qopt $exitcode "($progstr) No 'jq' or 'sed' in '$PATH' or not the required version." if (! -t 0) then # Clear stdin cat /dev/stdin >& /dev/null endif exit $exitcode endif # Convert JSON if ("$filename" == "") then # Filename was not specified - read from stdin if (! -t 0) then ### cat /dev/stdin | jq -M . >&! $scratchfile cat /dev/stdin | norm-json $qopt $ropt >&! $scratchfile set stat = $status else set exitcode = 3 errormsg $qopt $exitcode "($progstr) No filename specified and stdin seems to be empty." exit $exitcode endif else ### cat "$filename" | jq -M . >&! $scratchfile norm-json --import "$filename" $qopt $ropt >&! $scratchfile set stat = $status endif # Check if the conversion succeeded if ($stat > 0) then set exitcode = 3 set errrsn = "`head -n 2 $scratchfile`" printf "${errrsn}\n" goto Cleanup endif # Check if we have a format we could work with set result = `filecheck -sl "$scratchfile"` if ("$result" == "$scratchfile") then set head1 = "`head -n 1 $scratchfile`" # Check if first line of JSON starts with '[' if ("$head1" == '[') then cat $scratchfile | jq '.[]' >! ${scratchfile}.jq mv ${scratchfile}.jq $scratchfile set head1 = "`head -n 1 $scratchfile`" endif # Check if first line of JSON starts with '{' if ("$head1" != '{') then set exitcode = 5 errormsg $qopt $exitcode "($progstr) JSON file does not start with '{' in firstline. Instead: '$head1'." goto Cleanup endif else set exitcode = 6 errormsg $qopt $exitcode "($progstr) JSON file has less then 2 lines." goto Cleanup endif # If fieldsstr is empty, select all fields if ("$fieldsstr" == "") then set savedfield = "" foreach line ("`cat $scratchfile`") if ("$line" == '{') set havestart if ("$line" == '}') set havestop if ($?havestart && ! $?havestop) then # Check if we have to insert saved fieldname if ("$savedfield" != "") then if ("$line" == ' {' || "$line" == ' [') then set savedfield = "" else set field = `\echo "$line" | cut -d':' -f1 | sed 's|"||g'` set value = `\echo "$line" | cut -d':' -f2 | sed 's|"||g'` if ("$value" != '{' && "$value" != '[') then # Insert savedfield if ("$fieldsstr" == "") then set fieldsstr = "$savedfield" else set fieldsstr = "$fieldsstr $savedfield" endif endif set savedfield = "" endif endif # Only get values if they are not nested set grepres = `echo "$line" | grep '^ "'` if ("$grepres" != "") then # Only search for key pair values set grepres = `echo "$line" | grep ':'` if ("$grepres" != "") then set field = `\echo "$line" | cut -d':' -f1 | sed 's|"||g'` set value = `\echo "$line" | cut -d':' -f2 | sed 's|"||g'` if ("$field" != "" && "$value" != "") then if ("$value" == '{' || "$value" == '[') then set savedfield = "$field" else if ("$fieldsstr" == "") then set fieldsstr = "$field" else set fieldsstr = "$fieldsstr $field" endif endif endif endif endif endif end endif set headerstr = "" foreach label ($fieldsstr) if ("$headerstr" == "") then set headerstr = "$label" \echo '{"'${label}'": ' >>! ${scratchfile}.jq else set headerstr = "$headerstr $label" \echo ', "'${label}'": ' >>! ${scratchfile}.jq endif echo '(if (."'$label'" | type) == "array" then (."'$label'" | join(", ")) elif (."'$label'" | type) == "object" then (."'$label'" | join(", ")) else (."'$label'" | tostring) end)' >>! ${scratchfile}.jq end echo '} | join("^")\n' >>! ${scratchfile}.jq if (! $?noheader) then echo "$headerstr\n" >! ${scratchfile}.tsv endif # | sed 's|\[\d128-\d255\]||g' cat $scratchfile | sed 's|[ ]null,| "None",|g' | sed 's|[ ]null| "None"|g' | sed 's|" *"|"None"|g' | sed 's|\[ *\]|"None"|g' | sed 's|{ *}|"None"|g' \ | sed 's|"false"|false|g' | sed 's|[ ]false| "No"|g' | sed 's|"true"|true|g' | sed 's|[ ]true| "Yes"|g' \ | sed 's| \([-.][0-9*]*\),| "\1",|g' | sed 's| \([-.][0-9*]*\)$| "\1"|g' | sed 's| \([0-9*]*\),$| "\1",|g' | sed 's| \([0-9*]*\)$| "\1"|g' \ | jq -f ${scratchfile}.jq | sed 's|"||g' | sed 's|\^| |g' >>! ${scratchfile}.tsv set stat = $status # Check if the conversion succeeded if ($stat > 0) then set exitcode = 7 errormsg $qopt $exitcode "($progstr) Joining of fields failed. You may don't want to select nested fields." "Tool 'jq' quit joining with errors" goto Cleanup endif if ("$formatstr" == "tsv") then cat ${scratchfile}.tsv else print-table --output "$formatstr" --import ${scratchfile}.tsv endif # Normal exit goto Cleanup # Interrupted by ctrl-c Catch: set exitcode = 99 if ("$itrfile" != "") then if (! -f "$itrfile") then printf "${bell}\n" >& /dev/tty # errormsg $exitcode "Script aborted by user interrupt." "$script '$itrfile'" >& /dev/tty errormsg $exitcode "Script aborted by user interrupt." >& /dev/tty touch "$itrfile" if ($?SUDO_USER) then chown "${REALUSER}:$REALGROUP" "$itrfile" endif endif endif # Cleanup Cleanup: filecheck -rm ${scratchfile}.tsv filecheck -rm ${scratchfile}.jq filecheck -rm $scratchfile exit $exitcode