为什么DateTime.MinValue不能在UTC之前的时区中序列化?
我遇到了WCF REST服务的问题。 我尝试返回的线对象没有设置某些属性,导致DateTime.MinValuetypes为DateTimetypes的属性。 该服务返回一个空文档(HTTP状态200 ???)。 当我尝试自己调用JSON序列化时,引发的exception是:
SerializationException:在转换为UTC时,大于DateTime.MaxValue或小于DateTime.MinValue的DateTime值无法序列化为JSON。
这可以通过在控制台应用程序中运行以下代码来进行重现:
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(DateTime)); MemoryStream m = new MemoryStream(); DateTime dt = DateTime.MinValue; // throws SerializationException in my timezone ser.WriteObject(m, dt); string json = Encoding.ASCII.GetString(m.GetBuffer()); Console.WriteLine(json);
这是为什么? 我认为这与我的时区(GMT + 1)有关。 由于DateTime.MinValue是默认的(DateTime),我希望这可以序列化没有问题。
有关如何使我的REST服务的行为的任何提示? 我不想改变我的DataContract。
主要的问题是DateTime.MinValue
有DateTimeKind.Unspecified
种类。 它被定义为:
MinValue = new DateTime(0L, DateTimeKind.Unspecified);
但是这不是一个真正的问题,这个定义导致序列化过程中的问题。 JSON DateTime序列化通过以下方式完成:
System.Runtime.Serialization.Json.JsonWriterDelegator.WriteDateTime(DateTime value)
不幸的是它被定义为:
... if (value.Kind != DateTimeKind.Utc) { long num = value.Ticks - TimeZone.CurrentTimeZone.GetUtcOffset(value).Ticks; if ((num > DateTime.MaxValue.Ticks) || (num < DateTime.MinValue.Ticks)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.GetString("JsonDateTimeOutOfRange"), new ArgumentOutOfRangeException("value"))); } } ...
所以它不考虑Unspecified
并将其视为Local
。 为了避免这种情况,你可以定义你自己的常量:
MinValueUtc = new DateTime(0L, DateTimeKind.Utc);
要么
MinValueUtc = DateTime.MinValue.ToUniversalTime();
它看起来很奇怪,但它有帮助。
尝试添加此任何DateTime成员
[DataMember(IsRequired = false, EmitDefaultValue = false)]
大多数这些错误发生是因为datetime
的默认值是DateTime.MinValue
,从1年开始,JSON序列化从1970年开始。
如果您的时区是GMT + 1,那么您的时区中的DateTime.MinValue
的UTC值将比DateTime.MinValue
小一个小时。
使用这个构造函数:
public DataContractJsonSerializer(Type type, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, IDataContractSurrogate dataContractSurrogate, bool alwaysEmitTypeInformation)
示例代码:
DataContractJsonSerializer serializer = new DataContractJsonSerializer(o.GetType(), null, int.MaxValue, false, new DateTimeSurrogate(), false); public class DateTimeSurrogate : IDataContractSurrogate { #region IDataContractSurrogate 成员public object GetCustomDataToExport(Type clrType, Type dataContractType) { return null; } public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType) { return null; } public Type GetDataContractType(Type type) { return type; } public object GetDeserializedObject(object obj, Type targetType) { return obj; } public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes) { } public object GetObjectToSerialize(object obj, Type targetType) { if (obj.GetType() == typeof(DateTime)) { DateTime dt = (DateTime)obj; if (dt == DateTime.MinValue) { dt = DateTime.MinValue.ToUniversalTime(); return dt; } return dt; } if (obj == null) { return null; } var q = from p in obj.GetType().GetProperties() where (p.PropertyType == typeof(DateTime)) && (DateTime)p.GetValue(obj, null) == DateTime.MinValue select p; q.ToList().ForEach(p => { p.SetValue(obj, DateTime.MinValue.ToUniversalTime(), null); }); return obj; } public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData) { return null; } public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit) { return typeDeclaration; } #endregion }
我相信一个更优雅的方法是指示序列化程序不发出DateTime字段的默认值。 这将在传输过程中保存一些字节,并在序列化您没有任何值的字段时进行一些处理。 例:
[DataContract] public class Document { [DataMember] public string Title { get; set; } [DataMember(IsRequired = false, EmitDefaultValue = false)] public DateTime Modified { get; set; } }
或者你可以使用Nullables。 例:
[DataContract] public class Document { [DataMember] public string Title { get; set; } [DataMember] public DateTime? Modified { get; set; } }
这完全取决于您在项目中的要求和限制。 有时你不能只改变数据types。 在这种情况下,您仍然可以利用DataMember
属性并保持数据types不变。
在上面的例子中,如果你在服务器端有了new Document() { Title = "Test Document" }
,当序列化为JSON的时候会给你{"Title": "Test Document"}
所以处理起来更容易在JavaScript或任何其他客户端在电线的另一端。 在JavaScript中,如果你使用JSON.Parse(),并尝试读取它,你将返回undefined
。 在types化语言中,根据types(这通常是预期的行为),将具有该属性的默认值。
library.GetDocument(id).success(function(raw){ var document = JSON.Parse(raw); var date = document.date; // date will be *undefined* ... }
您可以通过OnSerializing属性和一些reflection来修复序列化过程中的问题:
[OnSerializing] public void OnSerializing(StreamingContext context) { var properties = this.GetType().GetProperties(); foreach (PropertyInfo property in properties) { if (property.PropertyType == typeof(DateTime) && property.GetValue(this).Equals(DateTime.MinValue)) { property.SetValue(this, DateTime.MinValue.ToUniversalTime()); } } }