json.netprecisionmstestsystem.text.json

System.Text.Json deserializing-serializing numbers gives slightly-off numbers for some .NET versions


I have a test where a json with a number is deserialized, then serialized, then both jsons (original an deserialized-serialized) are compared for string equality. The project is built for net8.0 and net48/481.

When using dotnet test, I get the following results:

When using the little Run Test button in VScode

enter image description here

I MOSTLY get a pass, but sometimes a fail (for the same number-related reason). In that case, closing VSCode, closing all dotnet.exe background processes, then reopening VScode resolves the issue usually. Unfortunately, I don't know a way to relaibly reproduce this issue.

Questions:


Here are project file

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>net8.0-windows;net481;net48</TargetFrameworks>
    <LangVersion>10.0</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
    <PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
    <PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
    <PackageReference Include="coverlet.collector" Version="3.1.2">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'net48' Or '$(TargetFramework)' == 'net481'">
        <PackageReference Include="System.Text.Json" Version="8.0.4" />
    </ItemGroup>

</Project>

and test file

using System.Text.Json;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class NumberSerializingTests
{

    [TestMethod]
    public void TestNumberSerializing()
    {
        var json = "{\"Number\":5.32}";
        var data = JsonSerializer.Deserialize<MyNumber>("{\"Number\":5.32}");
        var json2 = JsonSerializer.Serialize(data);
        Assert.AreEqual(json, json2);
    }
}

public class MyNumber
{
    public MyNumber(double number) { Number = number; }
    public double Number { get; }
}

Solution

  • The fix for your requirements is simple:

        public MyNumber(decimal number) { Number = number; }
        public decimal Number { get; }
    

    The reasons are covered in mine/Panagiotis's comments.