Skip to content

Instantly share code, notes, and snippets.

@tobert
Created July 2, 2026 12:43
Show Gist options
  • Select an option

  • Save tobert/e2e7adde0701e4bf7040f0ea50f61914 to your computer and use it in GitHub Desktop.

Select an option

Save tobert/e2e7adde0701e4bf7040f0ea50f61914 to your computer and use it in GitHub Desktop.
Preview: Kaish JSON Arrays & Hashes

Preview: Kaish JSON Arrays & Hashes

Preview — landing now, not final. kaish (会sh) is a predictable shell for AI agents. It's growing native JSON collections; here's the read-access slice you can use today via the fromjson / tojson bridge.

Records & arrays

u=$(fromjson '{"name":"amy","role":"maintainer"}')
echo ${u[name]} is the ${u[role]}   # amy is the maintainer
echo "record has ${#u} keys"        # record has 2 keys
k=name; echo ${u[$k]}               # amy   ($var is a dynamic key)

xs=$(fromjson '["apple","banana","cherry"]')
echo "${xs[0]} .. ${xs[-1]}"        # apple .. cherry   (negative index)
echo ${xs[0:2]}                     # ["apple","banana"]   (end-exclusive slice → a list)

Typed, not stringly

A subscript that lands on a JSON scalar unwraps to a real value — so comparisons and arithmetic are typed, no coercion:

s=$(fromjson '{"web":{"port":8080,"healthy":false}}')
echo $(( ${s[web][port]} + 1 ))                          # 8081   (real integer math)
[[ ${s[web][healthy]} == false ]] && echo "web is down"  # typed bool → "web is down"

for svc in $(fromjson '[{"name":"web","port":8080},{"name":"api","port":9000}]'); do
  echo "${svc[name]} -> ${svc[port]}"
done
# web -> 8080
# api -> 9000

tojson $s   # serialize back out; fromjson "$(tojson $x)" round-trips $x

Loud on every mistake

The design rule is crash > corrupt: a bad access never returns a plausible wrong answer — it errors, and the error teaches the fix.

${u.name}            → kaish uses bracket access, not dots — write the key as: [name]
${u[nope]}           → no such key
${xs[9]}             → index out of bounds (list length 2)
${xs[web]}           → string key on a list — use an integer index
${u[0]}              → integer index on a record — record keys are strings, use ${u["0"]}
[[ $xs == banana ]]  → cannot compare a list to a string — test membership with `[[ x in $coll ]]`

How it works

  • fromjson / tojson are the JSON ingress/egress bridge; everything in between is a structured value, not text.
  • Brackets only — no dots. Barewords are literal keys, $var is dynamic, integers index lists. Scalars unwrap at the access boundary so == and $(( )) are typed.
  • jq is still right there for transforms and existence checks (echo $u | jq 'has("tls")') — external JSON stays external, never silently re-typed.

Still to come in this line of work: list/record literals (xs=[a b c]), push, native [[ k in $r ]] membership, and record iteration by keys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment