Skip to content

Validate JSON scalars in place instead of materializing byte lists#8

Closed
ESRogs wants to merge 1 commit into
json-string-str-walkfrom
json-scalar-validators-str-walk
Closed

Validate JSON scalars in place instead of materializing byte lists#8
ESRogs wants to merge 1 commit into
json-string-str-walkfrom
json-scalar-validators-str-walk

Conversation

@ESRogs

@ESRogs ESRogs commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Important

Stacked on #5 (which sits on roc-lang#10173, which sits on roc-lang#10145). Merge in that order.

Overview

Completes the removal of StrList(U8) conversions from the JSON parse path. roc-lang#10173 converted whitespace trimming and scalar splitting; #5 converted string scanning and decoding; this PR converts the remaining four scalar/Dec helpers — is_json_number, json_dec_digits_are_zero, trim_json_dec_leading_zeros, and normalize_json_dec_digits. After it, the JSON parse path contains no Str.to_utf8 at all.

Detail

  • is_json_number walks the scalar's bytes in place: count_utf8_bytes + str_get_utf8_byte_unsafe replace the to_utf8 List and list_get_unsafe reads; the grammar walk is otherwise unchanged.
  • json_dec_digits_are_zero iterates lazily with for byte in Str.iter_utf8(digits).
  • trim_json_dec_leading_zeros counts leading zeros in place and takes the remainder with str_drop_first_bytes_unsafe — the dropped prefix is ASCII zeros, so the cut is a proven UTF-8 boundary (previously this revalidated the tail through from_utf8_lossy).
  • normalize_json_dec_digits splits the digit string with str_substring_unsafe / str_drop_first_bytes_unsafe at a digit boundary instead of two from_utf8_lossy reconstructions.

Allocation coverage

check_json_parse_allocs! in test/alloc-count/app.roc gains a skipped-numeric-field case on a small (SSO) document. On the base branch it fails with exactly one allocation (is_json_number's to_utf8 copy of the SSO scalar slice); with this PR the whole parse is zero allocations.

Benchmarks

Interleaved against the base branch (#5's tip; median wall time for a loop of 20,000 Json.parse calls decoding { k0 : Str }, default LLVM --opt=speed, macOS arm64):

document base this PR
500 numeric fields (499 skipped, validated by is_json_number) 1.16 s 1.13 s (-3%)
500 short string fields (control, no numeric scalars) 0.98 s 0.98 s

The modest delta is expected: to_utf8 on a slice of a heap-backed document was already zero-copy, so the win is the List wrapper and refcount traffic, plus the SSO allocation above.

Testing

JsonScalarParseEdgeCases (51), JsonEncodeNumberEdgeCases (13), JsonStringEscapes (64) all pass; the alloc-count platform app passes on the dev backend and --opt=speed including the new check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ESRogs

ESRogs commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Combined with #5 and opened upstream as roc-lang#10217 (branch json-parse-no-byte-lists). Kept for the review history.

@ESRogs ESRogs closed this Jul 18, 2026
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