From 2ca011b2ff4079dc300af9faceb547345622f7e1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 04:55:58 +0000 Subject: [PATCH 1/2] 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> --- RELEASE_NOTES.md | 4 ++++ src/FSharp.Data.Json.Core/JsonValue.fs | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 30396dcd6..02e0a4243 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,9 @@ # Release Notes +## 8.1.4 - Mar 30 2026 + +- 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 + ## 8.1.3 - Mar 23 2026 - Fix JSON `/* ... */` comment parser: `*` or `/` characters inside the comment body no longer cause premature termination and parse failure diff --git a/src/FSharp.Data.Json.Core/JsonValue.fs b/src/FSharp.Data.Json.Core/JsonValue.fs index 9f9c52b70..21be4f008 100644 --- a/src/FSharp.Data.Json.Core/JsonValue.fs +++ b/src/FSharp.Data.Json.Core/JsonValue.fs @@ -315,8 +315,18 @@ type private JsonParser(jsonText: string) = ensure (i < s.Length && s.[i] = '"') i <- i + 1 + // Track start of current unescaped run; flush as a bulk chunk when an escape or end is hit. + // This avoids per-character StringBuilder.Append calls for strings with few/no escapes, + // which is the common case in real-world JSON. + let mutable chunkStart = i + + let inline flushChunk upTo = + if upTo > chunkStart then + buf.Append(s, chunkStart, upTo - chunkStart) |> ignore + while i < s.Length && s.[i] <> '"' do if s.[i] = '\\' then + flushChunk i ensure (i + 1 < s.Length) match s.[i + 1] with @@ -371,10 +381,12 @@ type private JsonParser(jsonText: string) = | _ -> throw () i <- i + 2 // skip past \ and next char + chunkStart <- i else - buf.Append(s.[i]) |> ignore i <- i + 1 + // Flush any remaining unescaped characters + flushChunk i ensure (i < s.Length && s.[i] = '"') i <- i + 1 let str = buf.ToString() From 5c0679a4d7283b70c97338b6670a2a9335b50680 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 30 Mar 2026 04:56:00 +0000 Subject: [PATCH 2/2] ci: trigger checks