Skip to content

The JSON returned by SignalR does not contain the $type field #52342

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 task done
TonEnfer opened this issue Nov 24, 2023 · 2 comments · Fixed by #53035
Closed
1 task done

The JSON returned by SignalR does not contain the $type field #52342

TonEnfer opened this issue Nov 24, 2023 · 2 comments · Fixed by #53035
Labels
area-signalr Includes: SignalR clients and servers
Milestone

Comments

@TonEnfer
Copy link

TonEnfer commented Nov 24, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

When serializing descendant types, for methods that take a single (not a collection) parameter of the base type as an argument, the discriminator field is not added to JSON.
JSON for collection argument:

{
    "type": 1,
    "target": "WeatherForecast",
    "arguments": [
        [
            {
                "$type": "extended",
                "temperatureC": -15,
                "summary": "Freezing",
                "date": "2023-11-24"
            },
            {
                "$type": "summary",
                "summary": "Mild",
                "date": "2022-11-20"
            }
        ]
    ]
}

JSON for single argument:

{
    "type": 1,
    "target": "SummaryWeatherForecast",
    "arguments": [
        {
            "summary": "Mild",
            "date": "2023-11-24"
        }
    ]
}
{
    "type": 1,
    "target": "ExtendedWeatherForecast",
    "arguments": [
        {
            "temperatureC": -15,
            "summary": "Freezing",
            "date": "2023-11-24"
        }
    ]
}

This is similar to the problem described previously in #44852

Expected Behavior

I expect SignalR JSON to contain a $type field similar to responses from HTTP endpoints:

{
  "$type": "summary",
  "summary": "Mild",
  "date": "2023-11-24"
}
{
  "$type": "extended",
  "temperatureC": -15,
  "summary": "Freezing",
  "date": "2023-11-24"
}

Steps To Reproduce

Repository with code showing the problem.

I used the following classes to show the problem:

[JsonPolymorphic]
[JsonDerivedType(typeof(SummaryWeatherForecast), "summary")]
[JsonDerivedType(typeof(ExtendedWeatherForecast), "extended")]
public class WeatherForecast
{
    public DateOnly Date { get; set; }
}

public class SummaryWeatherForecast(string summary): WeatherForecast
{
    public string Summary { get; } = summary;
}

public class ExtendedWeatherForecast(string summary, int temperatureC): SummaryWeatherForecast(summary)
{
    public int TemperatureC { get; } = temperatureC;
}
public class WeatherForecastHub: Hub<IWeatherForecastHub>;

[SuppressMessage("ReSharper", "AsyncApostle.AsyncMethodNamingHighlighting")]
public interface IWeatherForecastHub
{
    Task SummaryWeatherForecast(WeatherForecast summaryWeatherForecast);

    Task ExtendedWeatherForecast(WeatherForecast extendedWeatherForecast);

    Task WeatherForecast(WeatherForecast[] extendedWeatherForecast);
}
[ApiController]
[Route("[controller]")]
public class WeatherForecastController(IHubContext<WeatherForecastHub, IWeatherForecastHub> hubContext): ControllerBase
{
    [HttpGet]
    public async Task<IEnumerable<WeatherForecast>> Get()
    {
        var forecast1 = new ExtendedWeatherForecast("Freezing", -15)
        {
            Date = DateOnly.FromDateTime(DateTime.UtcNow)
        };

        var forecast2 = new SummaryWeatherForecast("Mild") { Date = DateOnly.Parse("2022-11-20") };

        var result = new WeatherForecast[] { forecast1, forecast2 };

        await hubContext.Clients.All.WeatherForecast(result);

        return result;
    }

    [HttpGet("Summary")]
    public async Task<WeatherForecast> GetSummary()
    {
        var forecast2 = new SummaryWeatherForecast("Mild")
        {
            Date = DateOnly.FromDateTime(DateTime.UtcNow)
        };

        await hubContext.Clients.All.SummaryWeatherForecast(forecast2);

        return forecast2;
    }

    [HttpGet("Extended")]
    public async Task<WeatherForecast> GetExtended()
    {
        var forecast1 = new ExtendedWeatherForecast("Freezing", -15)
        {
            Date = DateOnly.FromDateTime(DateTime.UtcNow)
        };

        await hubContext.Clients.All.ExtendedWeatherForecast(forecast1);

        return forecast1;
    }
}

Exceptions (if any)

No response

.NET Version

8.0.100

Anything else?

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels label Nov 24, 2023
@TonEnfer TonEnfer changed the title The result returned by SignarR does not contain the $type field The result returned by SignalR does not contain the $type field Nov 24, 2023
@TonEnfer TonEnfer changed the title The result returned by SignalR does not contain the $type field The JSON returned by SignalR does not contain the $type field Nov 24, 2023
@Kahbazi Kahbazi added area-signalr Includes: SignalR clients and servers and removed old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels labels Nov 25, 2023
@Kahbazi
Copy link
Member

Kahbazi commented Nov 26, 2023

I did a little investigate and I think these line is causing this issue.

JsonSerializer.Serialize(writer, message.Result, message.Result.GetType(), _payloadSerializerOptions);

JsonSerializer.Serialize(writer, message.Item, message.Item.GetType(), _payloadSerializerOptions);

JsonSerializer.Serialize(writer, argument, argument.GetType(), _payloadSerializerOptions);

The serializer is using the instance type (which is the derived type) to serialize and I assume it has no information of the base class. Perhaps a fix is to add the argument type along side the argument list.

@acidbubbles
Copy link

If other people hit this problem, here is an indirect workaround: dotnet/runtime#77532 (comment)

Using such a custom resolver can force the type discriminator, It's not a pretty solution but it's better than the alternatives (pre-serializing, which means every parameter becomes a string; using a custom JSON converter on every implementation; passing an array of the base type; etc.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-signalr Includes: SignalR clients and servers
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants