Search code examples
c#jsonserializationsignalr.net-6.0

SignalR switching from NewtonsoftJsonProtocol to JsonProtocol throws System.Text.Json.JsonException: '{' is invalid after a value


In .Net 6, when trying to use SignalR with JsonProtocol (System.Text.Json), my client program throws the following exception

System.IO.InvalidDataException: Error reading JSON.
   ---> System.Text.Json.JsonException: '{' is invalid after a value. Expected either ',', '}', or ']'. Path: $ | LineNumber: 0 | BytePositionInLine: 4090

Code on server side:

services.AddSignalR()
    .AddJsonProtocol();

Code on client side:

conn = new HubConnectionBuilder()
        .WithUrl($"{_serverBaseAddress}mainHub", o =>
            {
                o.AccessTokenProvider = InitializeOrRefreshAccessTokenIfNeeded;
                o.Transports = HttpTransportType.WebSockets;
            }
        )
        .ConfigureLogging(logging =>
        {
            logging.AddConsole();
        })
        .AddJsonProtocol()
        .Build()

Note: everything works fine when using NewtonsoftJsonProtocol:

Code on server:

services.AddSignalR()
        .AddNewtonsoftJsonProtocol(opts =>
        {
            opts.PayloadSerializerSettings.TypeNameHandling = TypeNameHandling.All;
        });

Code on client:

conn = new HubConnectionBuilder()
        .WithUrl($"{_serverBaseAddress}mainHub", o =>
            {
                o.AccessTokenProvider = InitializeOrRefreshAccessTokenIfNeeded;
                o.Transports = HttpTransportType.WebSockets;
            }
        )
        .ConfigureLogging(logging =>
        {
            logging.AddConsole();
        })
        .AddNewtonsoftJsonProtocol(
        opts =>
            opts.PayloadSerializerSettings.TypeNameHandling = TypeNameHandling.All
        )
        .Build();

There is a difference, with NewtonsoftJson I specify TypeNameHandling.All but I do not know any equivalent for System.Text.Json.

Why not just keep Newtonsoft.Json? I would like to use source generators and specify a SerializerContext like below:

.AddJsonProtocol(options =>
{
    options.PayloadSerializerOptions.AddContext<MyJsonSerializerContext>();
});

which I believe I cannot do with NewtonsoftJson


Solution

  • As it turned out, switching from Newtonsoft.Json to System.Text.Json is not as simple as just switching protocols in the configuration.

    There is this page from Microsoft that describes the key differences between the 2 and helps with the migration: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-6-0

    As they explain, in some scenarios it will not be possible to move to System.Text.Json.

    So in my case, I was looking at errors on the client but actually, the server was throwing a cycling error like this one: How to JSON serialize without cyclic error Replacing [IgnoreDataMember] by [JsonIgnore] fixes the issue.

    Also TypeNameHandling.All which I was using in Newtonsoft.Json is not supported in System.Text.Json. I was using this setting because of polymorphic deserialization, but in this case, they propose to write custom converters as a workaround: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-6-0#support-polymorphic-deserialization