JSON データのデシリアライズにおける日付型の変換

(軽めの jQuery Advent Calendar 2012 の 23 日目です。)

次のようなコードで jQuery の getJSON 関数で JSON Web サービスを呼び出すと、
コールバック関数の引数 data には JSON データがデシリアライズ (パース) されたオブジェクトが渡されます。
ここで、data に含まれている値の型について考えてみます。


$.getJSON("Services/Products")
    .done(function (data) {
        // 取得したデータに対する処理
        var value = data[0].prop1; // 何型?
    });


JSON 形式では、オブジェクト、配列、文字列、数値、ブール値、null の記法が定義されており、
それぞれ JavaScript の Object, Array, String, Number, Boolean, null の値に対応します。
しかし、Date に対応する日付型は定義されていません。

getJSON 関数では、JSON 形式の文字列をデシリアライズするために
既定では $.parseJSON 関数 (JSON.parse 関数を IE7 以前のために拡張したもの) を使用します。
この場合、たとえ ISO 8601 の日付形式である "2012-12-24T14:59:59.999Z" のような文字列であっても、
自動的に Date 型に変換されることはありません。

しかし jQuery の Ajax 機能の設定値の中に converters というものがあり、
デシリアライズ方法をカスタマイズできるようになっています。
この機能は、jQuery 1.5で追加されたようです。

今回は、この converters を利用して日付を表す文字列を Date 型に変換してみます。

■ ISO 8601 の日付形式の場合

converters を設定するには、Ajax で通信を開始する前に $.ajaxSetup 関数を呼び出します。
キー "text json" に対してデシリアライズ関数を指定します。
次のように実装します。


$.ajaxSetup({
    converters: {
        "text json": function (text) {
            return JSON.parse(text, reviveDate);
        }
    }
});

function reviveDate(key, value) {
    if (value == null ||
        value.constructor !== String ||
        value.search(/^\d{4}-\d{2}-\d{2}/g) === -1)
        return value;
    return new Date(value);
}


実は JSON.parse 関数の第 2 引数には、キーと値を受け取って任意の値に変換する関数を指定できます。
この reviveDate 関数の中で、正規表現で文字列の先頭部分が YYYY-MM-DD に一致した場合は Date 型に変換しています。

次のように $.ajaxSettings.converters に直接上書きしても同様の効果です。

$.ajaxSettings.converters["text json"] = function (text) {
    return JSON.parse(text, reviveDate);
};

また、$.getJSON 関数ではなく $.ajax 関数を使う場合は、引数 settings に converters を指定できます。

では実際に次の JSON データをデシリアライズして、確認のためにブラウザーで表示してみます。

JSON データ (ISO 8601 形式)

表示サンプル

ここでは、先頭部分が YYYY-MM-DD の形式かどうかを判定しましたが、
他にも例えば「key がサフィックスとして "Date" を持つ (例: startDate)」という判定条件も考えられるでしょう。

なお、IE で Date のコンストラクターに ISO 8601 の日付形式を指定できるのは、IE9 以降です。

■ ASP.NET の場合

JSON Web サービスを WCF サービスなどで実装する場合、
.NET Framework の DateTime 型のオブジェクトは
"\/Date(1356361199999)\/" または "\/Date(1356361199999+0900)\/" のような形式にシリアライズされます。

したがって、先ほどの reviveDate 関数を次のように実装します。


function reviveDate(key, value) {
    if (value == null ||
        value.constructor !== String)
        return value;
    var m = /^\/Date\((\d+)(.+)?\)\/$/g.exec(value);
    if (!m) return value;
    return new Date(parseInt(m[1]));
}


先ほどと同じ意味のデータを WCF サービスで返すと次のようになります。表示結果は同じです。

JSON データ (ASP.NET 形式)

■ その他の場合

その他にも、日付の形式が案件で指定されている場合や、ISO 8601 形式を IE8 でも使いたい場合などが考えられますが、
同様に変換ロジックをカスタマイズすれば実現できると思います。

例えば、"2012年12月24日" という形式の場合は、次のようにして実現できます。


function reviveDate(key, value) {
    if (value == null ||
        value.constructor !== String)
        return value;
    var m = /^(\d{4})年(\d{1,2})月(\d{1,2})日$/g.exec(value);
    if (!m) return value;
    return new Date(parseInt(m[1]), parseInt(m[2]) – 1, parseInt(m[3]));
}


これで、次のようなデータを変換できます。

JSON データ (カスタム形式)

作成したサンプル
JSON Converter Sample 1 (ISO 8601 形式)
JSON Converter Sample 2 (ASP.NET 形式)
JSON Converter Sample 3 (カスタム形式)

