【问题标题】:Json.NET deserializing DateTimeOffset value fails for DateTimeOffset.MinValue without timezone对于没有时区的 DateTimeOffset.MinValue,Json.NET 反序列化 DateTimeOffset 值失败
【发布时间】:2018-05-31 15:58:17
【问题描述】:

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

在评估 JSON 负载并反序列化其内容时,Json.NET 偶然发现了 0001-01-01T00:00:00 的 DateTime 值,并且无法将其转换为 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

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

但问题是:如何?

【问题讨论】:

  • 无法复制,请参阅dotnetfiddle.net/krfPNn。也无法使用 Json.NET 版本 10 或 11.2 在 UTC-05:00 中重现。你在哪个时区?你使用的是什么版本的 Json.NET?
  • 我正在使用 Newtonsoft.Json v11.0.2,目前在 UTC + 2(德国)。
  • 好吧,如果问题只能在与 UTC 有正偏移的时区重现,那就太尴尬了。您能否在控制台应用程序中重现此情况并共享异常的完整 ToString() 输出,包括异常类型、消息、回溯和内部异常(如果有)?
  • 这里是例外pastebin.com/gX9R9wq0
  • @dbc 这是您的演示中的异常输出pastebin.com/iAuXaJN6

标签: c# json.net asp.net-core-mvc deserialization datetimeoffset


【解决方案1】:

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

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

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 反序列化您的DateTimeOffset 属性,并将IsoDateTimeConverter.DateTimeStyles 设置为DateTimeStyles.AssumeUniversal。此外,有必要通过设置JsonReader.DateParseHandling = DateParseHandling.None 来禁用JsonReader 中内置的自动DateTime 识别,这必须在阅读器开始解析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,则需要将 DateParseHandlingConverterthis answer 抓取到 How to prevent a single object property from being converted to a DateTime when it is a string 并将其与 FixedIsoDateTimeOffsetConverter 应用到您的模型中,如下所示:

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

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

更新

comments@RenéSchindhelm 写道,我创建了一个问题让 Newtonsoft 知道。它是Deserialization of DateTimeOffset value fails depending on system's timezone #1731

【讨论】:

  • 解决方法有效。我created an issue 让 Newtonsoft 知道。也许他们想出了一个解决办法。我真诚地感谢 dbc 在这里提供这种高质量的帮助。 +1
  • 我猜在 31-12-9999 23:59:59 日期为负时区也会在解析时抛出异常
【解决方案2】:

这是我用来解决 .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 });
            });
...

【讨论】:

    【解决方案3】:

    DateTimeOffset 更改为DateTime 解决了问题。

    【讨论】:

    • 在您不关心时区偏移的场景中,这是一个合适的解决方案。
    【解决方案4】:

    检查您的 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

    【讨论】:

    • 该问题在与 UTC 有正偏移的时区中重现,例如(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna。修改我机器的时区并重新运行后,我能够重现它。
    猜你喜欢
    • 2012-06-20
    • 2017-11-09
    • 2015-04-27
    • 1970-01-01
    • 1970-01-01
    • 2014-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多