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

Post a comment