バージョン情報
JSON.parse 関数: Internet Explorer 8 以降、その他の主要なブラウザー
ISO 8601 形式を受け取る Date のコンストラクター: Internet Explorer 9 以降、その他の主要なブラウザー

参照
jQuery.ajax()
Extending Ajax: Prefilters, Converters, and Transports
JSON の紹介
Date and Time Formats (W3C)
jQuery parseJSON automatic date conversion for Asp.net and ISO date strings

jQuery の IntelliSense

Visual Studio では、JavaScript に対しても IntelliSense (入力補完機能) を利用できます。
VS 2010でのJavaScript IntelliSenseの改善で説明されている通り、メンバーやその型などが動的に推論されます。

一方で、推論できない部分に対しては XML ドキュメント コメントを記述することで解決できます。
具体的には、関数の説明を表示したり、変数および戻り値の型を明示したりできます。
C# の XML ドキュメント コメントに近い形式で記述します。

しかし、本番環境では転送量を削減するため、空白やコメントを除去して軽量化した JavaScript ファイルを使うことがあります。
例えば jQuery であれば、jquery-1.8.3.min.js のような名前のファイルです。
そのような状況で開発する場合、<script src="~"> には軽量化した JavaScript ファイルのパスを指定しつつ、
XML ドキュメント コメントを利用したいと思うでしょう。
Visual Studio は、これに対応できる機能を備えています。

■ 自分のサイトに jQuery を配置する場合

実際に Visual Studio 2010 で、「ASP.NET Web アプリケーション」または
「ASP.NET MVC 2 Web アプリケーション」のテンプレートからプロジェクトを作成してみると、
jQuery としては次の 3 種類のファイルが含まれています。

  • jquery-1.4.1-vsdoc.js (XML ドキュメント コメントあり)
  • jquery-1.4.1.js (通常版)
  • jquery-1.4.1.min.js (軽量版)

jQuery 1.4.1 の XML ドキュメント コメント

<script src="~"> で jquery-1.4.1.min.js を読み込んでコードを記述してみます。

jQuery 1.4.1 の IntelliSense

このように、<script src="~"> に xxx.js または xxx.min.js が指定された場合、
同じ階層に xxx-vsdoc.js が存在するとその XML ドキュメント コメントが適用されます。
デバッグ時のステップ実行には、<script src="~"> で指定されたファイルが適用されます。

なお、jQuery 1.4.1 では XML ドキュメント コメントが日本語で記述されています (Visual Studio が日本語版の場合)。
jQuery の初心者が学習しながら実装していくには最適な環境だと思います。

ただし、これよりも後のバージョンでは日本語化されていないと思います。
「ASP.NET MVC 3 Web アプリケーション」のテンプレートに含まれている jQueryのバージョンは 1.5.1 ですが、
この XML ドキュメント コメントは英語で記述されています。

■ 外部の CDN 上の jQuery を参照する場合

リモートの jQuery を参照する場合も、原理は同様です。
CDN として Microsoft Ajax Content Delivery Network を利用すると便利です。
この CDN では、先ほどと同様の 3 種類のファイルがホストされています。

jQuery 1.8.3 の IntelliSense

■ JavaScript ライブラリを自作する場合

JavaScript ライブラリを自分で作成する場合は、
xxx-vsdoc.js にコードおよび XML ドキュメント コメントを記述して開発し、
Microsoft Ajax Minifier などの軽量化ツールで xxx.min.js を生成すればよいでしょう。

■ Visual Studio 2012 の新機能

Visual Studio 2012 では、上記で紹介した機能に加えて次の機能が使えるようになりました。

  • 関数のオーバーロードを表現するための <signature> タグ
  • XML ドキュメント コメントのみを記述できる xxx.intellisense.js ファイル。

jQuery 1.7.1 の IntelliSense

バージョン情報
Visual Studio 2010, 2012

参照
Microsoft Ajax Content Delivery Network
Microsoft Ajax Minifier
JavaScript IntelliSense (MSDN)
XML ドキュメント コメント (JavaScript) (MSDN)
VS 2010でのJavaScript IntelliSenseの改善
Visual Studio 2012 の jQuery インテリセンスとコード スニペット

switch における変数の宣言 (Objective-C)

Objective-C では、switch ステートメントのセクションの最初の行で変数を宣言するとビルド エラーになるようです。

1-1

これは変数が int 型の場合ですが、解決方法を 2 つ見つけました。

まずは、case ラベルの直後にセミコロン (;) を書くというものです。
これのおかげで 2 行目ということになるのでしょう。

