SSブログ

[Tips] 元に戻らない TimeSpan - TimeSpan which is not deserialized [.NET Framework]

.NET Framework です。

About NET Framework.

JavaScriptSerializer の Serialize・Deserialize で、TimeSpan が元に戻らなかったという話題です。

TimeSpan was not deserialized with JavaScriptSerializer as expected.

ASP.NET Web アプリケーションで使用するためのライブラリの単体テストを作っていたところ、不本意な失敗が起こるのに気がつきました。

I was creating the library and simple test for an ASPNET Web application, and found an unexpected failure happened.

どうやら、TimeSpan 型のプロパティをメンバーに持つクラスを、 JsonSerializer を使って、Serialize → Deserialize と処理すると、TimeSpan が元に戻らないようなのです。

TimeSpan, a member of a certain class, was not deserialised as expected.

調べてみたところ、シリアライズ前の TimeSpan と、シリアライズ後の JSON の内容は期待通りでした。

I was sure that contents were as expected in TimeSpan before Serializing and JSON after Serializing.

しかし、JSON からデシリアライズ後に作成された TimeSpan 構造体インスタンスに、JSON の内容が反映されていませんでした(Ticks = 0)。

However, I found that contents of TimeSpan after Deserializing were not reflected from JSON(Ticks = 0).

そこで、最小構成の単体テストを作ってみました。

Therefore I created a simple test of the smallest construction.

    // ランダムな Ticks で TimeSpan を作成
    TimeSpan tsBefore = new TimeSpan(ticks);
    JavaScriptSerializer serializer = new JavaScriptSerializer();
    string json = serializer.Serialize(tsBefore);
    TimeSpan tsAfter = serializer.Deserialize<TimeSpan>(json);
    Console.WriteLine("JSON:[{0}]", json);
    Console.WriteLine("シリアライズ以前の TimeSpan:[{0}]", tsBefore); // 例: [9776666.12:30:35.4369536]
    Console.WriteLine("デシリアライズ後の TimeSpan:[{0}]", tsAfter);  // 例: [00:00:00]
    Assert.AreNotEqual(tsBefore, tsAfter);
    Assert.IsTrue(tsAfter.Ticks == 0);

以下は出力結果です。

The output results are as follows.

JSON:[{"Ticks":4134101591365946880,"Days":4784839,"Hours":19,"Milliseconds":594,"Minutes":18,"Seconds":56,"TotalDays":4784839.804821698,"TotalHours":114836155.31572074,"TotalMilliseconds":413410159136594.69,"TotalMinutes":6890169318.9432449,"TotalSeconds":413410159136.59467}]
シリアライズ以前の TimeSpan:[4784839.19:18:56.5946880]
デシリアライズ後の TimeSpan:[00:00:00]

ダメか・・・。

Bad ...

で、TimeSpan を調べていて、いまさら気づいたんですが。

Well, I searched about TimeSpan more and noticed at the time.

TimeSpan は、ISerializable インターフェイスを実装してないんですね・・・。

TimeSpan had not implemented ISerializable interface ...

しかし JSON 変換は ISerializable ではなく、JavaScriptConverter 型コンバーターを使用したコンバートのはず。

But, JSON conversion should use the JavaScriptConverter type converter, not ISerializable.

また、個別のカスタム型コンバーターを実装する方法も記憶にありました。

In addition, I remembered that the JSON conversion could use the individual custom type converter.

そこで実験、TimeSpan 用のカスタム型コンバーターを作ります。

Therefore I created a custom type converter for TimeSpan.

// TimeSpan 用カスタム型コンバーター
public class CustomTimeSpanConverter : JavaScriptConverter
{
    public override IEnumerable<Type> SupportedTypes
    {
        get { return new ReadOnlyCollection<Type>(new List<Type>(new Type[] { typeof(TimeSpan) })); }
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        TimeSpan ts = (TimeSpan)obj;
        Dictionary<string, object> result = new Dictionary<string, object>();
        result["Ticks"] = ts.Ticks;
        return result;
    }

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        if (dictionary == null)
            throw new ArgumentNullException("dictionary");
        if (type == typeof(TimeSpan))
        {
            long ticks = Convert.ToInt64(dictionary["Ticks"]);
            TimeSpan ts = new TimeSpan(ticks);
            return ts;
        }
        return TimeSpan.MinValue;
    }
}

