天天看點

用 js 實作 JSON.parse 以及 JSON.stringify

最近寫 asp 發現一個問題,古老的 asp 缺少對 json 資料的安全支援,對象 轉字元的方法沒有,字元轉對象 雖然可以通過 eval("(" + json + ")") 的方式實作 json 解析,但是容易被不良人當作木馬入口,你期待的可能是一個正常的 json 字元串,但人家傳過來的有可能是 (function() { Response.Write("Hacked"); })() 之類的 js 執行語句。是以,用 js 實作了 parse 和 stringify 方法:

var JSON = { stringify: tojson, parse: fromjson };
WSH.Echo(JSON.stringify(JSON.parse('[ true, { "key": "value", "arr": [ { } ], "num" : 95.27 }, null, false ]')));

function fromjson(str) {
	var regTag = /[\{\[\"ntf\d\.]/, i = 0, len = str.length;
	function newParse() {
		var s = waitStr(regTag);
		if(!s) return;
		switch(s) {
			case "{": return findObj();
			case "[": return findArr();
			case "t": return findTrue();
			case "f": return findFalse();
			case "n": return findNull();
			case '"': return findStr();
		}
		return findNum(s);
	}

	function findObj() {
		var obj = new Object;
		while(i < len) {
			var s = waitStr(/\S/);
			if(s == "}") break;
			if(s == ",") continue;
			var key = findStr();
			waitStr(/\:/);
			obj[key] = newParse();
		}
		return obj;
	}

	function findArr() {
		var arr = new Array;
		while(i < len) {
			var s = waitStr(/\S/);
			if(s == "]") break;
			if(s == ",") continue;
			i--; arr.push(newParse());
		}
		return arr;
	}

	function findTrue() { i += 3; return true; }
	function findFalse() { i += 4; return false; }
	function findNull() { i += 3; return null; }

	function findStr() {
		var s = new Array;
		while(i < len) {
			var _s = str.charAt(i++);
			if(_s == '"') break;
			if(_s == "\\") _s = strDec(str.charAt(i++));
			s.push(_s);
		}
		return s.join("");
	}

	function findNum(s) {
		while(i < len) {
			var _s = str.charAt(i++);
			if(!/[\d\.]/.test(_s)) break;
			s += _s;
		}
		i--;
		return s - 0;
	}

	function waitStr(reg) {
		while(i < len) {
			var s = str.charAt(i++);
			if(reg.test(s)) return s;
		}
		return "";
	}

	var dic = { t: "\t", n: "\n", r: "\r", b: "\b", f: "\f", v: "\x0b" };
	function strDec(c) {
		switch(c) {
			case "x": return unescape("%" + str.slice(i, i += 2));
			case "u": return unescape("%u" + str.slice(i, i += 4));
		}
		return c in dic ? dic[c] : c;
	};

	return newParse();
}

function tojson(obj) {
	switch(typeof obj) {
		case "string": return toStr();
		case "number": return toNum();
		case "object": return toObj(obj);
		case "boolean": return toBool();
		case "date": return toTime();
		case "function": return obj;
		case "undefined": return '"undefined"';
		default: return "unknown";
	}
	function toStr() { return '"' + obj.replace(/[\"\\]/g, function(str) { return "\\" + str; }).replace(/\r/g, "\\r").replace(/\n/g, "\\n") + '"'; }
	function toNum() { return obj; }
	function toBool() { return obj ? "true" : "false"; }
	function toObj() {
		if(!obj) return "null";
		if(obj instanceof Array) return toArr();
		var arr = new Array;
		for(var x in obj) arr.push(tojson(x + "") + ":" + tojson(obj[x]));
		return "{" + arr.join(",") + "}";
	}
	function toArr() {
		var arr = new Array;
		for(var i = 0; i < obj.length; i++) arr.push(tojson(obj[i]));
		return "[" + arr.join(",") + "]";
	}
	function toTime() {
		var t = new Date(obj - 0);
		var str = t.getFullYear() + "-";
		str += z(t.getMonth() + 1) + "-";
		str += z(t.getDate()) + " ";
		str += z(t.getHours()) + ":";
		str += z(t.getMinutes()) + ":";
		str += z(t.getSeconds());
		return '"' + str + '"';
	}
	function z(num) { return num < 10 ? "0" + num : num; }
}
           

由于從字元轉對象的解析有些複雜,是以沒有做格式校驗,隻實作了安全解析。是以,錯誤的格式不會導緻解析異常,也不會影響正常解析,正常的日常使用是沒問題的。性能方面,因為是根據 json 字元串長度逐字解析,每個字元基本上隻處理一次,是以,字元串長度對性能的影響已經被降到了最低。

繼續閱讀