/* 文件名:Json_Class.nut 路径:Base/_Tool/Json_Class.nut 创建日期:2024-09-27 23:54 文件用途:Json类 */ class JSONParser { static version = "1.0.1"; state = ""; stack = null; container = null; key = ""; value = ""; converter = null; constructor() { stack = []; container = {}; } function parse(str, ...) { if (vargc > 0) converter = vargc[0]; // actions for string tokens local string = { go = function() { state = "ok"; }.bindenv(this), firstokey = function() { key = value; state = "colon"; }.bindenv(this), okey = function() { key = value; state = "colon"; }.bindenv(this), ovalue = function() { value = this._convert(value, "string", converter); state = "ocomma"; }.bindenv(this), firstavalue = function() { value = this._convert(value, "string", converter); state = "acomma"; }.bindenv(this), avalue = function() { value = value; this._convert(value, "string", converter); state = "acomma"; }.bindenv(this) }; // the actions for number tokens local number = { go = function() { state = "ok"; }.bindenv(this), ovalue = function() { value = this._convert(value, "number", converter); state = "ocomma"; }.bindenv(this), firstavalue = function() { value = this._convert(value, "number", converter); state = "acomma"; }.bindenv(this), avalue = function() { value = this._convert(value, "number", converter); state = "acomma"; }.bindenv(this) }; // action table // describes where the state machine will go from each given state local action = {}; action["{"] <- { go = function() { stack.push({ state = "ok" }); container = {}; state = "firstokey"; }.bindenv(this), ovalue = function() { stack.push({ container = container, state = "ocomma", key = key }); container = {}; state = "firstokey"; }.bindenv(this), firstavalue = function() { stack.push({ container = container, state = "acomma" }); container = {}; state = "firstokey"; }.bindenv(this), avalue = function() { stack.push({ container = container, state = "acomma" }); container = {}; state = "firstokey"; }.bindenv(this) }, action["}"] <- { firstokey = function() { local pop = stack.pop(); value = container; container = ("container" in pop) ? pop.container : null; key = ("key" in pop) ? pop.key : null; state = pop.state; }.bindenv(this), ocomma = function() { local pop = stack.pop(); container[key] <- value; value = container; container = ("container" in pop) ? pop.container : null; key = ("key" in pop) ? pop.key : null; state = pop.state; }.bindenv(this) }, action["["] <- { go = function() { stack.push({ state = "ok" }); container = []; state = "firstavalue"; }.bindenv(this), ovalue = function() { stack.push({ container = container, state = "ocomma", key = key }); container = []; state = "firstavalue"; }.bindenv(this), firstavalue = function() { stack.push({ container = container, state = "acomma" }); container = []; state = "firstavalue"; }.bindenv(this), avalue = function() { stack.push({ container = container, state = "acomma" }); container = []; state = "firstavalue"; }.bindenv(this) }, action["]"] <- { firstavalue = function() { local pop = stack.pop(); value = container; container = ("container" in pop) ? pop.container : null; key = ("key" in pop) ? pop.key : null; state = pop.state; }.bindenv(this), acomma = function() { local pop = stack.pop(); container.push(value); value = container; container = ("container" in pop) ? pop.container : null; key = ("key" in pop) ? pop.key : null; state = pop.state; }.bindenv(this) }, action[":"] <- { colon = function() { // Check if the key already exists // NOTE previous code used 'if (key in container)...' // but this finds table ('container') member methods too local err = false; foreach(akey, avalue in container) { if (akey == key) err = true; break } if (err) throw "Duplicate key \"" + key + "\""; state = "ovalue"; }.bindenv(this) }, action[","] <- { ocomma = function() { container[key] <- value; state = "okey"; }.bindenv(this), acomma = function() { container.push(value); state = "avalue"; }.bindenv(this) }, action["true"] <- { go = function() { value = true; state = "ok"; }.bindenv(this), ovalue = function() { value = true; state = "ocomma"; }.bindenv(this), firstavalue = function() { value = true; state = "acomma"; }.bindenv(this), avalue = function() { value = true; state = "acomma"; }.bindenv(this) }, action["false"] <- { go = function() { value = false; state = "ok"; }.bindenv(this), ovalue = function() { value = false; state = "ocomma"; }.bindenv(this), firstavalue = function() { value = false; state = "acomma"; }.bindenv(this), avalue = function() { value = false; state = "acomma"; }.bindenv(this) }, action["null"] <- { go = function() { value = null; state = "ok"; }.bindenv(this), ovalue = function() { value = null; state = "ocomma"; }.bindenv(this), firstavalue = function() { value = null; state = "acomma"; }.bindenv(this), avalue = function() { value = null; state = "acomma"; }.bindenv(this) } // state = "go"; stack = []; // current tokenizeing position local start = 0; try { local result, token, tokenizer = _JSONTokenizer(); while (token = tokenizer.nextToken(str, start)) { if ("ptfn" == token.type) { // punctuation/true/false/null action[token.value][state](); } else if ("number" == token.type) { // number value = token.value; number[state](); } else if ("string" == token.type) { // string value = tokenizer.unescape(token.value); string[state](); } start += token.length; } } catch (e) { state = e; } if (state != "ok" || regexp("[^\\s]").capture(str, start)) { local near = str.slice(start, GetMin(str.len(), start + 10)); throw "JSON Syntax Error near `" + near + "`"; } return value; } function GetMin(a, b) { return a< b ? a : b; } function _convert(value, type, converter) { if ("function" == typeof converter) { // # of params for converter function local parametercCount = 2; // .getinfos() is missing on ei platform if ("getinfos" in converter) { parametercCount = converter.getinfos().parameters.len() - 1; } if (parametercCount == 1) { return converter(value); } else if (parametercCount == 2) { return converter(value, type); } else { throw "Error: converter function must take 1 or 2 parameters" } } else if ("number" == type) { local Ret = (value.find(".") == null && value.find("e") == null && value.find("E") == null) ? value.tointeger() : value.tofloat(); return Ret; } else { return value; } } } class _JSONTokenizer { _ptfnRegex = null; _numberRegex = null; _stringRegex = null; _ltrimRegex = null; _unescapeRegex = null; constructor() { // punctuation/true/false/null this._ptfnRegex = regexp("^(?:\\,|\\:|\\[|\\]|\\{|\\}|true|false|null)"); // numbers this._numberRegex = regexp("^(?:\\-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)"); // strings this._stringRegex = regexp("^(?:\\\"((?:[^\\r\\n\\t\\\\\\\"]|\\\\(?:[\"\\\\\\/trnfb]|u[0-9a-fA-F]{4}))*)\\\")"); // ltrim pattern this._ltrimRegex = regexp("^[\\s\\t\\n\\r]*"); // string unescaper tokenizer pattern this._unescapeRegex = regexp("\\\\(?:(?:u\\d{4})|[\\\"\\\\/bfnrt])"); } function nextToken(str, ...) { local start = 0; if (vargc > 0) start = vargv[0]; local m, type, token, value, length, whitespaces; // count # of left-side whitespace chars whitespaces = this._leadingWhitespaces(str, start); start += whitespaces; if (m = this._ptfnRegex.capture(str, start)) { // punctuation/true/false/null value = str.slice(m[0].begin, m[0].end); type = "ptfn"; } else if (m = this._numberRegex.capture(str, start)) { // number value = str.slice(m[0].begin, m[0].end); type = "number"; } else if (m = this._stringRegex.capture(str, start)) { // string value = str.slice(m[1].begin, m[1].end); type = "string"; } else { return null; } token = { type = type, value = value, length = m[0].end - m[0].begin + whitespaces }; return token; } function _leadingWhitespaces(str, start) { local r = this._ltrimRegex.capture(str, start); if (r) { return r[0].end - r[0].begin; } else { return 0; } } // unesacape() replacements table _unescapeReplacements = { b = "\b", f = "\f", n = "\n", r = "\r", t = "\t" }; function unescape(str) { local start = 0; local res = ""; while (start< str.len()) { local m = this._unescapeRegex.capture(str, start); if (m) { local token = str.slice(m[0].begin, m[0].end); // append chars before match local pre = str.slice(start, m[0].begin); res += pre; if (token.len() == 6) { res += token; } else { local char = token.slice(1); if (char in this._unescapeReplacements) { res += this._unescapeReplacements[char]; } else { res += char; } } } else { res += str.slice(start); break; } start = m[0].end; } return res; } } class JSONEncoder { static VERSION = "2.0.0"; // max structure depth // anything above probably has a cyclic ref static _maxDepth = 32; function encode(value) { return this._encode(value); } function _encode(val, ...) { local depth = 0; if (vargc > 0) depth = vargv[0]; // detect cyclic reference if (depth > this._maxDepth) { throw "Possible cyclic reference"; } local r = "", s = "", i = 0; switch (typeof val) { case "table": case "class": s = ""; // serialize properties, but not functions foreach(k, v in val) { if (typeof v != "function") { s += ",\"" + k + "\":" + this._encode(v, depth + 1); } } s = s.len() > 0 ? s.slice(1) : s; r += "{" + s + "}"; break; case "array": s = ""; for (i = 0; i< val.len(); i++) { s += "," + this._encode(val[i], depth + 1); } s = (i > 0) ? s.slice(1) : s; r += "[" + s + "]"; break; case "integer": case "float": case "bool": r += val; break; case "null": r += "null"; break; case "instance": if ("_serializeRaw" in val && typeof val._serializeRaw == "function") { // include value produced by _serializeRaw() r += val._serializeRaw().tostring(); } else if ("_serialize" in val && typeof val._serialize == "function") { // serialize instances by calling _serialize method r += this._encode(val._serialize(), depth + 1); } else { s = ""; try { // iterate through instances which implement _nexti meta-method foreach(k, v in val) { s += ",\"" + k + "\":" + this._encode(v, depth + 1); } } catch (e) { // iterate through instances w/o _nexti // serialize properties, but not functions foreach(k, v in val.getclass()) { if (typeof v != "function") { s += ",\"" + k + "\":" + this._encode(val[k], depth + 1); } } } s = s.len() > 0 ? s.slice(1) : s; r += "{" + s + "}"; } break; case "blob": // This is a workaround for a known bug: // on device side Blob.tostring() returns null // (instaead of an empty string) r += "\"" + (val.len() ? this._escape(val.tostring()) : "") + "\""; break; // strings and all other default: r += "\"" + this._escape(val.tostring()) + "\""; break; } return r; } function _escape(str) { local res = ""; for (local i = 0; i< str.len(); i++) { local ch1 = (str[i] & 0xFF); if ((ch1 & 0x80) == 0x00) { // 7-bit Ascii ch1 = format("%c", ch1); if (ch1 == "\"") { res += "\\\""; } else if (ch1 == "\\") { res += "\\\\"; } else if (ch1 == "/") { res += "\\/"; } else if (ch1 == "\b") { res += "\\b"; } else if (ch1 == "\f") { res += "\\f"; } else if (ch1 == "\n") { res += "\\n"; } else if (ch1 == "\r") { res += "\\r"; } else if (ch1 == "\t") { res += "\\t"; } else if (ch1 == "\0") { res += "\\u0000"; } else { res += ch1; } } else { if ((ch1 & 0xE0) == 0xC0) { // 110xxxxx = 2-byte unicode local ch2 = (str[++i] & 0xFF); res += format("%c%c", ch1, ch2); } else if ((ch1 & 0xF0) == 0xE0) { // 1110xxxx = 3-byte unicode local ch2 = (str[++i] & 0xFF); local ch3 = (str[++i] & 0xFF); res += format("%c%c%c", ch1, ch2, ch3); } else if ((ch1 & 0xF8) == 0xF0) { // 11110xxx = 4 byte unicode local ch2 = (str[++i] & 0xFF); local ch3 = (str[++i] & 0xFF); local ch4 = (str[++i] & 0xFF); res += format("%c%c%c%c", ch1, ch2, ch3, ch4); } } } return res; } }