diff --git a/tests/FSharp.Data.Benchmarks/FSharp.Data.Benchmarks.fsproj b/tests/FSharp.Data.Benchmarks/FSharp.Data.Benchmarks.fsproj
index ae1b88d7b..f6a26bbae 100644
--- a/tests/FSharp.Data.Benchmarks/FSharp.Data.Benchmarks.fsproj
+++ b/tests/FSharp.Data.Benchmarks/FSharp.Data.Benchmarks.fsproj
@@ -14,6 +14,7 @@
PreserveNewest
+
diff --git a/tests/FSharp.Data.Benchmarks/JsonStjBenchmarks.fs b/tests/FSharp.Data.Benchmarks/JsonStjBenchmarks.fs
new file mode 100644
index 000000000..9be7c324f
--- /dev/null
+++ b/tests/FSharp.Data.Benchmarks/JsonStjBenchmarks.fs
@@ -0,0 +1,84 @@
+/// Benchmark: System.Text.Json-backed parser vs current FSharp.Data hand-written parser
+/// Prototypes "Option 2" from https://github.com/fsprojects/FSharp.Data/issues/1671 —
+/// keep the JsonValue public API while using Utf8JsonReader / JsonDocument as the parsing kernel.
+///
+/// Run with: dotnet run -c Release -- stj
+namespace FSharp.Data.Benchmarks
+
+open System.IO
+open System.Text.Json
+open BenchmarkDotNet.Attributes
+open FSharp.Data
+
+/// Converts a System.Text.Json JsonElement into a FSharp.Data JsonValue.
+/// This is the prototype STJ backend that could replace the hand-written parser.
+module private StjConverter =
+
+ let rec ofJsonElement (el: JsonElement) : JsonValue =
+ match el.ValueKind with
+ | JsonValueKind.Null -> JsonValue.Null
+ | JsonValueKind.True -> JsonValue.Boolean true
+ | JsonValueKind.False -> JsonValue.Boolean false
+ | JsonValueKind.String -> JsonValue.String(el.GetString())
+ | JsonValueKind.Number ->
+ let mutable d = 0m
+
+ if el.TryGetDecimal(&d) then
+ JsonValue.Number(d)
+ else
+ JsonValue.Float(el.GetDouble())
+ | JsonValueKind.Array ->
+ el.EnumerateArray()
+ |> Seq.map ofJsonElement
+ |> Seq.toArray
+ |> JsonValue.Array
+ | JsonValueKind.Object ->
+ el.EnumerateObject()
+ |> Seq.map (fun p -> p.Name, ofJsonElement p.Value)
+ |> Seq.toArray
+ |> JsonValue.Record
+ | _ -> JsonValue.Null
+
+ /// Parse a JSON string to JsonValue using System.Text.Json as the parsing backend.
+ let parse (text: string) : JsonValue =
+ use doc = JsonDocument.Parse(text)
+ ofJsonElement doc.RootElement
+
+/// Compares the current FSharp.Data hand-written parser with a System.Text.Json-backed
+/// prototype on three representative real-world JSON files. See issue #1671 for context.
+[]
+[]
+type JsonStjBenchmarks() =
+
+ let mutable githubJsonText = ""
+ let mutable twitterJsonText = ""
+ let mutable worldBankJsonText = ""
+
+ []
+ member _.Setup() =
+ let dataPath = Path.Combine(__SOURCE_DIRECTORY__, "../FSharp.Data.Tests/Data")
+ githubJsonText <- File.ReadAllText(Path.Combine(dataPath, "GitHub.json"))
+ twitterJsonText <- File.ReadAllText(Path.Combine(dataPath, "TwitterSample.json"))
+ worldBankJsonText <- File.ReadAllText(Path.Combine(dataPath, "WorldBank.json"))
+
+ // ── Current hand-written parser (baseline) ──────────────────────────────────────
+
+ []
+ member _.ParseGitHub_Current() = JsonValue.Parse(githubJsonText)
+
+ []
+ member _.ParseTwitter_Current() = JsonValue.Parse(twitterJsonText)
+
+ []
+ member _.ParseWorldBank_Current() = JsonValue.Parse(worldBankJsonText)
+
+ // ── STJ-backed prototype (Option 2 from #1671) ──────────────────────────────────
+
+ []
+ member _.ParseGitHub_Stj() = StjConverter.parse githubJsonText
+
+ []
+ member _.ParseTwitter_Stj() = StjConverter.parse twitterJsonText
+
+ []
+ member _.ParseWorldBank_Stj() = StjConverter.parse worldBankJsonText
diff --git a/tests/FSharp.Data.Benchmarks/Program.fs b/tests/FSharp.Data.Benchmarks/Program.fs
index 9e66baea8..a07e74048 100644
--- a/tests/FSharp.Data.Benchmarks/Program.fs
+++ b/tests/FSharp.Data.Benchmarks/Program.fs
@@ -12,11 +12,13 @@ let main args =
| [| "conversions" |] -> BenchmarkRunner.Run() |> ignore
| [| "html" |] -> BenchmarkRunner.Run() |> ignore
| [| "csv" |] -> BenchmarkRunner.Run() |> ignore
+ | [| "stj" |] -> BenchmarkRunner.Run() |> ignore
| _ ->
printfn "Running all benchmarks..."
BenchmarkRunner.Run() |> ignore
BenchmarkRunner.Run() |> ignore
BenchmarkRunner.Run() |> ignore
BenchmarkRunner.Run() |> ignore
+ BenchmarkRunner.Run() |> ignore
0
\ No newline at end of file
diff --git a/tests/FSharp.Data.Core.Tests/JsonParserProperties.fs b/tests/FSharp.Data.Core.Tests/JsonParserProperties.fs
index 7fcda85be..66a4f3b33 100644
--- a/tests/FSharp.Data.Core.Tests/JsonParserProperties.fs
+++ b/tests/FSharp.Data.Core.Tests/JsonParserProperties.fs
@@ -120,15 +120,34 @@ let unescape s =
r.Replace(s, convert)
[]
-let ``Parsing stringified JsonValue returns the same JsonValue`` () =
+let ``Parsing stringified JsonValue returns the same JsonValue`` () =
Arb.register() |> ignore
let parseStringified (json: JsonValue) =
json.ToString(JsonSaveOptions.DisableFormatting)
|> JsonValue.Parse = json
- Check.One ({Config.QuickThrowOnFailure with MaxTest = 1000},
- parseStringified)
+ Check.One({Config.QuickThrowOnFailure with MaxTest = 1000}, parseStringified)
+
+[]
+let ``Parsing JsonValue formatted with None (indented) returns the same JsonValue`` () =
+ Arb.register() |> ignore
+
+ let parseFormatted (json: JsonValue) =
+ json.ToString(JsonSaveOptions.None)
+ |> JsonValue.Parse = json
+
+ Check.One({Config.QuickThrowOnFailure with MaxTest = 500}, parseFormatted)
+
+[]
+let ``Parsing JsonValue formatted with CompactSpaceAfterComma returns the same JsonValue`` () =
+ Arb.register() |> ignore
+
+ let parseCompact (json: JsonValue) =
+ json.ToString(JsonSaveOptions.CompactSpaceAfterComma)
+ |> JsonValue.Parse = json
+
+ Check.One({Config.QuickThrowOnFailure with MaxTest = 500}, parseCompact)
[]
let ``Stringifying parsed string returns the same string`` () =