1

I am getting the following exception when I am trying to serialize a double that has the value NaN, via JsonConverter

The exception is:

System.ArgumentException: '.NET number values such as positive and negative infinity cannot be written as valid JSON. To make it work when using 'JsonSerializer', consider specifying 'JsonNumberHandling.AllowNamedFloatingPointLiterals' (see https://learn.microsoft.com/dotnet/api/system.text.json.serialization.jsonnumberhandling).'

And yes, due to the project context, I must use JsonConverter, avoiding it is not an option.

Bellow is the code:

// class to serialize

public class Subclass
{
    public double NaN { get; set; } = double.NaN;
}

// the convertor

public class SubConvertor : JsonConverter<Subclass>
{
    public override Subclass Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return new Subclass();
    }

    public override void Write(Utf8JsonWriter writer, Subclass dateTimeValue, JsonSerializerOptions options)
    {
        writer.WriteStringValue("NaaaN");
        writer.WriteNumberValue(dateTimeValue.NaN); //it does work if i would write  writer.WriteStringValue(dateTimeValue.NaN.ToString(), but the question is: are there any other more EFFICIENT solutions?
    }
}

// Program.cs

var subclass = new Subclass
{
    NaN = double.NaN // for a value like 2.3 it works without any problems
};

var serializeOptions = new JsonSerializerOptions
{
    NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals, //it does not solve the problem as the Excception above suggests
    Converters = { new SubConvertor() }
};

var jsonString = JsonSerializer.Serialize(subclass, serializeOptions);

For now I am serializing the double values as strings and after that I am reading and converting them to double. I would like to do this operations more efficient, thus this post.

4
  • You write that adding JsonNumberHandling.AllowNamedFloatingPointLiterals doesn't solve your issue, but it does, at least with the code you've given us to reproduce your issue. Commented Mar 5 at 10:13
  • It works becaue you've modififed my code. You ahve removed the "Converters = { new SubConvertor() }" I need that, as in my project I am having nested convertors. Commented Mar 5 at 10:22
  • Here's the inbuilt DoubleConverter - it simply tests for the values that require literals, and branches - github.com/dotnet/runtime/blob/main/src/libraries/… - so maybe your converter could do the same with double.IsInfinity etc, writing literals if needed; that doesn't sound inefficient. Commented Mar 5 at 10:29
  • I mean yeah, you're overriding the default converter, and as such overriding default features/ behavior of the converter, you can't expect it to still respect those options unless your converter does as well. FWIW I often find the need for custom converters an antipattern, it's a symptom that you're not properly separating entities/ domain objects from DTOs. Commented Mar 5 at 10:33

1 Answer 1

1

The reason that trying to serialize double.NaN is throwing despite setting JsonNumberHandling.AllowNamedFloatingPointLiterals is that this option is supported by the serializer but not Utf8JsonWriter or Utf8JsonReader. So when you converter calls writer.WriteNumberValue(dateTimeValue.NaN);, the writer throws.

Instead, within your converter, you need to call JsonSerializer.Serialize<double>(writer, value, options) with some appropriately configured options. If you want to use the incoming options, your converter should look like (demo #1 here):

public class SubConvertor : JsonConverter<Subclass>
{
    public override Subclass? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
        new Subclass { NaN = JsonSerializer.Deserialize<double>(ref reader, options) };

    public override void Write(Utf8JsonWriter writer, Subclass dateTimeValue, JsonSerializerOptions options) =>
        JsonSerializer.Serialize(writer, dateTimeValue.NaN, options);
}

Or if you would prefer that the converter enforce its own value for JsonNumberHandling (demo #2 here):

public class SubConvertor : JsonConverter<Subclass>
{
    static JsonSerializerOptions myOptions = new()
    {
        NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString,
    };
    
    public override Subclass? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
        new Subclass { NaN = JsonSerializer.Deserialize<double>(ref reader, myOptions) };

    public override void Write(Utf8JsonWriter writer, Subclass dateTimeValue, JsonSerializerOptions options) =>
        JsonSerializer.Serialize(writer, dateTimeValue.NaN, myOptions);
}

Once you fix the exception, you will encounter a second problem with your converter, namely that you are trying to write malformed JSON. My converter above serializes Subclass as a JSON primitive: "NaN". Your converter, by calling WriteStringValue() and then WriteNumberValue():

writer.WriteStringValue("NaaaN");
writer.WriteNumberValue(dateTimeValue.NaN);

is attempting to write a sequence of two primitive values like so: "NaaaN", "Nan". This is not allowed by the JSON standard unless you are writing the elements of an array. If you want your Subclass to be serialized as a JSON object with named properties, you must do so manually, e.g. like so:

public class SubConvertor : JsonConverter<Subclass>
{
    static JsonSerializerOptions myOptions = new()
    {
        NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString,
    };
    
    public override Subclass? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 
    {
        if (reader.TokenType == JsonTokenType.Null)
            return null;
        else if (reader.TokenType != JsonTokenType.StartObject)
            throw new JsonException();
        var subClass = new Subclass();
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndObject)
                break;
            else if (reader.TokenType != JsonTokenType.PropertyName)
                throw new JsonException();
            if (reader.ValueTextEquals("NaaaN"))
            {
                reader.Read();
                subClass.NaN = JsonSerializer.Deserialize<double>(ref reader, myOptions);
            }
            else
            {
                reader.Read();
                reader.Skip();
            }
        }
        return subClass;
    }

    public override void Write(Utf8JsonWriter writer, Subclass dateTimeValue, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        writer.WritePropertyName("NaaaN");
        JsonSerializer.Serialize(writer, dateTimeValue.NaN, myOptions);
        writer.WriteEndObject();
    }
}

Which produces the JSON {"NaaaN":"NaN"}.

Demo fiddle #3 here.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.