json1-5 Exercise 1: Reshape Orders, Each Total Answered Two Ways =============================================================== Run in Git Bash with jq 1.8.2 (the Chapter 4 install in C:/lwdj/json/c4/bin): bash ex1.sh --- orders.json --- [ {"id": 1, "customer": "ada", "status": "shipped", "date": "2026-10-01", "items": [{"sku": "A1", "qty": 2, "price": 3.5}, {"sku": "C3", "qty": 1, "price": 1.99}]}, {"id": 2, "customer": "grace", "status": "pending", "date": "2026-10-02", "items": [{"sku": "B2", "qty": 1, "price": 4.25}]}, {"id": 3, "customer": "ada", "status": "shipped", "date": "2026-10-03", "items": [{"sku": "D4", "qty": 6, "price": 0.89}]}, {"id": 4, "customer": "linus", "status": "cancelled", "date": "2026-10-03", "items": []}, {"id": 5, "customer": "grace", "status": "shipped", "date": "2026-10-05", "items": [{"sku": "A1", "qty": 1, "price": 3.5}, {"sku": "B2", "qty": 2, "price": 4.25}, {"sku": "C3", "qty": 3, "price": 1.99}]}, {"id": 6, "customer": "ada", "status": "pending", "date": "2026-10-06", "items": [{"sku": "C3", "qty": 4, "price": 1.99}]} ] --- ex1.sh --- # Exercise 1: reshape orders.json (6 orders) and check every answer two ways (compared with -S: the same keys in a different order are the same JSON) export PATH="/c/lwdj/json/c4/bin:$PATH" pass=0; fail=0 same() { # label, filter A, filter B -> both must print the same thing local a b; a=$(jq -S -c "$2" orders.json | tr -d '\r'); b=$(jq -S -c "$3" orders.json | tr -d '\r') if [ "$a" = "$b" ]; then pass=$((pass+1)); echo "SAME $1: $a"; else fail=$((fail+1)); echo "DIFF $1"; echo " A: $a"; echo " B: $b"; fi } echo '--- 1. one summary object per order' jq -c '.[] | {id, customer, n: (.items | length), total: (.items | map(.qty * .price) | add // 0)}' orders.json echo '--- 2. spend per customer (cancelled orders excluded), two ways' same "group_by vs reduce" \ '[.[] | select(.status != "cancelled")] | group_by(.customer) | map({(.[0].customer): (map(.items | map(.qty * .price) | add) | add)}) | add' \ 'reduce (.[] | select(.status != "cancelled")) as $o ({}; .[$o.customer] += ($o.items | map(.qty * .price) | add))' echo '--- 3. orders per status, two ways' same "group_by vs reduce" 'map(.status) | group_by(.) | map({(.[0]): length}) | add' 'reduce .[].status as $s ({}; .[$s] += 1)' echo '--- 4. units sold per sku, two ways' same "group_by vs reduce" '[.[].items[]] | group_by(.sku) | map({(.[0].sku): (map(.qty) | add)}) | add' 'reduce .[].items[] as $i ({}; .[$i.sku] += $i.qty)' echo '--- 5. running count of items, order by order (foreach)' jq -c '[foreach .[] as $o (0; . + ($o.items | length); {id: $o.id, items_so_far: .})]' orders.json echo '--- 6. the busiest day, and every order on it' jq -c 'group_by(.date) | max_by(length) | {date: .[0].date, orders: map(.id)}' orders.json echo '--- control: two different answers must be reported as DIFF' same "deliberately different" 'map(.id) | add' 'map(.id) | length' echo echo "same: $pass different: $fail (the one difference is the control)" Output: --- 1. one summary object per order {"id":1,"customer":"ada","n":2,"total":8.99} {"id":2,"customer":"grace","n":1,"total":4.25} {"id":3,"customer":"ada","n":1,"total":5.34} {"id":4,"customer":"linus","n":0,"total":0} {"id":5,"customer":"grace","n":3,"total":17.97} {"id":6,"customer":"ada","n":1,"total":7.96} --- 2. spend per customer (cancelled orders excluded), two ways SAME group_by vs reduce: {"ada":22.29,"grace":22.22} --- 3. orders per status, two ways SAME group_by vs reduce: {"cancelled":1,"pending":2,"shipped":3} --- 4. units sold per sku, two ways SAME group_by vs reduce: {"A1":3,"B2":3,"C3":8,"D4":6} --- 5. running count of items, order by order (foreach) [{"id":1,"items_so_far":2},{"id":2,"items_so_far":3},{"id":3,"items_so_far":4},{"id":4,"items_so_far":4},{"id":5,"items_so_far":7},{"id":6,"items_so_far":8}] --- 6. the busiest day, and every order on it {"date":"2026-10-03","orders":[3,4]} --- control: two different answers must be reported as DIFF DIFF deliberately different A: 21 B: 6 same: 3 different: 1 (the one difference is the control) WHY THIS WORKS AS AN ANSWER --------------------------- Every "totals" question is answered by two different methods (group_by versus reduce) and a script compares them, so a SAME line means two independent filters agree. The comparison uses jq -S (sorted keys) because group_by returns keys in alphabetical order and reduce returns them in the order it first saw them: my first version compared the raw text and reported two differences that were only key order (see the chapter's mistakes). The deliberately different pair at the end (sum of ids = 21 versus number of orders = 6) is reported as DIFF, which shows the comparison can fail. Hand check of one value: ada's orders 1, 3 and 6 are 8.99 + 5.34 + 7.96 = 22.29. Floating-point sums are exact here only because these particular numbers happen to print cleanly; with other prices you could see results like 22.290000000000003. Not tested: orders with other statuses, or missing price fields.