dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.23k stars 4.72k forks source link

System.Text.Json exception when trying to serialize NaN - problem with NTSTopologySuite #40140

Closed fededim closed 4 years ago

fededim commented 4 years ago

I am working on a project based asp.net core 3.1, EF Code First and NTSTopologySuite. I do have Point properties in my entites to specify geographic location, however when I try to return the entity from a standard WebApi2 controller the serialization fails with the following error:

System.ArgumentException: .NET number values such as positive and negative infinity cannot be written as valid JSON. at System.Text.Json.ThrowHelper.ThrowArgumentException_ValueNotSupported() at System.Text.Json.Utf8JsonWriter.WriteNumberValue(Double value) at System.Text.Json.Serialization.Converters.JsonConverterDouble.Write(Utf8JsonWriter writer, Double value, JsonSerializerOptions options) at System.Text.Json.JsonPropertyInfoNotNullable`4.OnWrite(WriteStackFrame& current, Utf8JsonWriter writer) at System.Text.Json.JsonPropertyInfo.Write(WriteStack& state, Utf8JsonWriter writer) at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type inputType, JsonSerializerOptions options, CancellationToken cancellationToken) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|27_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

The problem is that the Point class has some properties with NaN values. I could skip the serialization of Point properties with JsonIgnore attribute, but that's not what I need (I need to have the geographic location). Any plan to support NaN values in JSON serialization of Double ? As a matter of fact, double datatype has also this possible value and I do not see why it can't be serialized.

layomia commented 4 years ago

NaN and other floating point constants (infinity, negative infinity) are not valid for (de)serialization using System.Text.Json In ASP.NET 3.1, hence the error you are seeing.

For .NET 5.0, we've added a "number handling" feature that allows you to opt in to read and write these floating point constants - https://github.com/dotnet/runtime/pull/39363. This feature will be available in .NET preview 8, but will not be ported to 3.1

The work around for 3.1 is to add a custom converter for the double type to a JsonSerializerOptions instance, or on each property that requires this handling.

public class DoubleConverter : JsonConverter<double>
{
    public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.String && reader.GetString() == "NaN")
        {
            return double.NaN;
        }

        return reader.GetDouble(); // JsonException thrown if reader.TokenType != JsonTokenType.Number
    }

    public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
    {
        if (double.IsNaN(value))
        {
            writer.WriteStringValue("NaN");    
        }
        else
        {
            writer.WriteNumberValue(value);
        }
    }
}