Skip to content

Commit 2ca011b

Browse files
perf: bulk-append unescaped character runs in JSON parseString
Replace per-character StringBuilder.Append(char) calls with StringBuilder.Append(string, start, length) for consecutive runs of unescaped characters in the JSON string parser. For strings with no escape sequences (the common case in real-world JSON), this reduces the number of Append calls from O(n) to O(1), which should meaningfully speed up JsonValue.Parse on workloads with many string values. The approach mirrors what JsonStringEncodeTo already does for serialisation: track a chunk start position and flush accumulated characters as a bulk substring when an escape or closing quote is hit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent bb36ab3 commit 2ca011b

2 files changed

Lines changed: 17 additions & 1 deletion

File tree

RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Release Notes
22

3+
## 8.1.4 - Mar 30 2026
4+
5+
- Performance: `JsonValue.Parse` now copies unescaped string runs in bulk instead of appending character-by-character, reducing `StringBuilder.Append` calls for strings with few or no escape sequences
6+
37
## 8.1.3 - Mar 23 2026
48

59
- Fix JSON `/* ... */` comment parser: `*` or `/` characters inside the comment body no longer cause premature termination and parse failure

src/FSharp.Data.Json.Core/JsonValue.fs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,18 @@ type private JsonParser(jsonText: string) =
315315
ensure (i < s.Length && s.[i] = '"')
316316
i <- i + 1
317317

318+
// Track start of current unescaped run; flush as a bulk chunk when an escape or end is hit.
319+
// This avoids per-character StringBuilder.Append calls for strings with few/no escapes,
320+
// which is the common case in real-world JSON.
321+
let mutable chunkStart = i
322+
323+
let inline flushChunk upTo =
324+
if upTo > chunkStart then
325+
buf.Append(s, chunkStart, upTo - chunkStart) |> ignore
326+
318327
while i < s.Length && s.[i] <> '"' do
319328
if s.[i] = '\\' then
329+
flushChunk i
320330
ensure (i + 1 < s.Length)
321331

322332
match s.[i + 1] with
@@ -371,10 +381,12 @@ type private JsonParser(jsonText: string) =
371381
| _ -> throw ()
372382

373383
i <- i + 2 // skip past \ and next char
384+
chunkStart <- i
374385
else
375-
buf.Append(s.[i]) |> ignore
376386
i <- i + 1
377387

388+
// Flush any remaining unescaped characters
389+
flushChunk i
378390
ensure (i < s.Length && s.[i] = '"')
379391
i <- i + 1
380392
let str = buf.ToString()

0 commit comments

Comments
 (0)