gron Session 03: Composition

The three tools together. gron discovers structure, jq extracts and reshapes, yq bridges YAML. This session builds real multi-tool pipelines.

Pre-Session State

  • Can use gron for structure discovery

  • Can extract data with jq

  • Can read YAML with yq

Lesson 1: The Discovery-to-Extraction Pattern

Pattern: gron → grep → note the path → jq extracts.

Exercise 1.1: Full workflow

# Step 1: What does this API return?
gh api 'repos/EvanusModestus/domus-captures' | gron | grep -i topic

# Step 2: Found json.topics — extract with jq
gh api 'repos/EvanusModestus/domus-captures' | jq '.topics'

Exercise 1.2: Multi-hop discovery

# Commits have nested structure — where is the date?
gh api 'repos/EvanusModestus/domus-captures/commits?per_page=1' | gron | grep date

# Found: json[0].commit.author.date AND json[0].commit.committer.date
# Extract the one you want:
gh api 'repos/EvanusModestus/domus-captures/commits?per_page=5' | jq '.[].commit.author.date'

Lesson 2: YAML → JSON → gron

Pattern: yq converts YAML to JSON, gron explores it, jq extracts.

Exercise 2.1: Explore antora.yml structure

yq -o=json '.' ~/atelier/_bibliotheca/domus-captures/docs/asciidoc/antora.yml | gron | head -30

Exercise 2.2: Find all IP-like values

yq -o=json '.' ~/atelier/_bibliotheca/domus-captures/docs/asciidoc/antora.yml | gron | grep -E '[0-9]+\.[0-9]+\.[0-9]+'

Exercise 2.3: Extract discovered paths with jq

# gron showed: json.asciidoc.attributes["ad-dc-ip"] = "10.50.1.50";
yq -o=json '.asciidoc.attributes' ~/atelier/_bibliotheca/domus-captures/docs/asciidoc/antora.yml |
  jq 'to_entries[] | select(.value | tostring | test("10\\.50")) | "\(.key) = \(.value)"' -r

Lesson 3: Cross-Repo Exploration

Exercise 3.1: Compare repo structures

# Field-level diff between two repos
diff <(gh api 'repos/EvanusModestus/domus-captures' | gron | awk -F= '{print $1}' | sort) \
     <(gh api 'repos/EvanusModestus/domus-nvim' | gron | awk -F= '{print $1}' | sort) | head -20

Exercise 3.2: Cross-repo commit activity

for repo in domus-captures domus-nvim domus-infra-ops; do
  gh api "repos/EvanusModestus/$repo/commits?per_page=3" |
    jq --arg repo "$repo" '.[].commit | {repo: $repo, date: .author.date, msg: .message | split("\n")[0]}'
done | jq -s 'sort_by(.date) | reverse | .[:10]'

Lesson 4: gron for Debugging

Exercise 4.1: Find null values

cat << 'EOF' | gron | grep null
{"users": [{"name": "alice", "email": "alice@example.com"}, {"name": "bob", "email": null}]}
EOF

Instantly shows which fields are null. In large API responses, this is faster than scanning JSON.

Exercise 4.2: Find empty arrays

cat << 'EOF' | gron | grep '\[\]'
{"data": {"results": [], "errors": [], "warnings": ["low disk"]}}
EOF

Exercise 4.3: Count depth levels

gh api 'repos/EvanusModestus/domus-captures' | gron | awk -F'.' '{print NF}' | sort -n | uniq -c | sort -rn | head -5

Shows how deep the nesting goes and where most fields live.

Lesson 5: Building a Dashboard

Exercise 5.1: Repo summary from API

gh api 'users/EvanusModestus/repos?per_page=100' |
  jq '.[] | select(.name | startswith("domus")) | {name, language, updated: .updated_at, stars: .stargazers_count}' |
  jq -s 'sort_by(.updated) | reverse' |
  jq -r '.[] | [.name, .language // "none", .updated | split("T")[0], (.stars | tostring)] | @tsv' |
  column -t

Exercise 5.2: Attribute inventory across repos

for f in ~/atelier/_bibliotheca/domus-*/docs/asciidoc/antora.yml; do
  repo=$(basename "$(dirname "$(dirname "$(dirname "$f")")")")
  yq -o=json '.asciidoc.attributes // {}' "$f" |
    gron | grep -v '= {}' | wc -l |
    xargs -I{} printf "%-30s %s attributes\n" "$repo" "{}"
done

Exercises to Complete

  1. [ ] Discover the path to a repo’s license name, then extract it with jq

  2. [ ] Build a pipeline: yq reads antora.yml → gron finds VLAN attributes → jq formats as table

  3. [ ] Find all null values in a gh api response for any endpoint

  4. [ ] Create a one-liner that shows the 5 most recently updated domus repos

Session Log

Timestamp Notes

Start

<Record when you started>

End

<Record when you finished>