为啥 DateTimeOffset 的 DataContractJsonSerializer 和 Json.NET 序列化会产生不同的 json?
Posted
技术标签:
【中文标题】为啥 DateTimeOffset 的 DataContractJsonSerializer 和 Json.NET 序列化会产生不同的 json?【英文标题】:Why do DataContractJsonSerializer and Json.NET serialization of DateTimeOffset produce different json?为什么 DateTimeOffset 的 DataContractJsonSerializer 和 Json.NET 序列化会产生不同的 json? 【发布时间】:2020-10-19 08:59:31 【问题描述】:关于使用 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;
这会产生以下 json
"SaveDate":"DateTime":"\/Date(1591308000000)\/","OffsetMinutes":300
使用 Json.NET 我可以做到以下几点
TestToSeailize item = new TestToSeailize()
SaveDate = new DateTimeOffset(2020, 06, 05, 3, 0, 0, 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 方法反序列化。
【问题讨论】:
这是DateTime
和DateTimeOffset
的文档格式,请参阅Dates/Times and JSON。另请参阅相关的 Newtonsoft 文档newtonsoft.com/json/help/html/DatesInJSON.htm。
通过设置DataContractJsonSerializerSettings.DateTimeFormat
,您可以获得ISO 8601格式的底层DateTime
,如下所示:"SaveDate":"DateTime":"2020-06-04T22:00:00.00+00:00","OffsetMinutes":300
。见:dotnetfiddle.net/tnE2d3。但似乎没有办法将DateTimeOffset
序列化为 ISO 8601 字符串。
如果您需要 Newtonsoft 和 DataContractJsonSerializer
之间的一致性,您可能需要添加一个代理 DateTime
属性来序列化您的 SaveDate
属性。或者你可以在 Newtonsoft 端写一个转换器。如果这样的转换器可以满足您的需求,我可能会添加一个答案。
【参考方案1】:
DataContractJsonSerializer
为 DateTimeOffset
和 DateTime
生成的 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.None
或 DateParseHandling.DateTimeOffset
以避免过早将 ISO 8601 字符串反序列化为DateTime
JsonTextReader
的对象。
演示小提琴#2 here.
【讨论】:
以上是关于为啥 DateTimeOffset 的 DataContractJsonSerializer 和 Json.NET 序列化会产生不同的 json?的主要内容,如果未能解决你的问题,请参考以下文章
选择 SQL Server DatetimeOffset 作为 .Net DateTimeOffset.Ticks
“DateTime”和“DateTimeOffset”之间的区别[重复]