You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET不同版本System.Text.Json数字序列化反序列化不一致问题咨询

问题背景

我编写了一个测试用例:将包含数字的JSON反序列化后再序列化,对比原始与序列化后的JSON字符串是否相等。项目目标框架为net8.0和net48/481。

测试结果:

  • 使用dotnet test命令:
    • net8.0通过:原始数字5.32序列化后仍为5.32
    • net48/481失败:原始数字5.32序列化后变为5.3200000000000003
  • 使用VSCode的「Run Test」按钮测试:多数情况通过,但偶尔因相同数字问题失败,关闭VSCode及后台dotnet.exe进程重启后通常可解决,但无法稳定复现。

咨询以下问题:

  1. 为何dotnet test时net8.0通过而net48/481失败?
  2. 为何VSCode的「Run Test」按钮有时结果不同?
  3. 如何确保从JSON反序列化的数字能序列化为完全相同的数字字符串?

项目文件代码:

<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>

测试文件代码:

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; }
}

解答

1. 为何dotnet test时net8.0通过而net48/481失败?

核心原因是System.Text.Json的浮点数序列化逻辑在不同框架上存在差异:

  • .NET 8中System.Text.Json做了优化,默认启用了浮点数的"无损序列化",会尽可能保留原始JSON中的数字字符串形式,避免输出二进制浮点数的精确但冗长的十进制表示;
  • .NET Framework 4.8/4.81上使用的System.Text.Json 8.0.4包,序列化double类型时直接基于其实际存储值输出——5.32无法用二进制浮点数精确存储,实际内存中是5.3200000000000003,所以序列化时会输出这个值。

2. 为何VSCode的「Run Test」按钮有时结果不同?

这是测试环境的进程复用或框架加载异常导致的:

  • VSCode的测试进程可能复用了之前运行.NET 8测试的上下文,意外使用了.NET 8的序列化逻辑;
  • 后台dotnet.exe进程的缓存可能导致框架依赖加载混乱,重启进程后缓存清空,恢复对应目标框架的正常行为;
  • 偶尔的测试并行执行也可能引发框架版本冲突,导致结果波动。

3. 如何确保从JSON反序列化的数字能序列化为完全相同的数字字符串?

有三种可靠方案:

方案一:使用decimal类型替代double

decimal是十进制浮点数,能精确存储5.32这类十进制小数,反序列化和序列化后完全匹配原始字符串。修改代码如下:

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

方案二:自定义浮点数序列化转换器

编写自定义转换器,在反序列化时记录原始数字字符串,序列化时直接复用该字符串(或指定格式化规则):

public class DoubleStringPreserveConverter : JsonConverter<double>
{
    public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return double.Parse(reader.GetString());
    }

    public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
    {
        // 使用G17格式确保输出与原始值等价的最短字符串
        writer.WriteStringValue(value.ToString("G17"));
    }
}

使用时添加转换器:

var options = new JsonSerializerOptions();
options.Converters.Add(new DoubleStringPreserveConverter());
var data = JsonSerializer.Deserialize<MyNumber>(json, options);
var json2 = JsonSerializer.Serialize(data, options);

方案三:用JsonElement保留原始结构

如果不需要强类型对象,直接用JsonElement存储反序列化结果,序列化时会完全保留原始JSON的格式:

var jsonElement = JsonSerializer.Deserialize<JsonElement>(json);
var json2 = JsonSerializer.Serialize(jsonElement);

内容的提问来源于stack exchange,提问作者Kjara

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 07:49:59