Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Release Notes

## 8.1.12 - Apr 28 2026

- Fix: `JsonValue.WriteTo` now always uses `CultureInfo.InvariantCulture` when serializing `Number` (decimal) values, preventing invalid JSON output (e.g. `1,5` instead of `1.5`) when called with a `TextWriter` configured with a non-English culture.
- Perf: `JsonValue.WriteTo` no longer allocates an intermediate `System.String(' ', n)` per indentation level; spaces are written directly to the writer.

## 8.1.11 - Apr 22 2026

- Code: `HtmlParser` `EmitTag` removes dead code in the `else` branch (the expression `x.HasFormattedParent || x.IsFormattedTag` was always equivalent to `x.HasFormattedParent` since `x.IsFormattedTag` is always `false` in that branch). Uses `name` directly to avoid re-computing `CurrentTagName()` for formatted/script tag checks. Also removes redundant `.ToLowerInvariant()` calls in `IsFormattedTag` and `IsScriptTag` since tag names are already lowercased at read time.
Expand Down
9 changes: 7 additions & 2 deletions src/FSharp.Data.Json.Core/JsonValue.fs
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,16 @@ type JsonValue =
member x.WriteTo(w: TextWriter, saveOptions, ?indentationSpaces: int) =
let indentationSpaces = defaultArg indentationSpaces 2

// Write `count` space characters without allocating an intermediate string.
let inline writeSpaces count =
for _ = 1 to count do
w.Write(' ')

let newLine =
if saveOptions = JsonSaveOptions.None then
fun indentation plus ->
w.WriteLine()
System.String(' ', indentation + plus) |> w.Write
writeSpaces (indentation + plus)
else
fun _ _ -> ()

Expand All @@ -94,7 +99,7 @@ type JsonValue =
function
| Null -> w.Write "null"
| Boolean b -> w.Write(if b then "true" else "false")
| Number number -> w.Write number
| Number number -> w.Write(number.ToString(CultureInfo.InvariantCulture))
| Float v when Double.IsInfinity v || Double.IsNaN v -> w.Write "null"
| Float number ->
let s = number.ToString("R", CultureInfo.InvariantCulture)
Expand Down
28 changes: 28 additions & 0 deletions tests/FSharp.Data.Core.Tests/JsonValue.fs
Original file line number Diff line number Diff line change
Expand Up @@ -859,3 +859,31 @@ let ``JsonValue WriteTo with None (default) produces indented output`` () =
let result = writer.ToString()
result.Contains("\n") |> should equal true
result.Contains(" ") |> should equal true

[<Test>]
let ``JsonValue WriteTo serializes decimals using InvariantCulture regardless of thread culture`` () =
// In cultures that use ',' as decimal separator (e.g. de-DE), TextWriter.Write(decimal)
// could produce invalid JSON like {"price":1,5} instead of {"price":1.5}.
// WriteTo must always use InvariantCulture for decimal numbers.
use _holder = withCulture "de-DE"
let json = JsonValue.Record [| "price", JsonValue.Number 1.5M |]
use writer = new System.IO.StringWriter()
json.WriteTo(writer, JsonSaveOptions.DisableFormatting)
let result = writer.ToString()
result |> should equal """{"price":1.5}"""

[<Test>]
let ``JsonValue ToString serializes decimal array using InvariantCulture`` () =
use _holder = withCulture "fr-FR"
let json = JsonValue.Array [| JsonValue.Number 1.5M; JsonValue.Number 99.99M |]
json.ToString(JsonSaveOptions.DisableFormatting)
|> should equal "[1.5,99.99]"

[<Test>]
let ``JsonValue WriteTo indentation uses correct number of spaces`` () =
let json = JsonValue.Record [| "x", JsonValue.Number 1M |]
use writer = new System.IO.StringWriter()
json.WriteTo(writer, JsonSaveOptions.None, 4)
let result = writer.ToString()
// With 4-space indent, the property line should start with 4 spaces
result |> should contain " \"x\""
Loading