Json.NET DateTimeOffset.MinValue 反序列化 DateTimeOffset 值失败,没有时区

Json.NET deserializing DateTimeOffset value fails for DateTimeOffset.MinValue without timezone

在我的 ASP.NET Core Web-API 项目中,我收到一个 HTTP POST 调用到我的 API 控制器之一。

在评估 JSON 负载并反序列化其内容时,Json.NET 偶然发现日期时间值 0001-01-01T00:00:00 并且无法将其转换为 DateTimeOffset 属性。

我注意到该值应该代表 DateTimeOffset.MinValue 的值,但它缺少时区似乎会使解串器出错。我只能想象 DateTimeOffset.Parse 试图将其转换为主机当前时区,这会导致 DateTimeOffset.MinValue.

的下溢

属性 非常简单:

[JsonProperty("revisedDate", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset? RevisedDate { get; set; }

这是发送给客户端的响应:

{
    "resource.revisedDate": [
        "Could not convert string to DateTimeOffset: 0001-01-01T00:00:00. Path 'resource.revisedDate', line 20, position 44."
    ]
}

我正在使用 Newtonsoft.Json v11.0.2,目前使用的是 UTC + 2(德国)。异常回溯和错误消息在这里:https://pastebin.com/gX9R9wq0.

我无法修复调用代码,所以我必须在我这边修复它。

但问题是:如何?

检查您的 Json.NET 版本,然后检查您的输入值和格式。我正在尝试以下示例,它对我来说工作正常:

void Main()
{
    var json = @"{""offset"":""0001-01-01T00:00:00""}";
    var ds = Newtonsoft.Json.JsonConvert.DeserializeObject<TestDS>(json);
    Console.WriteLine(ds);
}
public class TestDS {
    [Newtonsoft.Json.JsonProperty("offset", NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
    public DateTimeOffset? DSOffset { get; set; }
}

这是输出:

DSOffset 1/1/0001 12:00:00 AM -06:00

问题 似乎只有当机器的时区 TimeZoneInfo.Local 与 UTC 有正偏移时才可重现,例如(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna。我无法在具有非正偏移量的时区重现它,例如 UTC-05:00 或 UTC 本身。

具体来说,在 JsonReader.ReadDateTimeOffsetString() 中调用 DateTimeOffset.TryParse 使用 DateTimeStyles.RoundtripKind:

if (DateTimeOffset.TryParse(s, Culture, DateTimeStyles.RoundtripKind, out dt))
{
    SetToken(JsonToken.Date, dt, false);
    return dt;
}

这显然会导致具有正 UTC 偏移量的时区出现下溢错误。如果在调试器中我使用 DateTimeStyles.AssumeUniversal 进行解析,则可以避免该问题。

您可能想 report an issue 向 Newtonsoft 询问此事。只有当计算机的时区具有特定值时,特定 DateTimeOffset 字符串的反序列化才会失败这一事实似乎是错误的。

解决方法 是使用 IsoDateTimeConverter to deserialize your DateTimeOffset properties with IsoDateTimeConverter.DateTimeStyles set to DateTimeStyles.AssumeUniversal. In addition it is necessary to disable the automatic DateTime recognition built into JsonReader by setting JsonReader.DateParseHandling = DateParseHandling.None,必须在 reader 开始解析之前完成DateTimeOffset 属性的值。

首先定义如下JsonConverter:

public class FixedIsoDateTimeOffsetConverter : IsoDateTimeConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(DateTimeOffset) || objectType == typeof(DateTimeOffset?);
    }

    public FixedIsoDateTimeOffsetConverter() : base() 
    {
        this.DateTimeStyles = DateTimeStyles.AssumeUniversal;
    }
}

现在,如果您可以为您的控制器修改 JsonSerializerSettings,请使用以下设置:

var settings = new JsonSerializerSettings
{
    DateParseHandling = DateParseHandling.None,
    Converters = { new FixedIsoDateTimeOffsetConverter() },
};

如果您无法轻松修改控制器的 JsonSerializerSettings,则需要从 to 中获取 DateParseHandlingConverter 并将其与 FixedIsoDateTimeOffsetConverter 一起应用到您的模型如下:

[JsonConverter(typeof(DateParseHandlingConverter), DateParseHandling.None)]
public class RootObject
{
    [JsonProperty("revisedDate", NullValueHandling = NullValueHandling.Ignore)]
    [JsonConverter(typeof(FixedIsoDateTimeOffsetConverter))]
    public DateTimeOffset? RevisedDate { get; set; }
}

DateParseHandlingConverter 必须应用于模型本身而不是 RevisedDate 属性 因为 JsonReader 已经将 0001-01-01T00:00:00 识别为 DateTime 在调用 FixedIsoDateTimeOffsetConverter.ReadJson() 之前。

更新

, @RenéSchindhelm writes, I created an issue to let Newtonsoft know. It is Deserialization of DateTimeOffset value fails depending on system's timezone #1731.

这就是我用来解决 .NET Core 3 中的问题的方法。

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
            .AddNewtonsoftJson(options =>
            {
                options.SerializerSettings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore;
                options.SerializerSettings.DateParseHandling = DateParseHandling.None;
                options.SerializerSettings.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal });
            });
...

DateTimeOffset更改为DateTime解决了问题。