Почему DateTime.MinValue нельзя сериализовать в часовых поясах до UTC? - PullRequest
50 голосов
/ 26 октября 2010

У меня проблемы с 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.

Ответы [ 6 ]

67 голосов
/ 26 октября 2010

Основная проблема 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();

Это выглядит странно, конечно, но это помогает.

13 голосов
/ 06 сентября 2012

Попробуйте добавить это к любому члену DateTime

[DataMember(IsRequired = false, EmitDefaultValue = false)]

Большинство этих ошибок происходит потому, что значение по умолчанию datetime равно DateTime.MinValue, начиная с года 1 и сериализации JSON от1970 год.

6 голосов
/ 26 октября 2010

Если ваш часовой пояс GMT + 1, тогда значение UTC DateTime.MinValue в вашем часовом поясе будет на час меньше DateTime.MinValue.

5 голосов
/ 22 мая 2011

используя этот конструктор:

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
    }
2 голосов
/ 06 ноября 2015

Я считаю, что более элегантный способ - указать сериализатору не выдавать значение по умолчанию для полей 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*
    ...
}
0 голосов
/ 24 марта 2015

Это можно исправить во время сериализации с помощью атрибута 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());
    }
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...