天天看点

用 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 字符串长度逐字解析,每个字符基本上只处理一次,因此,字符串长度对性能的影响已经被降到了最低。

继续阅读