System.Text.Json serializes a value 1.0 of type double to an value 1 of type int by default
Asked Answered
H

2

10

I am in the process of upgrading to ASP.NET Core 3.1, after switching to System.Text.Json noticed strange result variation in floating point values. Upon investigation, I realized System.Text.Json converts a double value to int. For example, consider a simple contract:

public class RowVector
{
    [JsonPropertyName("x")]
    public double X { get; set; }

    [JsonPropertyName("y")]
    public double Y { get; set; }

    [JsonPropertyName("z")]
    public double Z { get; set; }
}

var rowVector = new RowVector { X = 1.0, Y = 213.9, Z = 112.0 };
var serializedData = JsonSerializer.Serialize(rowVector);

Serialized-Data output with System.Text.Json :{"x":1,"y":213.9,"z":112}

Here x and z are int values, whereas in Newtonsoft.Json,

Serialized-Data output with Newtonsoft.Json :{"X":1.0,"Y":213.9,"Z":112.0}

Here x and z are retained as double.

So with system.text.json, upon deserialization these values are int and this leads to variation in our processing calculation, any idea why this is implemented in such a way in System.Text.Json?

Herbarium answered 20/4, 2020 at 6:27 Comment(6)
It makes no difference what they are serialized as, they will be deserialized correctly into your RowVector class. This is not where your issue is.Restrained
I would not use System.Text.Json... it is just another attempt by Microsoft to reinvent the wheel.... It is going to be a while before Microsoft nails this project or abandons it like it has done many times before.Aparri
@IanKemp what if this going to be consumed in Javascript or other disparate system? The whole point of JSON is to be able to interact between technologies....Aparri
@IanKemp As this RowVector will be deserialized in a Python web application, it has no idea system.text.json has done this conversion. It remains as Int in PythonHerbarium
@MaheshNayak Since you didn't mention that in your question, I had no way of knowing...Restrained
I get the same issue with decimalOmnibus
S
9

What you have seen is a known portability issue with JSON:

Numbers in JSON are agnostic with regard to their representation within programming languages. While this allows for numbers of arbitrary precision to be serialized, it may lead to portability issues. For example, since no differentiation is made between integer and floating-point values, some implementations may treat 42, 42.0, and 4.2E+1 as the same number, while others may not.

Also see here:

For example, a JavaScript-based validator may accept 1.0 as an integer, whereas the Python-based jsonschema does not.

So either way, the portability issues are there when passing numbers in JSON.

You can always have your own converters to extend / modify the default behaviors of serializer.

Symphonious answered 20/4, 2020 at 7:35 Comment(3)
yes, I totally agree to the JSON portability issue, but still it would be great if System.Text.Json provides option like FloatParseHandling in the framework, rather every application writes its own custom converter for a trivial datatype like double, float.Herbarium
@MaheshNayak You could submit a feature request on Github and see what the Microsoft people say. Though you do have the workaround here if you change data type to decimal, you could serialize 1.0 / X = 1.0m as-is. But the trade-off is, you might have value cast issues.Symphonious
Yes, I have raised an issue github.com/dotnet/runtime/issues/35195 I am already working on to type cast to float in Python application which will not lead to any type cast issue.Herbarium
S
4

You can use a custom converter.

class DoubleConverter : JsonConverter<double>
{
    public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => reader.GetDouble();

    static StandardFormat f = StandardFormat.Parse("F");
    static StandardFormat g = StandardFormat.Parse("G");

    public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
    {
        Span<byte> buffer = stackalloc byte[30];
        var format = value % 1 == 0 ? f : g;
        Utf8Formatter.TryFormat(value, buffer, out var written, format);
        writer.WriteRawValue(buffer[..written]);
    }
}

Schema answered 5/9, 2022 at 15:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.