Skip to content

Instantly share code, notes, and snippets.

@mkfmnn
Last active June 21, 2024 05:45
Show Gist options
  • Save mkfmnn/7b637aa0ec9b0422b3b75ec181a132fc to your computer and use it in GitHub Desktop.
Save mkfmnn/7b637aa0ec9b0422b3b75ec181a132fc to your computer and use it in GitHub Desktop.
# A basic re-implementation of gron in JQ
# Operates in a streaming manner, without having to read the entire input first or hold it in memory
# Try: cat big.json | while read; do printf '%s\n' "$REPLY"; sleep 0.1; done | jqgron
# Most operations are slower than gron, but `jqgron -u -j` is actually faster!
def topath:
map(if (tostring|test("\\A[[:alpha:]$_][[:alnum:]$_]*\\z")|not)
then "[\(tojson)]" else ".\(.)" end) | join("");
def tojqpath:
topath | if .[0:1] == "[" then "." + . else . end;
def parsepath: if length == 0 then [] else
[capture("\\A(?:\\.(?<prop>[^.\\[]+)|\\[(?<field>(?:[^\"\\]]+|\"(?:[^\\\\\"]|\\\\.)+\"))\\])(?<rest>.*)\\z")] as $m
| if ($m|length) == 0 then error("unparsed input in path: \(.)") else $m[] end
| [(.prop // (.field|fromjson)), (.rest | parsepath[])] end;
def parsejqpath:
if . == "." then ""
elif .[0:2] == ".[" then .[1:]
elif .[0:1] == "." then .
else error("malformed path: \(.)") end
| parsepath;
def _addparents($path):
# expects input like {s:{}, e:[]}
(path[:-1]|tostring) as $pathstr |
if ($path|length == 0) or (.s|has($pathstr)) then .
else _addparents($path[:-1]) | .s[$pathstr] += 1 | .e += [$path] end;
# Converts a jq-style stream into a gron-style stream, with container openers
# and no container terminators
def prefixstream(i):
foreach (i | select(length == 2)) as $e ({s:{}}
; .e = [] | _addparents($e[0])
; (.e[] | [.[0:-1], if (.[-1]|type) == "number" then [] else {} end]), $e);
# A simplified version of fromstream that doesn't require container terminators and that
# only outputs a single object
def fromstream2(i):
reduce i as $i (null; if $i|length == 2 then setpath($i[0]; $i[1]) else . end);
def gron(i):
prefixstream(i) | "json\(.[0]|topath) = \(.[1]|tojson);";
def ungron(i):
fromstream2(
i
| capture("\\Ajson(?<path>(?:[^\"]|\"(?:[^\\\\\"]|\\\\.)+\")*) = (?<value>.*);\\z")
| [(.path|parsepath), (.value|fromjson)]
);
json=
ungron=
while getopts ju name
do
case $name in
j) json=1;;
u) ungron=1;;
?) printf "Usage: %s: [-j] [-u] [file]\n" $0
exit 2;;
esac
done
shift $(($OPTIND - 1))
if [ ! -z "$json" ]; then
if [ ! -z "$ungron" ]; then
exec jq -n $JQ_OPTS 'include "./gron";fromstream2(inputs)' "$@"
else
exec jq -cn --stream $JQ_OPTS 'include "./gron";prefixstream(inputs)' "$@"
fi
else
if [ ! -z "$ungron" ]; then
exec jq -nR $JQ_OPTS 'include "./gron";ungron(inputs)' "$@"
else
exec jq -rn --stream $JQ_OPTS 'include "./gron";gron(inputs)' "$@"
fi
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment