关于使用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);
DataContractJSONSerializer
为DateTimeOffset
和DateTime
生成的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.none
或DateParseHandling.dateTimeOffset
,以避免JSONTExtreader
过早地将ISO8601字符串反序列化为DateTime
对象。
这里演示小提琴#2。