Почему DateTime.MinValue не может быть сериализован в часовых поясах раньше UTC?
Я испытываю проблемы с сервисом REST WCF. Объект проводки, который я пытаюсь вернуть, имеет определенные свойства, которые не заданы, что приводит к DateTime.MinValue для свойств типа DateTime. Служба возвращает пустой документ (с HTTP-статусом 200???). Когда я пытаюсь вызвать сериализацию JSON самостоятельно, исключение:
SerializationException: значения DateTime, которые больше, чем DateTime.MaxValue или меньше, чем DateTime.MinValue при преобразовании в UTC не могут быть сериализованы в 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.
Ответы
Ответ 1
Основная проблема 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();
Это выглядит странно, но это помогает.
Ответ 2
Попробуйте добавить это к любому члену DateTime
[DataMember(IsRequired = false, EmitDefaultValue = false)]
Большинство из этих erros происходит, потому что значение по умолчанию datetime
равно DateTime.MinValue
, которое составляет год 1, а сериализация JSON - с 1970 года.
Ответ 3
Если ваш часовой пояс GMT + 1, то значение UTC DateTime.MinValue
в вашем часовом поясе будет на час меньше DateTime.MinValue
.
Ответ 4
с помощью этого конструктора:
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
}
Ответ 5
Я считаю, что более элегантным способом является указание сериализатору не выдавать значение по умолчанию для полей 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*
...
}
Ответ 6
Вы можете исправить это во время сериализации через атрибут 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());
}
}
}