-
Notifications
You must be signed in to change notification settings - Fork 48
Description
Description:
When deserializing F# records or unions in streaming scenarios (e.g., ASP.NET Core minimal APIs), Utf8JsonReader.Skip() throws an exception when encountering unknown/unmapped properties. This is because Skip() doesn't work correctly when IsFinalBlock is false.
I discovered this issue after upgrading to .NET 10, and couldn't repro in .NET 9.
Reproduction:
I couldn't reproduce this with a unit test as it manifest streaming in scenarios. I've included a sample ASP.NET Core minimal API in my branch that demonstrates the issue:
// ASP.NET Core minimal API
type GreetingRequest = { Name: string }
let configureJsonOptions (options: JsonSerializerOptions) =
options.UnmappedMemberHandling <- JsonUnmappedMemberHandling.Skip
JsonFSharpOptions.Default().AddToJsonSerializerOptions(options)
// POST with extra property: {"Name": "World", "ExtraProperty": "ignored"}
// Expected: 200 OK (extra property skipped)
// Actual: 400 Bad Request (Skip() fails)
Root cause:
In Record.fs (line 210) and Union.fs (lines 475, 536), reader.Skip() is called to skip unknown properties. However, Utf8JsonReader.Skip() throws InvalidOperationException when IsFinalBlock is false, which I think is the case in streaming scenarios.
Proposed fix:
I have a commit ready with a fix: master...fcallejon:FSharp.SystemTextJson:master
The fix replaces reader.Skip() with a SafeSkip extension method that handles non-final blocks by manually reading and tracking depth. There might be better ways to implement this - open to suggestions:
[<Extension>]
static member SafeSkip(reader: byref<Utf8JsonReader>) =
if reader.IsFinalBlock then
reader.Skip()
else
let mutable depth = 0
reader.Read() |> ignore
match reader.TokenType with
| JsonTokenType.StartObject
| JsonTokenType.StartArray -> depth <- depth + 1
| _ -> ()
while depth > 0 && reader.Read() do
match reader.TokenType with
| JsonTokenType.StartObject
| JsonTokenType.StartArray -> depth <- depth + 1
| JsonTokenType.EndObject
| JsonTokenType.EndArray -> depth <- depth - 1
| _ -> ()Cheers!
Fernando.-