FsCodec icon indicating copy to clipboard operation
FsCodec copied to clipboard

[`System.Text.Json`] `NotSupportedException` when passing `obj` to `JsonSerializer.Serialize`

Open rynoV opened this issue 8 months ago • 7 comments

Dotnet version: 9.0.3 STJ: 9.0.4 FsCodec: 3.0.3

There's a strange edge case when passing an obj to JsonSerializer.Serialize:

open System.Text.Json.Serialization
open FsCodec.SystemTextJson

let s v =
    System.Text.Json.JsonSerializer.Serialize(v:>obj)

[<JsonConverter(typeof<UnionConverter<Test>>)>]
type Test =
    | Test1 of int
    | Test2 of int

do s <| Test1 0

This will fail with:

System.NotSupportedException: F# discriminated union serialization is not supported. Consider authoring a custom converter for the type. The unsupported member type is located on type 'Test+Test1'. Path: $.
 ---> System.NotSupportedException: F# discriminated union serialization is not supported. Consider authoring a custom converter for the type.
   at System.Text.Json.Serialization.Converters.UnsupportedTypeConverter`1.Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
   at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.JsonConverter`1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ThrowNotSupportedException(WriteStack& state, Exception innerException)
   at System.Text.Json.Serialization.JsonConverter`1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.Serialize(Utf8JsonWriter writer, T& rootValue, Object rootValueBoxed)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.SerializeAsObject(Utf8JsonWriter writer, Object rootValue)
   at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.Serialize(Utf8JsonWriter writer, T& rootValue, Object rootValueBoxed)
   at System.Text.Json.JsonSerializer.WriteString[TValue](TValue& value, JsonTypeInfo`1 jsonTypeInfo)
   at System.Text.Json.JsonSerializer.Serialize[TValue](TValue value, JsonSerializerOptions options)

I call it an edge case because there are a couple ways where this doesn't fail:

  • If the union is changed to have a single case (e.g. remove Test2 above), the serialization succeeds
  • If the value is not cast to obj (i.e. remove :>obj above), the serialization succeeds
  • If Options.Create(autoUnionToJsonObject = true) is passed to Serialize, the serialization succeeds even with the cast to obj

Something interesting is the error message refers to the union case of the value being serialized (Test.Test1), whereas the normal NotSupportedException will refer only to the type (Test), I believe because somewhere along the line Object.GetType() is being called on the value.

When I override UnionConverter and print when methods are called, it shows that CanConvert is called once and returns true, but the exception is thrown before Write is called.

This case comes up when using SignalR: https://github.com/dotnet/aspnetcore/blob/c946659badd12f1a632bce7d7309b3dc4dc1275f/src/SignalR/common/Protocols.Json/src/Protocol/JsonHubProtocol.cs#L917

rynoV avatar Apr 11 '25 15:04 rynoV