为什么DateTimeOffset的DataContractJsonSerializer和Json.NET序列化会生成不同的json?

关于使用DataContractJsonSerializer和Json.NET的JsonConvert对DateTimeOffset值进行序列化和反序列化的方式,我想了解一个问题。

我有以下课程

[DataContract]
public class TestToSeailize
{
    [DataMember]
    public DateTimeOffset SaveDate { get; set; }
}

我可以使用DataContractJsonSerializer对此进行序列化:

TestToSeailize item = new TestToSeailize()
{
    SaveDate = new DateTimeOffset(2020,06,05,3,TimeSpan.FromHours(5))
};

DataContractJsonSerializer serializer = new DataContractJsonSerializer(item.GetType(),settings);
using (MemoryStream ms = new MemoryStream())
{
    serializer.WriteObject(ms,item);
    var json = Encoding.UTF8.GetString(ms.ToArray()); 
    Console.WriteLine(json);
    return json;
}

这将导致以下json {"SaveDate":{"DateTime":"\/Date(1591308000000)\/","OffsetMinutes":300}

并使用Json.NET,我可以执行以下操作

TestToSeailize item = new TestToSeailize()
{
    SaveDate = new DateTimeOffset(2020,TimeSpan.FromHours(5))
};

string json = JsonConvert.SerializeObject(item);

这将导致以下json {"SaveDate":"2020-06-05T03:00:00+05:00"}

为什么它们会产生不同的json?有没有一种方法可以更改DataContract序列化以产生与Json.NET相同的json?

我要解决的实际问题是让DataContractJsonSerializer序列化的数据通过JsonConvert.DeserialzeObject方法反序列化。

iCMS 回答:为什么DateTimeOffset的DataContractJsonSerializer和Json.NET序列化会生成不同的json?

DataContractJsonSerializerDateTimeOffsetDateTime生成的JSON已记录。来自Dates/Times and JSON

DateTimeOffset在JSON中表示为复杂类型:{"DateTime":dateTime,"OffsetMinutes":offsetMinutes}offsetMinutes成员是与感兴趣事件的位置相关联的格林威治标准时间(GMT)的本地时间偏移,该时间现在也称为协调世界时(UTC)。 dateTime成员表示感兴趣事件发生时的时间实例(再次,当使用ASP.NET AJAX时,它成为JavaScript中的DateTime;当不使用时,它成为字符串)。序列化时,dateTime成员始终在GMT中序列化。因此,如果描述纽约时间凌晨3:00,则dateTime的时间部分为8:00 AM,offsetMinutes为300(减去GMT的300分钟或5小时)。

注意

DateTime和DateTimeOffset对象,当序列化为JSON时,仅将信息保留为毫秒级。序列化过程中会丢失亚毫秒级的值(微秒/纳秒)。

并且来自DateTime Wire Format

DateTime值以"/Date(700000+0500)/"的形式显示为JSON字符串,其中第一个数字(在提供的示例中为700000)是GMT时区中的毫秒数,自此以来的常规(非夏令时)时间1970年1月1日午夜。该数字可能为负值,代表较早的时间。在示例中,由“ +0500”组成的部分是可选的,它指示时间属于本地时间-也就是说,应在反序列化时将其转换为本地时区。如果不存在,则将时间反序列化为Utc。实际数字(在此示例中为“ 0500”)及其符号(+或-)将被忽略。

对于Newtonsoft,请参阅文档页面Serializing Dates in JSON,以了解如何序列化日期和时间。默认情况下,使用ISO 8601格式的字符串,但支持多种格式。

现在,可以通过设置DataContractJsonSerializerSettings.DateTimeFormat自定义数据协定DateTime的格式:

var settings = new DataContractJsonSerializerSettings
{
    DateTimeFormat = new DateTimeFormat("yyyy-MM-ddTHH\\:mm\\:ss.ffFFFFFzzz",CultureInfo.InvariantCulture)
    {
    },};
DataContractJsonSerializer serializer = new DataContractJsonSerializer(item.GetType(),settings);
// Remainder as in your question.

但是DateTimeOffset的结果如下:

{"SaveDate":{"DateTime":"2020-06-04T22:00:00.00+00:00","OffsetMinutes":300}}

这不是您要查找的简单字符串。似乎没有任何记录的方法可以覆盖DateTimeOffset的序列化格式。演示小提琴#1 here

自从您写了我要解决的实际问题是,让DataContractJsonSerializer序列化的数据通过JsonConvert DeserialzeObject方法反序列化,将Json.NET配置为反序列化DataContractJsonSerializer格式。首先,定义以下自定义JsonConverter

public class DataContractDateTimeOffsetConverter : JsonConverter
{
    readonly bool canWrite;
    
    public DataContractDateTimeOffsetConverter() : this(true) { }
    public DataContractDateTimeOffsetConverter(bool canWrite) => this.canWrite = canWrite;

    public override bool CanWrite => canWrite;
    public override bool CanConvert(Type objectType) => objectType == typeof(DateTimeOffset) || objectType == typeof(DateTimeOffset?);

    [JsonObject(NamingStrategyType = typeof(DefaultNamingStrategy))] // Ignore camel casing
    class DateTimeOffsetDTO<TOffset> where TOffset : struct,IComparable,IFormattable
    {
        public DateTime DateTime { get; set; }
        public TOffset OffsetMinutes { get; set; }
    }

    public override void WriteJson(JsonWriter writer,object value,JsonSerializer serializer)
    {
        var input = (DateTimeOffset)value;
        var oldDateFormatHandling = writer.DateFormatHandling;
        var oldDateTimeZoneHandling = writer.DateTimeZoneHandling;
        try
        {
            writer.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
            writer.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
            var offsetMinutes = input.Offset.TotalMinutes;
            var offsetMinutesInt = checked((int)offsetMinutes);
            var dateTime = input.DateTime.AddMinutes(-input.Offset.TotalMinutes);
            if (offsetMinutesInt == offsetMinutes) // An integer number of mintues
                serializer.Serialize(writer,new DateTimeOffsetDTO<int> { DateTime = dateTime,OffsetMinutes = offsetMinutesInt });
            else
                serializer.Serialize(writer,new DateTimeOffsetDTO<double> { DateTime = dateTime,OffsetMinutes = offsetMinutes });
        }
        finally
        {
            writer.DateFormatHandling = oldDateFormatHandling;
            writer.DateTimeZoneHandling = oldDateTimeZoneHandling;
        }
    }

    public override object ReadJson(JsonReader reader,Type objectType,object existingValue,JsonSerializer serializer)
    {
        switch (reader.MoveToContentAndAssert().TokenType)
        {
            // note that if there is a possibility of getting ISO 8601 strings for DateTimeOffset as well as complex objects,you may need to configure
            // JsonSerializerSettings.DateParseHandling = DateParseHandling.None or DateParseHandling.DateTimeOffset at a higher code level to 
            // avoid premature deserialization as DateTime by JsonTextReader.
            case JsonToken.String:
            case JsonToken.Date:
                return (DateTimeOffset)JToken.Load(reader);
                
            case JsonToken.StartObject:
                var old = reader.DateTimeZoneHandling;
                try
                {
                    reader.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
                    var dto = serializer.Deserialize<DateTimeOffsetDTO<double>>(reader);
                    var result = new DateTimeOffset(new DateTime(dto.DateTime.AddMinutes(dto.OffsetMinutes).Ticks,DateTimeKind.Unspecified),TimeSpan.FromMinutes(dto.OffsetMinutes));
                    return result;
                }
                finally
                {
                    reader.DateTimeZoneHandling = old;
                }
                
            case JsonToken.Null:
                return null;    
                
            default:
                throw new JsonSerializationException(); // Unknown token
        }
    }
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

现在,您可以通过将转换器添加到JsonSerializerSettings.Converters来反序列化DataContractJsonSerializer生成的JSON:

var settings = new JsonSerializerSettings
{
    Converters = { new DataContractDateTimeOffsetConverter(true) },};

var item = JsonConvert.DeserializeObject<TestToSeailize>(json,settings);

注意:

  • 如果不想以DataContractJsonSerializer格式进行序列化,请将canWrite : false传递给转换器的构造函数。

  • 如果有可能获得ISO 8601字符串以及DateTimeOffset值的复杂对象,则可能需要以更高的代码级别配置JsonSerializerSettings.DateParseHandling = DateParseHandling.NoneDateParseHandling.DateTimeOffset以避免DateTime将ISO 8601字符串过早反序列化为JsonTextReader对象。

演示小提琴#2 here

本文链接:https://www.f2er.com/2037538.html

大家都在问