web-dev-qa-db-ja.com

DateTime.MinValueがUTCより前のタイムゾーンでシリアル化できないのはなぜですか?

WCF RESTサービス。 (HTTPステータス200 ???)。JSONシリアル化を自分で呼び出そうとすると、スローされる例外は次のとおりです。

SerializationException:DateTime.MaxValueより大きい、またはUTCに変換されたときに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はdefault(DateTime)なので、これを問題なくシリアル化できると期待しています。

RESTサービスを動作させる方法に関するヒントはありますか?DataContractを変更したくありません。

50
Teun D

主な問題は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();

もちろん奇妙に見えますが、役立ちます。

67

これを任意のDateTimeメンバーに追加してみてください

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

これらのエラーのほとんどは、datetimeのデフォルト値がDateTime.MinValueこれは1年目からであり、JSONシリアル化は1970年からです。

13
user1505015

タイムゾーンがGMT + 1の場合、タイムゾーンのDateTime.MinValueのUTC値はDateTime.MinValueよりも1時間少なくなります。

6
Adam Robinson

このコンストラクタを使用して:

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
Daniel Cai

よりエレガントな方法は、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*
    ...
}
_
2
Reza

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