小把戲 - 讓 JSON.parse() 內建日期解析功能
0 | 3,981 |
JSON.parse 時另外指定 Reviver 函式將 "yyyy-MM-ddTHH:mm:ssZ" 轉成 Date 型別,已是我用 JavaScript 解析 JSON 字串的SOP。參考:我慣用的標準做法 幾乎每次使用都要加工,一直抱怨為何瀏覽器不直接內建。先前寫 Knockout.js、AngularJS,我都會寫個共用 AJAX 呼叫函式,在其中內含 Date Reviver 邏輯。這幾天起了一個小專案,不想動用 KO、NG、Vue.js 造成接手同學困擾,決定用純 jQuery 解決,再度碰上日期字串無法反序列回 Date 物件的問題。
用以下範例展示,假設有 demo.json 內容為 [ "2012-12-21T00:00:00Z" ]
,使用 jQuery.getJSON() 取回,期望解析為日期物件陣列但事與願違,預設 demo.json 將被解析為字串陣列。如下所示:
<html>
<body>
<div></div>
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script>
$.getJSON("demo.json").done(function(data) {
$("div").text("type[" + typeof(data[0]) + "]: " + data[0]);
});
</script>
</body>
</html>
得到結果為 type[string]: 2012-12-21T00:00:00Z
。一個簡單的解決方法是改成呼叫 $.ajax() 並自訂 converter,JSON.parse() 時傳入自訂 Reviver 函式:
<html>
<body>
<div></div>
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script>
//REF: https://blog.darkthread.net/blog/jsonnet-datetimekind-issue-solution/
function dateReviver(key, value) {
var a;
if (typeof value === 'string') {
//UTC
a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
}
//Unspecified
a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)$/.exec(value);
if (a) {
return new Date(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]);
}
//with Timezone
a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)([+-])(\d{2}):(\d{2})$/.exec(value);
if (a) {
var dir = a[7] == "+" ? -1 : 1;
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4] + dir * a[8], +a[5] + dir * a[9], +a[6]));
}
}
return value;
}
</script>
<script>
$.ajax({
type: "GET",
url: "demo.json",
converters: {
"text json": function(json) {
return JSON.parse(json, dateReviver);
}
}
}).done(function(data) {
$("div").text(Object.prototype.toString.call(data[0]) + ": " + data[0]);
});
</script>
</body>
</html>
修改後 ISO 8601 格式字串將一律轉為 Date 物件。結果為 [object Date]: Fri Dec 21 2012 08:00:00 GMT+0800 (台北標準時間)
。
但這招只解決 jQuery.ajax 時的 JSON 解析問題,我想更進一步修改 JSON.parse(),讓它預設自動將 ISO 8601 格式轉成 Date,如此便省去每次 JSON.parse() 都得加工傳參數的麻煩。拜 JavaScript 靈活彈性之賜,只需對 JSON.parse 動點手腳就可以實現夢想。
如以下程式範例,我先將原生的 JSON.parse 備份到 JSON._parse,另寫一個新版本取代它。新版本底層也是呼叫原有 JSON._parse,只是一律多傳 dateReviver 函式處理日期,為維持原有可自訂 Reviver 函式的彈性,新版本也要接受 reviver 參數,遇到外部傳入 reviver 函式時,改用自訂邏輯解析。如此,外界呼叫 JSON.parse() 時不必傳入 reviver 預設就有日期解析功能。
<html>
<body>
<div id=d1></div>
<div id=d2></div>
<div id=d3></div>
<script>
JSON._parse = JSON.parse;
JSON.parse = function(json, reviver) {
function dateReviver(key, value) {
var a;
if (typeof value === 'string') {
//UTC
a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]));
}
//Unspecified
a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)$/.exec(value);
if (a) {
return new Date(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6]);
}
//with Timezone
a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)([+-])(\d{2}):(\d{2})$/.exec(value);
if (a) {
var dir = a[7] == "+" ? -1 : 1;
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4] + dir * a[8], +a[5] + dir * a[9], +a[6]));
}
}
if (reviver) return reviver(key, value);
return value;
}
return JSON._parse(json, dateReviver);
}
</script>
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script>
$.getJSON("demo.json").done(function(data) {
$("#d1").text(Object.prototype.toString.call(data[0]) + ": " + data[0]);
});
var json = '"2012-12-21T00:00:00Z"';
$("#d2").text(Object.prototype.toString.call(JSON.parse(json)));
$("#d3").text(JSON.parse('{"a":"123"}', function(key,value) {
if (key=="a") return "456";
return value;
}).a);
</script>
</body>
</html>
測試成功,結果為:
[object Date]: Fri Dec 21 2012 08:00:00 GMT+0800 (台北標準時間)
[object Date]
456
需注意,置換 JSON.parse 的把戲要在載入 jquery.js 前完成才能順利讓 jQuery.ajax 改用新版 JSON.parse。範例中我做了三段測試,jQuery.getJSON()、JSON.parse() 都可直接將日期字串解析為日期物件,最後測試自訂 Reviver 硬將屬性 a 覆寫成 456,驗證自訂 Reviver 函式有效。
以上小把戲提供大家參考。
A little trick to replace JSON.parse() with a modified version with built-in date reviver function. It can save a lot of time passing reviver function to JSON.parse() every time.
Comments
Be the first to post a comment