Skip to content

Instantly share code, notes, and snippets.

@Valodim
Last active July 8, 2022 21:53
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Valodim/7017924 to your computer and use it in GitHub Desktop.
Save Valodim/7017924 to your computer and use it in GitHub Desktop.
zsh json parser (WIP)
#!/bin/zsh
typeset -A closings
closings=( '{' '}' '[' ']' )
# usage: find-matching strvar pos
# heart of the parser. matches one "element." matching depends on first
# character (at pos), can be " for string matching or any in closings
# associative parameter.
json-find-matching () {
setopt localoptions extendedglob
REPLY=$2
# char at position should be opening
local extra=0 instr=0 seek=${${(P)1}[REPLY]} closing
# special case: we are simply inside a string
if [[ ${${(P)1}[REPLY]} == '"' ]]; then
instr=1
closing='"'
else
# bad opening char?
(( $+closings[$seek] )) || return 2
# remember closing char
closing=$closings[$seek]
fi
# iterate string while there are chars left
while (( REPLY++ < ${(P)#1} )); do
# current character
chr=${${(P)1}[REPLY]}
# if we are in a string, check if we get out at this point
if (( instr )); then
# debug
# echo instr $chr $extra
# check if the char is a ", and the text left to us is not an odd number of backslashes
if [[ $chr == '"' && ${${(P)1}[1,REPLY-1]} != *[^\\](\\\\)#\\ ]]; then
# just matching a string? we're done, then.
[[ $closing == '"' ]] && return
# either way, we're out!
instr=0
fi
# carry on my wayward son
continue
fi
# is it a closing one? if so, reduce extra and quit if we're at zero
[[ $chr == $closing ]] && (( extra-- == 0 )) && return
# if we find another one of these, increase level
[[ $chr == $seek ]] && (( extra++ ))
# opening a string? handle specially, escaping might be going on!
[[ $chr == '"' ]] && instr=1
# otherwise, don't care
# debug
# echo $chr $extra
done
return 1
}
# parses a list into an array. char at pos MUST be a [!
# usage: json-parse-list strvar pos
json-parse-list () {
# opening char must be a bracket!
[[ ${${(P)1}[$2]} == '[' ]] || return 2
# find beginning and end
local pos=$2 chr
# clear reply, and cast to array
reply=()
while (( pos++ < ${(P)#1} )); do
chr=${${(P)1}[pos]}
# skip blanks regardless
[[ $chr == [[:blank:]] ]] && continue
# break at closing bracket?
[[ $chr == ']' ]] && return
# skip commas, if there is a previous element
[[ $chr == ',' ]] && (( $#reply > 0 )) && continue
# should be a sublist, object or string - save entire thing
if [[ $chr == '"' ]] || (( $+closings[$chr] )); then
json-find-matching $1 $pos || return 2
reply+=( ${${(P)1}[pos,REPLY]} )
pos=$REPLY
continue
fi
# we should never be here!
return 1
done
}
# parses a list into an array. if said array is not associative, there will
# always be pairs of key+value.
#
# usage: json-parse-object strvar pos
json-parse-object () {
# opening char must be a curly brace!
[[ ${${(P)1}[$2]} == '{' ]] || return 2
# set up some beginnings
local pos=$2 chr
# clear reply, and cast to array if necessary
reply=()
while (( pos++ < ${(P)#1} )); do
chr=${${(P)1}[pos]}
# skip blanks regardless
[[ $chr == [[:blank:]] ]] && continue
# break at closing bracket?
[[ $chr == '}' ]] && return
# skip the : if we have a key
[[ $chr == ':' && -n $key ]] && continue
# skip comma, if there is a previous element in $reply
[[ $chr == ',' ]] && (( $#reply > 0 )) && continue
# no key yet? must be a string, then
if [[ -z $key ]]; then
if [[ $chr == '"' ]]; then
json-find-matching $1 $pos || return 2
key=${${(P)1}[pos,REPLY]}
pos=$REPLY
continue
fi
# if there is no key but we didn't get one either, we can't be here!
return 2
fi
# should be a sublist, object or string - save entire thing
if [[ $chr == '"' ]] || (( $+closings[$chr] )); then
json-find-matching $1 $pos || return 2
# save it
reply+=( ${${key#\"}%\"} ${${(P)1}[pos,REPLY]} )
pos=$REPLY
# empty key
key=
continue
fi
# we should never be here!
return 1
done
}
teststr='{"a":{"b":["hi\"lo",[],{"swag":"yolo"},"lulz"],"x":{"swag":"y"}},"b":{}}'
# 1 6 11
# 1012
# object test with \"
# json-find-matching teststr 6 && echo $REPLY
# string test
# json-find-matching teststr 12 && echo $REPLY
# list test
# json-find-matching teststr 11 && echo $REPLY
# test parsing of a list
json-parse-list teststr 11 && print -l $reply
# test parsing of an object
json-parse-object teststr 6 && print -l $reply
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment