为什么DateTime.MinValue不能在UTC之前的时区中序列化?

时间:2010-10-26 16:43:43

标签: c# wcf json datetime serialization

我遇到了WCF REST服务的问题。我尝试返回的wire对象具有未设置的某些属性,导致DateTime.MinValue为DateTime类型的属性。该服务返回一个空文档(HTTP状态为200 ???)。当我尝试自己调用JSON序列化时,抛出的异常是:

  

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。

7 个答案:

答案 0 :(得分:66)

主要问题是DateTime.MinValueDateTimeKind.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();

当然看起来很奇怪,但它有所帮助。

答案 1 :(得分:13)

尝试在任何DateTime会员

上添加此内容
[DataMember(IsRequired = false, EmitDefaultValue = false)]

大多数错误发生是因为datetime的默认值是DateTime.MinValue,它是从1年开始,而JSON序列化是从1970年开始。

答案 2 :(得分:6)

如果您的时区为GMT + 1,那么您所在时区的DateTime.MinValue的UTC值将比DateTime.MinValue小一小时。

答案 3 :(得分:5)

使用此构造函数:

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
    }

答案 4 :(得分:2)

我认为更优雅的方法是指示序列化程序不要为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; } 
}

这完全取决于您在项目中可能具有的要求和限制。有时您不能只改变数据类型。在这种情况下,您仍然可以利用DataMember属性并保持数据类型不变。

在上面的例子中,如果你在服务器端有new Document() { Title = "Test Document" },那么当序列化为JSON时,它会给你{"Title": "Test Document"},因此在JavaScript或其他客户端中更容易处理电线的一面。在JavaScript中,如果您使用JSON.Parse(),并尝试阅读它,您将返回undefined。在类型语言中,您将具有该属性的默认值,具体取决于类型(通常是预期的行为)。

library.GetDocument(id).success(function(raw){ 
    var document = JSON.Parse(raw);
    var date = document.date; // date will be *undefined*
    ...
}

答案 5 :(得分:0)

您可以在序列化期间通过OnSerializing属性和一些反射修复此问题:

[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());
    }
  }
}

答案 6 :(得分:0)

可以通过指定自定义DateTimeFormat来避免此问题(使用WriteDateTimeInDefaultFormat方法):

DataContractJsonSerializerSettings settings = new DataContractJsonSerializerSettings();
settings.DateTimeFormat = new DateTimeFormat("yyyy-MM-ddThh:mm:ss.fffZ");
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(MyDataContract), settings);

请注意,您应该确保DateTime值的确是UTC格式,但这可以与DateTime.MinValue一起使用。

有关使用的DateTimeFormat的其他详细信息,请参见this