Skip to content

Add JSON mutation helpers#8

Merged
hellerve merged 3 commits into
mainfrom
claude/json-mutation-helpers
Jun 22, 2026
Merged

Add JSON mutation helpers#8
hellerve merged 3 commits into
mainfrom
claude/json-mutation-helpers

Conversation

@carpentry-agent

Copy link
Copy Markdown

Summary

  • Add shallow mutation helpers: set-key, delete-key, push, set-nth for modifying JSON objects and arrays without manually unwrapping Box/Map/Array
  • Add deep mutation helpers: set-in and update-in for modifying nested structures via path traversal
  • Extend get-in to support integer array indices in paths (e.g. &[@"users" @"0" @"name"])
  • set-in automatically creates intermediate objects for missing keys
  • All functions take the JSON value by ownership and return a new value; type mismatches return the input unchanged
  • 28 new tests covering all new functions, edge cases, and mixed object/array paths

Test plan

  • All 234 tests pass (206 existing + 28 new)
  • carp-fmt -c passes on both changed files
  • angler passes with no findings

Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.

@carpentry-reviewer carpentry-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build & Tests

CI: pass (ubuntu-latest, macos-latest)
Local build: no Carp compiler available on this machine — relied on CI.
Tests: all 234 pass (206 existing + 28 new) per CI.

Findings

Reviewed json.carp in full (new get-step, modified get-in, all 8 new functions) and all 28 new tests. Checked ownership semantics, edge cases, and API consistency.

Ownership: correct throughout

All mutation functions take j by value (owned) and destructure via match, consuming the input. Non-matching branches use (ignore v) to drop unused values. set-in-at correctly copies children out via @(Array.unsafe-nth &a i) before recursing, then replaces with Array.aset. No double-free or leak risks.

Forward declaration register set-in-at (line 761): correct

Same pattern as register json-parse-value (line 309) and register serialize-internal (line 526) already in this file. Necessary for recursive self-reference within defmodule.

get-in behavioral change: safe but technically breaking

Old behavior: get-in on arrays always returned Nothing (it only called JSON.get, which is object-only). New behavior: get-step dispatches on node type — Arr nodes try Int.from-string on the key, everything else falls through to JSON.get. Numeric-string keys on objects still work correctly (the array path only fires for Arr nodes).

This is the right design, but it is technically a breaking change — anyone relying on get-in returning Nothing for arrays would see different behavior. Worth noting for versioning (minor bump).

Edge cases to consider

  1. set-in with empty path — returns v, discarding j (replaces root). Reasonable but undocumented and untested. Consider adding one test to document this behavior.

  2. Mid-path type mismatch — e.g. (JSON.set-in (JSON.obj [("a", Num 1.0)]) &[@"a" @"b"] v) where "a" maps to a number, not a container. The code silently discards v and returns unchanged. This is consistent with set-key/set-nth semantics (type mismatch = no-op), but the set-in doc says "missing intermediate object keys are created automatically" which could mislead users into expecting it also handles non-container intermediates. Consider adding a brief note or a test.

  3. Negative indices — correctly rejected at all levels (get-step:692, set-nth:754, set-in-at:770). No test for negative string indices in get-in (e.g. @"-1"), though the code handles it correctly.

update-in double traversal

get-in (borrow + copy) then set-in (consume + rebuild) is O(2 * depth). Fine for practical JSON tree depths. No correctness issue.

API consistency: good

Naming matches Carp stdlib conventions (set-nth, push). Readers borrow (&JSON), transformers consume (owned JSON). Doc strings match the terse lowercase style used elsewhere in the file.

No CHANGELOG exists in this repo.

Verdict: merge

Clean, well-structured addition. Ownership is correct, tests are solid, API is consistent. The get-in behavioral change and undocumented edge cases (empty path, mid-path type mismatch) are worth noting for the release but are not blockers.

@hellerve hellerve marked this pull request as ready for review June 20, 2026 19:14

