为什么 DataContractJsonSerializer 和 DateTimeOffset 的 Json.NET 序列化产生不同的 json?
Why do DataContractJsonSerializer and Json.NET serialization of DateTimeOffset produce different json?
我有一个问题,我试图了解有关使用 DataContractJsonSerializer 和 Json.NET 的 JsonConvert 序列化和反序列化 DateTimeOffset 值的方式。
我有以下class
[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 方法反序列化。
DataContractJsonSerializer
为 DateTimeOffset
和 DateTime
生成的 JSON 已记录在案。来自 Dates/Times and JSON:
DateTimeOffset is represented in JSON as a complex type: {"DateTime":dateTime,"OffsetMinutes":offsetMinutes}
. The offsetMinutes
member is the local time offset from Greenwich Mean Time (GMT), also now referred to as Coordinated Universal Time (UTC), associated with the location of the event of interest. The dateTime
member represents the instance in time when the event of interest occurred (again, it becomes a DateTime in JavaScript when ASP.NET AJAX is in use and a string when it is not). On serialization, the dateTime member is always serialized in GMT. So, if describing 3:00 AM New York time, dateTime has a time component of 8:00 AM and offsetMinutes are 300 (minus 300 minutes or 5 hours from GMT).
Note
DateTime and DateTimeOffset objects, when serialized to JSON, only preserve information to millisecond precision. Sub-millisecond values (micro/nanoseconds) are lost during serialization.
DateTime values appear as JSON strings in the form of "/Date(700000+0500)/"
, where the first number (700000 in the example provided) is the number of milliseconds in the GMT time zone, regular (non-daylight savings) time since midnight, January 1, 1970. The number may be negative to represent earlier times. The part that consists of "+0500" in the example is optional and indicates that the time is of the Local kind - that is, should be converted to the local time zone on deserialization. If it is absent, the time is deserialized as Utc. The actual number ("0500" in this example) and its sign (+ or -) are ignored.
对于 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
的序列化格式。演示 fiddle #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
更高的代码级别以避免 JsonTextReader
.
将 ISO 8601 字符串过早反序列化为 DateTime
对象
演示 fiddle #2 here.
我有一个问题,我试图了解有关使用 DataContractJsonSerializer 和 Json.NET 的 JsonConvert 序列化和反序列化 DateTimeOffset 值的方式。
我有以下class
[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 方法反序列化。
DataContractJsonSerializer
为 DateTimeOffset
和 DateTime
生成的 JSON 已记录在案。来自 Dates/Times and JSON:
DateTimeOffset is represented in JSON as a complex type:
{"DateTime":dateTime,"OffsetMinutes":offsetMinutes}
. TheoffsetMinutes
member is the local time offset from Greenwich Mean Time (GMT), also now referred to as Coordinated Universal Time (UTC), associated with the location of the event of interest. ThedateTime
member represents the instance in time when the event of interest occurred (again, it becomes a DateTime in JavaScript when ASP.NET AJAX is in use and a string when it is not). On serialization, the dateTime member is always serialized in GMT. So, if describing 3:00 AM New York time, dateTime has a time component of 8:00 AM and offsetMinutes are 300 (minus 300 minutes or 5 hours from GMT).Note
DateTime and DateTimeOffset objects, when serialized to JSON, only preserve information to millisecond precision. Sub-millisecond values (micro/nanoseconds) are lost during serialization.
DateTime values appear as JSON strings in the form of
"/Date(700000+0500)/"
, where the first number (700000 in the example provided) is the number of milliseconds in the GMT time zone, regular (non-daylight savings) time since midnight, January 1, 1970. The number may be negative to represent earlier times. The part that consists of "+0500" in the example is optional and indicates that the time is of the Local kind - that is, should be converted to the local time zone on deserialization. If it is absent, the time is deserialized as Utc. The actual number ("0500" in this example) and its sign (+ or -) are ignored.
对于 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
的序列化格式。演示 fiddle #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 字符串以及
将 ISO 8601 字符串过早反序列化为DateTimeOffset
值的复杂对象,您可能需要配置JsonSerializerSettings.DateParseHandling = DateParseHandling.None
或DateParseHandling.DateTimeOffset
更高的代码级别以避免JsonTextReader
.DateTime
对象
演示 fiddle #2 here.