1-2

もう 1 つは、セクションを波かっこ ({ }) で囲むというものです。
スコープが明示されるということでしょう。

1-3

では次に、変数が NSString 型の場合はどうでしょうか。

この場合はなんと、セミコロンを使う方法が通用しません。
セクション内の何行目に記述してもエラーとなるようです。
(もっと複雑なコードを記述したときに、NSString 型でエラーにならないこともあるのですが、条件がわかりません。)

2-1, 2-2

波かっこを使う方法で解決します。

2-3

さらに、default セクション内で NSString 型の変数を試してみました。
この場合は、セミコロンを使う方法でも波かっこを使う方法でも解決できるようです。

3-1, 3-2, 3-3

ちなみに C# では、上記の場合にはエラーにはなりませんが、
複数のセクションで同じ名前の変数を宣言する場合、それぞれのセクションを波かっこで囲まなければなりません。

4-1

4-2

バージョン情報
Xcode 4.3.3

Object.defineProperty 関数で Observable なオブジェクトを作る

(あまり知られていないけど役に立つ JavaScript tips Advent Calendar 2012 の 9 日目です。Qiita に投稿したものと同じ内容です。)

JavaScript の Object.defineProperty 関数 (および Object.defineProperties 関数) は、オブジェクトにプロパティを動的に定義します。
この関数の引数 descriptor の get 属性と set 属性に、プロパティへのアクセサーとなる関数を指定できるわけですが、
ここに任意の処理を追加できます。

この仕組みを利用して、Observable (監視可能) なオブジェクトを作ることができます。
ここでは、Observable なオブジェクトとは、プロパティ値の変更を通知するオブジェクトを指すこととします。
jQuery を活用して、次のように実装できます。


var obj = {};
Object.defineProperty(obj, "prop1", createDescriptor("prop1"));

function createDescriptor(name) {
    var value;
    return {
        get: function () {
            return value;
        },
        set: function (v) {
            if (value === v) return;
            value = v;
            $(this).trigger("propertychange", [name, v]);
        },
        enumerable: true,
        configurable: true
    };
}

obj.prop1 = "Original";
$(obj).on("propertychange", function (e, name, value) {
    alert(name + ": " + value);
});
obj.prop1 = "New";


通知の発行と受信には、それぞれ jQuery の trigger メソッドon メソッドを利用しています。
これで、prop1 プロパティの値が変更されると、その通知を受け取ることができるようになりました。
最後の行が実行されると、ダイアログが表示されます。

これを汎用化するために、コンストラクターにしてみます。


var Observable = (function () {
    function Observable(obj) {
        if (obj == null) return;
        for (var p in obj) {
            Object.defineProperty(this, p, createDescriptor(p, obj[p]));
        }
    }

    function createDescriptor(name, value0) {
        var value = value0;
        return {
            get: function () {
                return value;
            },
            set: function (v) {
                if (value === v) return;
                value = v;
                $(this).trigger("propertychange", [name, v]);
            },
            enumerable: true,
            configurable: true
        };
    }
    return Observable;
})();


これで、Plain なオブジェクトを Observable なオブジェクトに変換することができます。


var obj = { id: 123, name: "Original" };
var observable = new Observable(obj);

$(observable).on("propertychange", function (e, name, value) {
    alert(name + ": " + value);
});
observable.id = 456;
observable.name = "New";


このようなクラスを何に利用できるかというと、例えば Model と View を同期させる仕組みを作ることができます。
というわけで、このクラスとテンプレート エンジンを組み合わせた簡単なサンプルを作ってみました。

Data Binding Sample
Data Binding Sample

ここで使われているテンプレート エンジンについては詳しく説明しませんが、
jQuery Templates のようなテンプレート エンジンを自作したものです。
<div class="times"> の部分が appModel というデータにバインドされていて、バインド時に appModel は Observable 型になります。
1 秒ごとにタイマーで Model を更新すると、それが自動的に View まで伝播します。
記述量が少なく、ロジックを追いやすい JavaScript で HTML アプリケーションを構築することができます。

ただし、Object.defineProperty 関数は IE8 以下で動作しません。
今後 Windows XP のサポートが終了し、jQuery 2.x が主流になってくる頃には、
各 MVx フレームワークでもこういった手法が採り入れられていくことが期待されます。

バージョン情報
Internet Explorer 9 以降、その他の主要なブラウザー
jQuery 1.7 以降

参照
Object.defineProperty 関数 (JavaScript) (MSDN)
defineProperty (MDN)
ECMA-262 5th edition で導入された Object.defineProperty を使い、属性を指定してプロパティを定義する