過去介紹過微軟針對DateTime制訂的獨有JSON表示法: "\/Date(…)\/"。今天實際應用時,發現一個有趣現象: ASP.NET Server傳來包含DateTimeJSON字串,因使用JavaScriptSerializer解析,日期會呈現"\/Date(…)\/"格式;在Client端以JSON.parse()還原回成物件,由於未應用到日期值,故未另外將其轉換成JavaScript Date型別,在JavaScript物件中該值維持字串型別。稍後再次以JSON.stringify()成JSON字串傳至ASP.NET Server端解析時,卻出現了"/Date(1330444800000)/ is not a valid value for DateTime. "錯誤訊息。

明明是JavaScriptSerializer.Serialize()出的內容,再送回去給JavaScriptSerializer.Deserialize()卻解不回來,WTF?

用範例來重現問題:

<%@ Page Language="C#" %>
 
<!DOCTYPE>
 
<script runat="server">
    void Page_Load(object sender, EventArgs e)
    {
        string mode = Request["mode"];
        if (!string.IsNullOrEmpty(mode))
        {
            var jss =
                new System.Web.Script.Serialization.JavaScriptSerializer();
            if (mode == "get")
            {
                List<DateTime> list = new List<DateTime>();
                list.Add(new DateTime(2012, 2, 29));
                Response.Write(jss.Serialize(list));
            }
            else if (mode == "post")
            {
                try
                {
                    List<DateTime> list =
                        jss.Deserialize<List<DateTime>>(Request["json"]);
                    Response.Write("List[0]=" + list.First());
                }
                catch (Exception ex)
                {
                    Response.Write("Error: " + ex.Message);
                }
            }
            Response.End();
        }
    }
</script>
 
<html>
<head runat="server">
    <title>JSON Date Lab</title>
    <script type='text/javascript' 
            src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.1.js'></script>   
    <script>
        $(function () {
            $("#btnTest").click(function () {
                //取得List<DateTime> JSON
                $.get("?mode=get", {}, function (json) {
                    alert("JSON=" + json);
                    //將取得的JSON字串傳回去,可解析,但會有時區誤差
                    $.post("?mode=post", { json: json }, function (r) {
                        alert("TEST1:" + r);
                    });
                    //將JSON字串先解析為物件
                    var list = JSON.parse(json);
                    //因JSON.parse()不認得\/Date(...)\/格式,只會被當成字串處理
                    //而"\/"相當於"\",故最終被解析成字串"/Date(...)/",
                    var rejson = JSON.stringify(list);
                    alert("Object->" + rejson);
                    //將這個字串陣列JSON.stringify()再傳回去,則會因"\/"變成"/"而出現錯誤
                    $.post("?mode=post", { json: rejson }, function (r) {
                        alert("TEST2:" + r);
                    });
                });
            });
        });
    </script>
    <style>
        body,input { font-size: 9pt; }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <input type="button" id="btnTest" value="Test" />
    </form>
</body>
</html>

在Server端建立List<DateTime>,以JavaScriptSerializer.Serialize()後傳至Client端,得到結果如下,符合預期:

JSON=["\/Date(1330444800000)\/"]

直接將剛才接到字串抛回Server端進行JavaScriptSerializer.Deserialize(),可順利取回DateTime,但因時區標準不同會有8小時時差,原本的2012/2/29被視為格林威治時間2/28 16:00。

TEST1:List[0]=2012/2/28 下午 04:00:00

若在Client端使用JSON.parse()轉換,得到的是字串陣列,內建的JSON.parse()不認得"/Date(1330444800000)/",並不會自動將其轉為Date()。

Object->["/Date(1330444800000)/"]

將以上字串陣列JSON.stringify()後再傳給JavaScriptSerializer.Deserialize(),卻發生以下錯誤:

TEST2:Error: /Date(1330444800000)/ is not a valid value for DateTime.

其實問題根源還挺明顯的,JSON.parse()時,依一般JavaScript法則將"\/"視為"/",故解讀出的字串變成"/Date(1330444800000)/";而在JSON.stringify()處理時,"/Date(1330444800000)/"中的正斜線(/)並非必須使用反斜線額外標示的特殊符號,故維持"/Date(1330444800000)/",而非微軟所期望的"\/Date(1330444800000)\/",於是在Server端轉換時便發生錯誤。

一個鋸箭做法是對JSON.stringify()產生的字串進行加工調整,使用RegExp將其中的"/Date(1330444800000)/"再置換回"\/Date(1330444800000)\/",不過考量過,這樣仍會留下8小時時差的問題待解。另一個做法是將"/Date(1330444800000)/"在Client端轉成JavaScript Date()型別,之後用JSON.stringify()會變成"2012-02-28T16:00:00.000Z",而這個格式可被JavaScriptSerializer.Deserialize()正確解讀成DateTime,還可一併解決時區問題,算是較好的解決方案。

以下是程式範例。程式中宣告了一個parseMsJsonDate()可將"/Date(1330444800000)/"轉為Date(),寫法偷學自Kendo UI範例,已是我第二次應用,改寫時還是不禁對JS高人的巧妙寫法讚嘆不已呀...

    <script>
 
        //JSON日期轉換
        var dateRegExp = /^\/Date\((.*?)\)\/$/;
        window.parseMsJsonDate = function (value) {
            var date = dateRegExp.exec(value);
            return new Date(parseInt(date[1]));
        }
 
        $(function () {
 
            $("#btnTest").click(function () {
                //取得List<DateTime> JSON
                $.get("?mode=get", {}, function (json) {
                    alert("JSON=" + json);
                    //將JSON字串先解析為物件
                    var list = JSON.parse(json);
                    //將\/Date(...)\/解析成Javascript日期型別
                    for (var i = 0; i < list.length; i++)
                        list[i] = parseMsJsonDate(list[i]);
                    var rejson = JSON.stringify(list);
                    $.post("?mode=post", { json: rejson }, function (r) {
                        //正確顯示時間,連時區差問題也一併解決
                        alert(r);
                    });
                });
            });
        });
    </script>

執行時,兩次alert結果如下:

JSON=["\/Date(1330444800000)\/"]

List[0]=2012/2/29 上午 12:00:00


Comments

# by william0657

最近在學 jQuery + json 看了黑暗大的文章,受益良多,感謝分享!! 心得:這篇又讓我學到一個縮寫 【WTF】

Post a comment