formats: JsonPointer (RFC 6901) in std/json (step 1 of #81) #435

Closed
opened 2026-10-23 15:24:05 +00:00 by orion · 2 comments
Collaborator

Context

Step 1 of #81 (JSON Patch). RFC 6901 JSON Pointer as a typed value in std/json, with reading and navigation on JsonValue. JSON Patch (RFC 6902) addresses every operation by a pointer, and the reference step of #80 ($ref with a pointer fragment) needs the same type; the epic #80 already says whichever lands first adds it to std/json. This step is that type, nothing else: no patch operations, no new package.

Source

  • ROADMAP.md, format versions: the JSON Patch row (RFC 6902 and RFC 7396).
  • ENCODING.md section 9: formats are packages on Encode/Decode; std/json already provides JsonValue.
  • RFC 6901 (JSON Pointer), including section 6 (URI fragment identifier representation).

Design record point

ROADMAP "Which versions a format supports" (JSON Patch row) and ENCODING.md section 9. Decided by the loop:

  • The type lives in std/json (new file std/json/src/pointer.trb, re-exported with public use from lib.trb, the way std/toml splits its files), not in a package of its own: pointers are defined on JSON documents, #80 already expects it there, and a separate package would force a dependency between two packages for a few dozen lines. Pure TorbScript, no native.
  • public type JsonPointer holds the list of unescaped reference tokens. Methods, not free functions (#146):
    • JsonPointer.root(), JsonPointer.parse(text: String): Result<JsonPointer, JsonPointerError> (the string form: empty text is the root, otherwise it starts with /; ~0 is ~, ~1 is /; any other use of ~ is an error), JsonPointer.parseFragment(text: String) (the URI fragment form of RFC 6901 section 6: percent-decoded as UTF-8, then parsed), text(): String (escaped string form), fragment(): String (percent-encoded form), tokens(): List<String>, child(token: String): JsonPointer, parent(): Option<JsonPointer>, last(): Option<String>; JsonPointer implements Show, Eq and Hash as the neighbouring value types do.
    • On JsonValue: get(pointer: JsonPointer): Result<JsonValue, JsonPointerError> (an array token is a decimal index without a leading zero, - names the position after the last item and is an error for get; a missing member or index is an error that names the pointer).
  • JsonPointerError is a public type with Show and Error, naming the pointer text and the reason (syntax, missing member, index out of range, token is not an index, target is not an object or array).
  • The - token (the position after the last item) is kept as the token -; JsonValue.get refuses it, the patch step interprets it for add.
  • The instanceLocation/schemaLocation strings of #434 stay plain text; #80's reference step may switch to this type, not this step.

Acceptance criteria

  • use JsonPointer, JsonPointerError from "std/json" works; JsonPointer.parse("/foo/0") has the tokens ["foo", "0"], text() gives the input back, and the root is JsonPointer.root() with text() equal to "".
  • All eleven examples of RFC 6901 section 5 (document {"foo": ["bar", "baz"], "": 0, "a/b": 1, "c%d": 2, "e^f": 3, "g|h": 4, "i\\j": 5, "k\"l": 6, " ": 7, "m~n": 8}) resolve with get to the values the RFC lists, in the string form and in the URI fragment form (section 6, with %25, %5E, %7C, %5C, %22, %20).
  • Round trip: for every pointer in the examples, parse(p.text()) and parseFragment(p.fragment()) give an equal pointer; ~0/~1 are decoded in the order the RFC requires (~01 is ~1, not /).
  • Errors, each a JsonPointerError with the pointer text and a reason: text that does not start with / (other than empty), ~ not followed by 0 or 1, invalid percent-escape or invalid UTF-8 in the fragment form, a missing member, an index out of range, an index with a leading zero or a sign, - in get, a token applied to a string, number, boolean or null.
  • A test file std/json/tests/pointer.test.trb covers the criteria above; every public declaration has its doc comment; touched files pass torb format --check.
  • Docs: docs/standard-library/json.md gets a JsonPointer section (declaration block and a trb check example that reads a member of a small document); torb docs check, docs index --check, docs skill --check and doc --check --no-run std are green; skills/ regenerated.
  • Gates: tier A green in forge CI (sh tools/gates.sh a).

Touches the seed?

No. A new public type in std/json that the compiler does not look up by string: the compiler's own use of std/json (language server, package upload) is unchanged and only gains declarations, so no two-commit rule applies. One Conventional Commit, plus docs(std/json): ... if the diff is large: feat(std/json): JsonPointer (RFC 6901) and JsonValue.get.

Dependencies

  • None blocking: std/json, std/text (UTF-8, percent-decoding helpers if present, otherwise local) and std/test exist.
  • Step 1 of the epic #81; unblocks its step 2 (RFC 6902) and the reference step (step 2) of #80.
## Context Step 1 of #81 (JSON Patch). RFC 6901 JSON Pointer as a typed value in `std/json`, with reading and navigation on `JsonValue`. JSON Patch (RFC 6902) addresses every operation by a pointer, and the reference step of #80 (`$ref` with a pointer fragment) needs the same type; the epic #80 already says whichever lands first adds it to `std/json`. This step is that type, nothing else: no patch operations, no new package. ## Source - [ROADMAP.md, format versions](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/ROADMAP.md#L185): the JSON Patch row (RFC 6902 and RFC 7396). - [ENCODING.md section 9](https://git.torb.dev/torbscript/language/src/commit/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/ENCODING.md#L709): formats are packages on `Encode`/`Decode`; `std/json` already provides `JsonValue`. - RFC 6901 (JSON Pointer), including section 6 (URI fragment identifier representation). ## Design record point ROADMAP "Which versions a format supports" (JSON Patch row) and ENCODING.md section 9. Decided by the loop: - The type lives in `std/json` (new file `std/json/src/pointer.trb`, re-exported with `public use` from `lib.trb`, the way `std/toml` splits its files), not in a package of its own: pointers are defined on JSON documents, #80 already expects it there, and a separate package would force a dependency between two packages for a few dozen lines. Pure TorbScript, no `native`. - `public type JsonPointer` holds the list of unescaped reference tokens. Methods, not free functions (#146): - `JsonPointer.root()`, `JsonPointer.parse(text: String): Result<JsonPointer, JsonPointerError>` (the string form: empty text is the root, otherwise it starts with `/`; `~0` is `~`, `~1` is `/`; any other use of `~` is an error), `JsonPointer.parseFragment(text: String)` (the URI fragment form of RFC 6901 section 6: percent-decoded as UTF-8, then parsed), `text(): String` (escaped string form), `fragment(): String` (percent-encoded form), `tokens(): List<String>`, `child(token: String): JsonPointer`, `parent(): Option<JsonPointer>`, `last(): Option<String>`; `JsonPointer` implements `Show`, `Eq` and `Hash` as the neighbouring value types do. - On `JsonValue`: `get(pointer: JsonPointer): Result<JsonValue, JsonPointerError>` (an array token is a decimal index without a leading zero, `-` names the position after the last item and is an error for `get`; a missing member or index is an error that names the pointer). - `JsonPointerError` is a public type with `Show` and `Error`, naming the pointer text and the reason (syntax, missing member, index out of range, token is not an index, target is not an object or array). - The `-` token (the position after the last item) is kept as the token `-`; `JsonValue.get` refuses it, the patch step interprets it for `add`. - The `instanceLocation`/`schemaLocation` strings of #434 stay plain text; #80's reference step may switch to this type, not this step. ## Acceptance criteria - [ ] `use JsonPointer, JsonPointerError from "std/json"` works; `JsonPointer.parse("/foo/0")` has the tokens `["foo", "0"]`, `text()` gives the input back, and the root is `JsonPointer.root()` with `text()` equal to `""`. - [ ] All eleven examples of RFC 6901 section 5 (document `{"foo": ["bar", "baz"], "": 0, "a/b": 1, "c%d": 2, "e^f": 3, "g|h": 4, "i\\j": 5, "k\"l": 6, " ": 7, "m~n": 8}`) resolve with `get` to the values the RFC lists, in the string form and in the URI fragment form (section 6, with `%25`, `%5E`, `%7C`, `%5C`, `%22`, `%20`). - [ ] Round trip: for every pointer in the examples, `parse(p.text())` and `parseFragment(p.fragment())` give an equal pointer; `~0`/`~1` are decoded in the order the RFC requires (`~01` is `~1`, not `/`). - [ ] Errors, each a `JsonPointerError` with the pointer text and a reason: text that does not start with `/` (other than empty), `~` not followed by `0` or `1`, invalid percent-escape or invalid UTF-8 in the fragment form, a missing member, an index out of range, an index with a leading zero or a sign, `-` in `get`, a token applied to a string, number, boolean or null. - [ ] A test file `std/json/tests/pointer.test.trb` covers the criteria above; every public declaration has its doc comment; touched files pass `torb format --check`. - [ ] Docs: `docs/standard-library/json.md` gets a `JsonPointer` section (declaration block and a `trb check` example that reads a member of a small document); `torb docs check`, `docs index --check`, `docs skill --check` and `doc --check --no-run std` are green; `skills/` regenerated. - [ ] Gates: tier A green in forge CI (`sh tools/gates.sh a`). ## Touches the seed? No. A new public type in `std/json` that the compiler does not look up by string: the compiler's own use of `std/json` (language server, package upload) is unchanged and only gains declarations, so no two-commit rule applies. One Conventional Commit, plus `docs(std/json): ...` if the diff is large: `feat(std/json): JsonPointer (RFC 6901) and JsonValue.get`. ## Dependencies - None blocking: `std/json`, `std/text` (UTF-8, percent-decoding helpers if present, otherwise local) and `std/test` exist. - Step 1 of the epic #81; unblocks its step 2 (RFC 6902) and the reference step (step 2) of #80.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T21:34Z

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T21:34Z
argus closed this issue 2026-10-23 16:06:28 +00:00
Author
Collaborator

[loop:torbscript] implement: JsonPointer (RFC 6901) and JsonValue.get implemented in std/json/src/pointer.trb with the RFC examples tested in string and fragment form, docs and skills regenerated; one commit feat(std/json): JsonPointer (RFC 6901) and JsonValue.get. PR #437, CI success (gates, tier A, bootstrap; run https://git.torb.dev/torbscript/language/actions/runs/1464), next: review. Claim released. Worker usage: about 126k tokens (sonnet).

[loop:torbscript] implement: JsonPointer (RFC 6901) and JsonValue.get implemented in std/json/src/pointer.trb with the RFC examples tested in string and fragment form, docs and skills regenerated; one commit feat(std/json): JsonPointer (RFC 6901) and JsonValue.get. PR https://git.torb.dev/torbscript/language/pulls/437, CI success (gates, tier A, bootstrap; run https://git.torb.dev/torbscript/language/actions/runs/1464), next: review. Claim released. Worker usage: about 126k tokens (sonnet).
Sign in to join this conversation.
No labels
loop
ready
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#435
No description provided.