提问者:小点点

为什么DataContractJsonSerializer和DateTimeOffset的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 ,0, 0,  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;
}

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

string json = JsonConvert.SerializeObject(item);


共1个答案

匿名用户

DataContractJSONSerializerDateTimeOffsetDateTime生成的JSON如文档所示。从日期/时间和JSON:

DateTimeOffset在JSON中表示为复杂类型:{“datetime”:datetime,“offsetMinutes”:offsetMinutes}offsetMinutes成员是与感兴趣事件的位置相关联的格林威治标准时间(GMT)的本地时间偏移量,现在也称为协调世界时(UTC)。DateTime成员在感兴趣的事件发生时及时表示实例(同样,在使用ASP.NET AJAX时,它变成JavaScript中的DateTime,而在不使用时,它变成字符串)。在序列化时,dateTime成员始终以GMT序列化。因此,如果描述纽约时间凌晨3:00,dateTime的时间分量为上午8:00,offsetMinutes为300(从格林尼治时间起减去300分钟或5小时)。

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

对于Newtonsoft,请参见文档页面在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.
{"SaveDate":{"DateTime":"2020-06-04T22:00:00.00+00:00","OffsetMinutes":300}}

它不是您要寻找的简单字符串。对于DateTimeOffsite似乎没有任何重写序列化格式的文档化方法。这里演示小提琴#1。

自从您写过之后,我试图解决的实际问题是让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传递给转换器的构造函数。

    如果有可能获得ISO8601字符串以及DateTimeOffset值的复杂对象,则可能需要在更高的代码级别配置JSONSerializerSettings.dateParseHandling.noneDateParseHandling.dateTimeOffset,以避免JSONTExtreader过早地将ISO8601字符串反序列化为DateTime对象。

    这里演示小提琴#2。