JsonSerializer:区分 DateTime 对象的种类

JsonSerializer: discriminate the Kind of DateTime objects

我有一个包含日期(以及其他数据)的动态对象。

其中一些日期具有 UTC 类型,而其他日期具有本地类型,例如:

var dynamicObject = new 
{
     utcDate = DateTime.UtcNow,    //This one has Kind = DateTimeKind.Utc
     localDate = DateTime.Now      //This one has Kind = DateTimeKind.Local
}

然后我有一个 JsonSerializer,其工作方式如下:

var isoDateTimeConverter = new IsoDateTimeConverter();
isoDateTimeConverter.DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'";
                                            //                       ^
                                            //                  Notice this

var serializerSettings = new JsonSerializerSettings();
SerializerSettings.Converters.Add(isoDateTimeConverter);

var response = context.HttpContext.Response;
var writer = new JsonTextWriter(response.Output) { Formatting = Formatting };

var serializer = JsonSerializer.Create(serializerSettings);
serializer.Serialize(writer, dynamicObject);

writer.Flush();

这会创建一个 JSON 字符串,如下所示:

{
     "utcDate":"2019-05-02T19:52:20Z",
     "localDate":"2019-05-02T15:52:20Z"
}

这是预料之中的,因为我的 isoDateTimeConverter 目前是如何定义的。

但我想连载如下:

{
     "utcDate":"2019-05-02T19:52:20Z",
     "localDate":"2019-05-02T15:52:20"    // <--- no Z
}

意味着我想添加一个 'Z' 只有当 DateTime 的种类是 Utc.

IsoDateTimeConverter and/or JsonSerializerSettings 这可能吗?

我会在不设置 DateTimeFormat 的情况下使用 IsoDateTimeConverter,它会输出完整的 DateTime 值,带有几分之一秒,如果 DateTimeKindUTC 或如果 Local 则为“+-HH:mm”格式的时区偏移量(如果 None 则为空字符串)。

否则,K specifier 将根据您的需要设置值的格式,为与 UTC 的偏移附加相同的值。

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

public class Program
{
    public static void Main(string[] args)
    {
        var dynamicObject = new
        {
            utcDate = DateTime.UtcNow, //This one has Kind = DateTimeKind.Utc
            localDate = DateTime.Now //This one has Kind = DateTimeKind.Local
        }

        ;
        var isoDateTimeConverter = new IsoDateTimeConverter();
        isoDateTimeConverter.DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssK";
        var serializerSettings = new JsonSerializerSettings();
        serializerSettings.Converters.Add(isoDateTimeConverter);
        var s = new System.Text.StringBuilder();
        using (var w = new System.IO.StringWriter(s))
        using (var writer = new JsonTextWriter(w)
                   {Formatting = Formatting.Indented})
        {
            var serializer = JsonSerializer.Create(serializerSettings);
            serializer.Serialize(writer, dynamicObject);
            writer.Flush();
        }

        Console.WriteLine(s.ToString());
    }
}

dotnetfiddle.

ISO 8601 格式允许日期和时间的多种变化,达到任意精度。来自 Wikipedia's article on ISO 8601:

There is no limit on the number of decimal places for the decimal fraction. However, the number of decimal places needs to be agreed to by the communicating parties.

几件事:

  • 您不应将 DateTime 值与 DateTimeKind.Local 值序列化而没有偏移量。由于是当地时间,因此应提供本地偏移量。否则接收方可能会在他们的本地时间解释该值——这可能与发送方的不同。

  • The K specifier,当与 DateTimeStyles.RoundTripKind 配对时,在所有支持的 ISO 8601 格式中正确序列化:

    • DateTimeKind.Utc 被序列化并附加 Z
    • DateTimeKind.Local 被序列化并附加了本地偏移量,例如 -07:00
    • DateTimeKind.Unspecified 被序列化,没有附加任何内容
  • 这些是 IsoDateTimeConverter 已经给出的默认值,它是 DateTime 值的默认转换器。因此,在大多数情况下,您根本不需要指定任何转换器。

  • ISO 8601 允许任何长度的小数秒。RoundTripKind 样式给出 7 位小数,因为这是 DateTime 支持的精度。 JavaScript 中的客户端代码通常只支持毫秒,因此在客户端解析时会截断多余的小数。

  • 如果您必须截断小数,则:

    isoDateTimeConverter.DateTimeFormat = "yyyy-MM-dd'T'HH:mm:ssK";
    
  • 如果您觉得必须截断本地时间的偏移量,更好的方法是在序列化之前使用 DateTime.SpecifyKind 设置 DateTimeKind.Unspecified

  • 如果您觉得应该全局更改此设置,那么您可以创建自己的 JsonConverter。您可以继承 IsoDateTimeConverter 并覆盖 ReadJsonWriteJson 方法,或者您可以直接从 JsonConverter 开始。由于不推荐这种方式,这里就不加了。