テスト内の変換部で、JavaScriptSerializer クラス インスタンスに、先ほど作った TimeSpan 構造体用カスタム型コンバーターを登録します。

In a conversion code in the test, I registered the custom type converter with JavaScriptSerializer class instance.

    // ランダムな Ticks で TimeSpan を作成
    TimeSpan tsBefore = new TimeSpan(ticks);
    JavaScriptSerializer serializer = new JavaScriptSerializer();
    serializer.RegisterConverters(new JavaScriptConverter[] {
        new CustomTimeSpanConverter()
    });  // カスタム型コンバーター
    string json = serializer.Serialize(tsBefore);
    TimeSpan tsAfter = serializer.Deserialize<TimeSpan>(json);
    Console.WriteLine("JSON:[{0}]", json);
    Console.WriteLine("シリアライズ以前の TimeSpan:[{0}]", tsBefore); // 例: [9776666.12:30:35.4369536]
    Console.WriteLine("デシリアライズ後の TimeSpan:[{0}]", tsAfter);  // 例: [9776666.12:30:35.4369536]
    Assert.AreEqual(tsBefore, tsAfter); // 成功

出力結果です。

The output results are as follows.

JSON:[{"Ticks":4134101591365946880}]
シリアライズ以前の TimeSpan:[4784839.19:18:56.5946880]
デシリアライズ後の TimeSpan:[4784839.19:18:56.5946880]

期待通りになりました。

It became as good as expected.

しかし、最初にテストで失敗したときは、あるクラス内のメンバーとして TimeSpan を使っています。

However, I used TimeSpan as a certain intraclass member when I failed on a test first.

このような状況に対しても、上記の TimeSpan カスタム型コンバーターを登録するだけで対応できるかどうか確認しておきます。

So I had to confirm that the custom conversion could convert a interclass TimeSpan as expected.

まず、TimeSpan 構造体をメンバーとして使用するクラスを作成します。

At first I created a class using TimeSpan structure as a member.

public class TimeSpanContainer
{
    public TimeSpan InnerTimeSpan { get; set; }
}

このクラスを使用して、シリアライズ前とデシリアライズ後のインスタンスを比較します。

With this class, I compared the instance after deserializing with the instance before serializing.

    // ランダムな Ticks で TimeSpan を作成
    TimeSpan tsBefore = new TimeSpan(ticks);
    TimeSpanContainer tscBefore = new TimeSpanContainer();
    tscBefore.InnerTimeSpan = tsBefore;
    JavaScriptSerializer serializer = new JavaScriptSerializer();
    serializer.RegisterConverters(new JavaScriptConverter[] {
        new CustomTimeSpanConverter()
    });  // カスタム コンバーター
    string json = serializer.Serialize(tscBefore);
    TimeSpanContainer tscAfter = serializer.Deserialize<TimeSpanContainer>(json);
    TimeSpan tsAfter = tscAfter.InnerTimeSpan;
    Console.WriteLine("JSON:[{0}]", json);
    Console.WriteLine("シリアライズ以前の TimeSpan:[{0}]", tsBefore); // 例: [9776666.12:30:35.4369536]
    Console.WriteLine("デシリアライズ後の TimeSpan:[{0}]", tsAfter);  // 例: [9776666.12:30:35.4369536]
    Assert.AreEqual(tsBefore, tsAfter); // 成功

出力結果です。

The output results are as follows.

JSON:[{"InnerTimeSpan":{"Ticks":2785614643002932224}}]
シリアライズ以前の TimeSpan:[3224091.00:31:40.2932224]
デシリアライズ後の TimeSpan:[3224091.00:31:40.2932224]

・・・無事に一件落着しそうです。

... This issue seems to be settled in the safety.

よかった、よかった。

Was good.


nice!(2)  コメント(2)  トラックバック(0) 
共通テーマ:パソコン・インターネット

nice! 2

コメント 2

ゆうみ

みみちゃん先生 難しい文字がいっぱいあって
おらには未知との遭遇のようです。
by ゆうみ (2014-08-31 16:21) 

One-for-you

Javaは、便利なものと思い込んでおります。
I am thinking and considering that THE Java seems to be a quite convinient Language in that owrld・・・^^;
by One-for-you (2014-09-20 02:48) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。