Newtonsoft.Json在.NET 5 / Core和.NET Framework上表现不同。

7
以下程序在 .NET 5(或 .NET Core)和 .NET Framework 上运行时会产生不同的结果。
为什么行为会不同呢?我不是要解决反序列化问题的解决方案;我的目标是理解发生了什么。
class Versioned 
{
    public Version V {get; set;} = new Version(1,0);
}

static void Main(string[] args)
{
    // Serialised with version Newtonsoft.Json 9.0.1
    var json = "{\"V\":{\"Major\":2,\"Minor\":0,\"Build\":-1,\"Revision\":-1,\"MajorRevision\":-1,\"MinorRevision\":-1}}";

    Console.WriteLine($".NET: {System.Environment.Version}");
    Console.WriteLine($"Json.NET: {System.Reflection.Assembly.GetAssembly(typeof(Newtonsoft.Json.JsonConvert))}");

    Console.WriteLine(json);
    
    try 
    {
        var b = Newtonsoft.Json.JsonConvert.DeserializeObject<Versioned>(json);
        Console.WriteLine(b.V);
    } 
    catch (Exception ex) { Console.WriteLine(ex.GetBaseException().Message); }
}

在.NET 5中:输出结果(为了易读性格式化):

.NET: 5.0.1
Json.NET: Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed
{
    "V": {
        "Major": 2,
        "Minor": 0,
        "Build": -1,
        "Revision": -1,
        "MajorRevision": -1,
        "MinorRevision": -1
    }
}

使用以下错误信息:

无法将当前的JSON对象(例如 {"name":"value"})反序列化为类型'System.Version',因为该类型需要一个JSON字符串值以正确反序列化。

要解决此错误,请将JSON更改为JSON字符串值,或更改反序列化类型,使其成为普通.NET类型(例如不是像整数这样的基本类型,不是像数组或列表这样的集合类型),可以从JSON对象反序列化。也可以将JsonObjectAttribute添加到类型中,以强制它从JSON对象反序列化。 路径'V.Major',行1,位置14。

在.NET Framework 4.6.2上输出如下:
.NET: 4.0.30319.42000
Json.NET: Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed
{
    "V": {
        "Major": 2,
        "Minor": 0,
        "Build": -1,
        "Revision": -1,
        "MajorRevision": -1,
        "MinorRevision": -1
    }
}
1.0

此外,在 .NET 4.6.2 上,若没有默认值,则行为又发生了变化。
class Versioned
{
    public Version V { get; set; }// = new Version(1, 0);
}

在 .NET 5.0 上,输出结果相同:

(...)
Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Version' because...

在 .NET Framework 4.6.2 上,现在的输出结果如下:

(...)
Version's parameters must be greater than or equal to zero.
Parameter name: build

据我所知,在这些 .NET 版本之间,System.Version 类在外部方面没有发生改变,这不应该影响这里的问题(我只知道 ISpanFormattable 的差异)。我查看了源码(.NET Core 版本 .NET Framework 4.8 版本),但我没有看到任何可以解释不同行为的东西。

4
如果你将Newtonsoft.Json从11版本升级到最新版本会发生什么? - Pavel Anikhouski
最新版本的Json.NET(12.0.3)的行为与v11相同。 - tymtam
1
我不确定那是如何工作的...基于.NET Framework版本没有抛出异常,我猜测它使用了反射来查找那些后备字段,并进行了一些花哨的映射以去除前导“_”。请注意,这些后备字段在.NET 5中已更改为“readonly”:这并不一定会阻止反射,但也许足以使json.net放弃。您能否尝试使用自己的Version类在.NET 5上进行测试,该类具有非只读后备字段? - canton7
有趣的是,带有readonly字段的Version的自定义版本也可以工作:https://dotnetfiddle.net/tPP3rv。我删除了`ToString`的内容,但这*不应该*影响任何东西..... - canton7
1个回答

7

看起来Json.NET在.NET Core 2.2中新增了一个VersionConverter,它知道如何正确地序列化和反序列化Version实例。当您使用.NET Core 2.2+时,它会自动被检测到并使用。

使用VersionConverter,Json.NET希望将您的Version对象序列化为像"1.0"这样的字符串,而不是JSON对象。如果您在.NET Core 2.2+上通过序列化Versioned的新实例来创建帖子中的json字符串:

var json = Newtonsoft.Json.JsonConvert.SerializeObject(new Versioned());

你会看到它返回例如 {"V":"1.0"}

同样地,VersionConverter 只知道如何读取版本字符串,比如 "1.0",不知道如何处理包含 MajorMinor 等属性的对象。

查看此问题以获取更多信息在这里查看代码运行情况


谢谢您查看此内容。我尝试找到旧的代码,但无法看出它如何运行。您可以在https://github.com/JamesNK/Newtonsoft.Json/commits/master/Src/Newtonsoft.Json/Converters/VersionConverter.cs找到没有抛出异常的旧代码。 - tymtam
旧代码只是默认的JSON(de)序列化器,我认为 - 它没有为“版本”做任何特殊处理,只是序列化其属性,并通过使用反射写入其私有字段进行反序列化。破坏性变更是添加“VersionConverter”。 - canton7

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接