2012年5月10日 星期四

jQuery JSON 與 ASP.NET WebService 有關日期返回值處理經驗談

設計 Ajax 習慣使用 jQuery 的 ajaxr功能,搭配 .NET 的 WebService 來提供資料,一般資料的傳遞並無問題,但由 WebService返回 DateTime 物件時, Client 端的 JSON(使用http://www.json.org/ json2.js )無法完全轉成日期物件,而是字串,使得物件的序列/反序列不完全。

經追蹤測試,由WebService 回傳 (ObjectQuery<UserData> 一個Entity Framework 的資料表物件),在 return 時是否使 Serialize()進行序列化,會有不同結果
ID:      帳號 (String)
PWD:密碼(String)
Unit:   所屬單位(String)
Authorize:層級(String)
Name:  姓名((String)
ModifiedDate:資料更動日期(DateTime)
CreatedDate: 資料建立日期(DateTime)
FailedLoginCount:登入失敗次數紀錄(int)
LastLoginDate:最近一次成功登入日期 (DateTime)
LastFailedLoginDate:最近一次登入失敗日期(DateTime)


若在 ASP.NET 先經 JavaScriptSerializer.Serialize((ObjectQuery<UserData>)users) 進行序列化,則回傳 jQuery 字串為:
"{"d":"[{\"ID\":\"a\",\"PWD\":\"7367cc4cee061a476290d18978830414\",\"Unit\":\"資訊室\",\"Authorize\":\"1\",\"Name\":\"aA\",\"ModifiedDate\":\"\\/Date(1217994242220)\\/\",\"CreatedDate\":\"\\/Date(1217992404960)\\/\",\"FailedLoginCount\":null,\"LastLoginDate\":null,\"LastFailedLoginDate\":null,\"EntityState\":2,\"EntityKey\":{\"EntitySetName\":\"UserData\",\"EntityContainerName\":\"DraftEntities\",\"EntityKeyValues\":[{\"Key\":\"ID\",\"Value\":\"a\"}],\"IsTemporary\":false}}]"}"


jquery於ajax完成後,如果成功,則 success:function(reqObj) 會自動調用 JSON.parse(reqObj)對回傳資料進行剖析後(注意:.Net 的真正資料是存在 reqObj.d裡),所以在變成下列「字串」,必須再將 JSON.parse()處理一次。
reqObj.d="[{"ID":"a","PWD":"7367cc4cee061a476290d18978830414","Unit":"資訊室","Authorize":"1","Name":"aA","ModifiedDate":"\/Date(1217994242220)\/","CreatedDate":"\/Date(1217992404960)\/","FailedLoginCount":null,"LastLoginDate":null,"LastFailedLoginDate":null,"EntityState":2,"EntityKey":{"EntitySetName":"UserData","EntityContainerName":"DraftEntities","EntityKeyValues":[{"Key":"ID","Value":"a"}],"IsTemporary":false}}]"


但因為就算重新 parse,日期部分仍為字串 「/Date(1217994242220)/」, Client端必須另行對日期欄進行處理  eval(result[i].LastLoginDate.replace('/','"')) 才能真正得到 javascript 的日期物件。

asp.net允許物件不經過JavaScriptSerializer.Serialize(物件) 序列化,而直接回傳 (ObjectQuery<UserData>)users 物件的字串,
"{"d":[{"__type":"WebTemplate.Shared.UserData","ID":"a","PWD":"7367cc4cee061a476290d18978830414","Unit":"資訊室","Authorize":"2","Name":"aA","ModifiedDate":"\/Date(1217994242220)\/","CreatedDate":"\/Date(1217992404960)\/","FailedLoginCount":null,"LastLoginDate":null,"LastFailedLoginDate":null,"EntityState":2,"EntityKey":{"EntitySetName":"UserData","EntityContainerName":"DraftEntities","EntityKeyValues":[{"Key":"ID","Value":"a"}],"IsTemporary":false}}]}"
經 JSON.parse(jquery會自動調用)後變成「物件陣列」,唯日期欄位內容會以文字方式表示:
"/Date(1217992404960)/"

如果再將返回之物件傳給 JSON.parse() 剖析,會產生錯誤(例外事件)

由上面的結果可知,不論 .Net 端是否經過序列化(Serialize)過程,經由 jQuery ajax 取得回傳的物件,其中日期物件部分,皆以"\Date(nnnnn)\" 之字串型式回傳
為了解決這個問題,最徹底的方法就是改寫 json2.js,修正說明如下:(我取得的版本是 2011-10-19)

、原程式的 462-465 行,是用來判斷字串是不是可以轉換成 javascript 物件:
if (/^[\],:{}\s]*$/
         .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
                   .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
                   .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
 
而 472 行就是利用 eval 將字串轉成 物件。
                j = eval('(' + text + ')');


、所以我們要修改這裡的程式,使其可將  /Date(nnnnn) / 轉成  new Date(nnnn)
首先,原始由 .Net 回傳的日期在字串的為
     \"\\/Date(nnnnn)\\/\"   (經過 序列化)
            或
    "\/Date(nnnnn)\/"     (直接物件回傳)

所以我們在丟給 eval 之前要先將上面的字串內容換成   new Date(nnnnn),於是我在 461 行,加入了:
text = text.replace(/(\\\"\\\\\/){1}(Date\(\d*\)){1}(\\\\\/\\"\\){1}/g, "new $2")
                       .replace(/(\"\\\/){1}(Date\(\d*\)){1}(\\\/\"){1}/g, "new $2"); 

 但加了這一行之後,只要有日期物件傳入,就會引發例外,原來 new Date(nnnnn) 無法通過上述 462 - 465 的 test ,因此還還修改462 - 465 的程式碼,如下列藍字部分
if (/^[\],:{}\s]*$/
                    .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
                        .replace(/new Date\([0-9]*\)/ig, '')
                        .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
                        .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {


這樣就可以應付上列兩種日期字串格式。

三、 記得:在 .Net 端如果有進行 JavaScriptSerializer.Serialize()序列化,在Client端除了 jQuery自動調用 JSON.parse() 外,還必須在程式中進行一次 JSON.parse(retObj.d),才會得到真正的物件內容。如果.Net 沒有進行 JavaScriptSerializer.Serialize()序列化,就不須再一次 JSON.parse(retObj.d),不然會出錯!

** 此方法僅對 .Net 之回傳資料測試通過,其他程式語言未測試 ,不保證適用 **

XXX 為什麼 IE6 可以自動調用 parse,但在 IE8、FireFox  就是不行 ?????
原來 IE8 與 FireFox(11)已內建原生 JSON  程式,所以 jQuery 會直接調用原生的 JSON.parse,以致上面所述修改 json2.js 的功用皆失效,根本不會叫用 json2.js。
查看 json2.js  程式碼之 162 至 165 來

var JSON;
if (!JSON) {
    JSON = {};
}

即判斷如果已註冊 JSON 物件,則 json2 就不再註冊此功能,為了能使用上面針對日期物件修正的功能,只好把 163、165兩行註解掉,強制 jQuery 使用 jsons2.js 的 JSON 功能!

var JSON;
// if (!JSON) {
    JSON = {};
//}

相關文章:JSON.parse 無去剖析 .NET2 的JSON 日期
修改後之下載(zip檔)  json2.cmj.js

沒有留言:

張貼留言