.NET不同版本System.Text.Json数字序列化反序列化不一致问题咨询
问题背景
我编写了一个测试用例:将包含数字的JSON反序列化后再序列化,对比原始与序列化后的JSON字符串是否相等。项目目标框架为net8.0和net48/481。
测试结果:
- 使用
dotnet test命令:net8.0通过:原始数字5.32序列化后仍为5.32net48/481失败:原始数字5.32序列化后变为5.3200000000000003
- 使用VSCode的「Run Test」按钮测试:多数情况通过,但偶尔因相同数字问题失败,关闭VSCode及后台
dotnet.exe进程重启后通常可解决,但无法稳定复现。
咨询以下问题:
- 为何
dotnet test时net8.0通过而net48/481失败? - 为何VSCode的「Run Test」按钮有时结果不同?
- 如何确保从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.Json8.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
相关产品推荐
相关产品推荐

