作者:Truly
日期:2007.7.29
如我前面文章介紹的那樣,JSON在現代JavaScript編程中佔據了重要位置,如果你閱讀過大型的類庫代碼,例如AjaxPro,MS ASP.NET AJAX等,你會發現,在組織和互動資料和都普遍採用了JSON方式。我前面那篇文章《設計模式在JavaScript中的應用(1)》中,使用了較為簡單的資料對應,為了避免時區的差別,我將日期用JSON簡單表示為如下格式:
"CreateDate":new Date('Wed, 25 Jul 2007 09:59:00 GMT+8')
這樣的日期表示在小型應用中具有簡單,可讀性高的優點,但是對於大型應用程式而言,更好的組織資料,壓縮資料,減少通訊量則更為重要。今天我們探討一下微軟ASP.NET AJAX中日期類型的JSON處理。
首先看一下ASP.NET AJAX伺服器端對日期類型JSON序列化的處理:(詳見Ajax擴充源碼中的JavaScriptSerializer.cs)
internal static readonly long DatetimeMinTimeTicks = (new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).Ticks;
private static void SerializeDateTime(DateTime datetime, StringBuilder sb) {
sb.Append(@"""\/Date(");
sb.Append((datetime.ToUniversalTime().Ticks - DatetimeMinTimeTicks) / 10000);
sb.Append(@")\/""");
}
最早期,微軟使用下面方法進行序列化:
ASP.NEXT 中的 AJAX JSON 序列化程式將 DateTime 執行個體編碼為 JSON 字串。在預發布周期中,ASP.NET AJAX 使用格式“@ticks@”,其中 ticks 表示從通用協調時間 (UTC) 1970 年 1 月 1 日起經過的毫秒數。以 UTC 表示的日期和時間(如 1989 年 11 月 29 日 4:55:30 AM)會編碼為“@62831853071@”。雖然簡單易懂,但此格式無法區分序列化的日期和時間值與看起來像序列化日期但又不需要進行還原序列化的值。因此,ASP.NET AJAX 團隊對最終版本進行了更改,通過採用“\/Date(ticks)\/”格式解決了這一問題。
新格式藉助一個小技巧減少了誤解的可能性。在 JSON 中,字串中的正斜杠 (/) 字元可以用反斜線 (\) 進行轉義(即使沒有對此進行嚴格要求)。ASP.NET AJAX 團隊利用這點修改了 JavaScriptSerializer,將 DateTime 執行個體編寫為字串“\/Date(ticks)\/”。兩個正斜杠的轉義只是表面的,但對 JavaScriptSerializer 至關重要。按照 JSON 規則,“\/Date(ticks)\/” 在技術上相當於 “/Date(ticks)/”,但 JavaScriptSerializer 會將前者還原序列化為 DateTime,將後者還原序列化為 String。因此與預發布版本的“@ticks@”格式相比,混淆的可能性會大大減少。
註:此段文字來源於MSDN文章《JavaScript 和 .NET 中的 JavaScript Object Notation (JSON) 簡介》
這裡我要補充一個技巧,大家知道,在document中如果出現<script>和</script>那麼腳步解譯器就會對其中文本進行解析,所以如果你編寫下面的代碼,則會提示錯誤:
<script>
document.write("<script>alert(1);</script>");
</script>
因為中間出現了“</script>”這樣的代碼,那麼我們可以使用:
<script>
document.write("<script>alert(1);</scr"+"ipt>");
</script>
來避免這種錯誤,這時我們還可以使用“\”來轉義:
<script>
document.write("<script>alert(1);<\/script>");
</script>
下面我們看ASP.NET AJAX指令碼庫中對日期的還原序列化代碼:
Sys.Serialization.JavaScriptSerializer.deserialize = function Sys$Serialization$JavaScriptSerializer$deserialize(data) {
/// <param name="data" type="String"></param>
/// <returns></returns>
var e = Function._validateParams(arguments, [
{name: "data", type: String}
]);
if (e) throw e;
if (data.length === 0) throw Error.argument('data', Sys.Res.cannotDeserializeEmptyString);
try {
var exp = data.replace(new RegExp('(^|[^\\\\])\\"\\\\/Date\\((-?[0-9]+)\\)\\\\/\\"', 'g'), "$1new Date($2)");
return eval('(' + exp + ')');
}
catch (e) {
throw Error.argument('data', Sys.Res.cannotDeserializeInvalidJson);
}
}
我把這個序列化器相關代碼整理到Sys.Serialization.js檔案中了,有興趣的可以下載查看。
總結:這樣的方式很有參考價值,值得我們在封裝資料時借鑒,關於JS對資料的壓縮,我前面有一篇簡易的作法《壓縮JavaScript的宏》,而更加進階的混淆壓縮,將留到下一篇文章進行重點研討。
謝謝閱讀。