@hellerve hellerve left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase and adjust comment style. No fences etc.

@carpentry-agent

Copy link
Copy Markdown
Author

Addressed @hellerve's feedback:

  • Rebase on main: branch was already up to date with main, no conflicts.
  • Remove markdown fences from doc strings: removed ``` fences from get-in, obj, set-in, and update-in doc strings. Code examples are now plain indented text, matching the style used by other doc strings in the file (e.g. parse, str, pretty-str).

The top-level module doc ((doc JSON ...)) still uses fences for the installation snippet — left those alone since they're outside this PR's scope and serve a different purpose.

@carpentry-reviewer carpentry-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build & Tests

CI: pass (ubuntu-latest, macos-latest)
Local build: no Carp compiler available on this machine — relied on CI.
Tests: all pass per CI (206 existing + 28 new).

Prior feedback

hellerve requested: rebase on main and adjust comment style (remove markdown fences from doc strings). Both addressed:

  • Rebase: branch was already up to date with main, no conflicts.
  • Markdown fences: removed from get-in and obj doc strings. All new doc strings (set-key, delete-key, push, set-nth, set-in, update-in) use the terse indented-example style matching the rest of the file. The top-level (doc JSON ...) module doc still has fences for the installation snippet — correctly left alone (not in scope).

Previous reviewer (carpentry-reviewer) found no bugs and recommended merge. I've independently verified the code below.

Findings

Reviewed all 8 new/modified functions and 28 new tests in full.

Ownership: correct throughout. All mutation functions consume j via match, ignore unused owned values (v, f) on fallback paths, and never double-free. set-in-at correctly copies children out via @(Array.unsafe-nth &a i) + Box.unbox before recursing, then replaces with Array.aset. The forward register set-in-at pattern matches existing serialize-internal and json-parse-value usage.

get-in behavioral change: safe. New get-step helper dispatches on node type — Arr nodes try Int.from-string, everything else falls through to JSON.get. Negative indices correctly rejected. Numeric-string keys on objects still work (the array path only fires for Arr nodes).

API consistency: good. Naming matches stdlib conventions (set-nth, push). Borrowing vs. consuming follows the existing pattern (readers borrow, transformers consume).

Minor test gaps (non-blocking):

  • No test for set-nth with negative index (code handles it via >= i 0, but untested)
  • No test for get-in/set-in with empty path (both work correctly — empty path returns root / replaces root)
  • No test for negative numeric string path element on arrays (e.g. @"-1")

These are edge cases the code handles correctly; tests would just document the behavior.

Verdict: merge

Style fixes are applied. Code is clean and correct. Ownership is handled properly on every path. Test coverage is solid. The minor test gaps are suggestions, not blockers.

@hellerve hellerve left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I have to tell you no fence comments one more time this issue will be closed.

@carpentry-agent

Copy link
Copy Markdown
Author

Addressed @hellerve's feedback: removed all separator/fence comments (; ===...=== and ; ---...--- lines) from the test file. Section title comments are kept as plain ; Section name lines. No markdown fences remain in any PR-added doc strings.

@carpentry-reviewer carpentry-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build & Tests

CI: pass (ubuntu-latest + macos-latest)
Local build: no Carp compiler available — relied on CI.
Tests: all 234 pass per CI.

Prior feedback

hellerve's last comment warned the PR would be closed if fence comments appeared again. This commit removes them:

  • All ; ===...=== and ; ---...--- separator banners removed from test/json.carp. Section labels kept as plain ; Section name lines. Verified: grep -n '===\|---' test/json.carp returns nothing. ✓
  • Markdown triple-backtick fences removed from get-in and obj doc strings. All 8 new function doc strings use terse indented-example style matching parse, str, pretty-str. ✓
  • Top-level (doc JSON ...) module doc fences untouched (not in scope). ✓

Prior code reviews (two rounds) confirmed: ownership correct throughout, set-in-at forward declaration matches existing patterns, get-in behavioral change is safe, API naming follows stdlib conventions.

Findings

None new. The latest commit is purely comment/doc-string style cleanup — no code changes. Verified by inspecting the diff against the prior commit.

Verdict: merge

All separator and fence comments are gone. Code was already approved in prior rounds. Style now matches the repo.

@hellerve hellerve left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A rebase should help clearing things up.

Comment thread test/json.carp Outdated
(use Test)

; ---------------------------------------------------------------------------
; Helpers

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the section comments, not just the fence surrounding it.

carpentry-heartbeat[bot] added 3 commits June 22, 2026 09:14
set-key, delete-key, push, set-nth for shallow modification of objects
and arrays. set-in and update-in for deep nested modification. get-in
now supports integer array indices in paths.
@carpentry-agent carpentry-agent Bot force-pushed the claude/json-mutation-helpers branch from 3156b43 to ad90cf9 Compare June 22, 2026 07:17
@carpentry-agent

Copy link
Copy Markdown
Author

Addressed @hellerve's feedback:

  • Rebased onto main — resolved conflicts with the "Declutter comments" commit (cf0c801) and the escape-json-str optimization (Optimize escape-json-str to eliminate per-character allocations #9). The separator-removal commit was dropped since main already handled it.
  • Removed all section and fence comments from new test code — no more ; ===...=== fences, no more ; Section name headers. The test descriptions in each assert carry the context. Matches the style established on main.

@carpentry-reviewer carpentry-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Build & Tests

CI: pass (ubuntu-latest, macos-latest) — all 234 tests pass.
Local build: no Carp compiler available on this machine — relied on CI.

Prior feedback

hellerve requested three things across 4 review rounds:

  1. Remove markdown fences from doc strings — done. All 8 new function doc strings (get-step, get-in, set-key, delete-key, push, set-nth, set-in, update-in) use terse indented-example style matching parse, str, pretty-str. Verified: no triple-backtick fences in any PR-added doc strings. The top-level (doc JSON ...) module doc fences are untouched (not in scope). ✓
  2. Remove section/fence comments from tests — done. No ; ===...===, ; ---...---, or ; Section name lines remain in the new test code. The test descriptions in each assert carry the context. grep -n '^;' test/json.carp returns nothing. ✓
  3. Rebase onto main — done. Branch includes all of main, including cf0c801 (Declutter comments) and 74450d5 (#9, escape-json-str optimization). git merge-base --is-ancestor main claude/json-mutation-helpers confirms. ✓

hellerve's inline comment ("Remove the section comments, not just the fence surrounding it.") was addressed in the latest commit.

Findings

Reviewed all 8 new/modified functions in json.carp (lines 682-798) and all 28 new tests in test/json.carp (lines 925-1147).

Ownership: correct throughout. All mutation functions consume j via owned match, ignore unused owned values on fallback paths, and never double-free. set-in-at correctly copies children out via @(Array.unsafe-nth &a i) + Box.unbox before recursing, then replaces with Array.aset. The forward register set-in-at pattern matches existing json-parse-value (line 309) and serialize-internal (line 519).

get-in behavioral change: safe. New get-step helper (line 684) dispatches on node type — Arr nodes try Int.from-string, everything else falls through to JSON.get. Negative indices correctly rejected (>= i 0 guard, line 688). Numeric-string keys on objects still work because the array path only fires for Arr nodes.

API consistency: good. Naming matches stdlib (set-nth, push). Borrowing vs. consuming follows existing pattern (readers borrow, transformers consume). Doc strings match the terse lowercase style.

No bugs found. No CHANGELOG in this repo.

Verdict: merge

All of hellerve's feedback from 4 rounds has been addressed: branch is rebased, fences removed, section comments removed. Code is clean and correct. Ownership is handled properly on every path. Test coverage is solid (28 new tests).

@hellerve hellerve merged commit 6ee8156 into main Jun 22, 2026
2 checks passed
@hellerve hellerve deleted the claude/json-mutation-helpers branch June 22, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant