UNPKG

niosh-json

Version:

extended json parser and stringify

441 lines (440 loc) 25.8 kB
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– define function of revivers and replacers /** * @callback JSONKeyValueFunction * @param {string} key * @param {any} value * @return {any} */ //#endregion //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– define configuration object /** * Object for setup JSON * - debug : active or disactive messages (default false) * - circulars : enable writing of circulars properties (default false) * - nothorws : disable exceptions on errors returning undefined (default false) * @typedef {Object} JSONConfigurationObject * @property {boolean} debug * @property {boolean} circulars * @property {boolean} nothrows */ //#endregion //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– create internal secret memory area called "µ" as memory const µ = JSON.µ = { stringify: JSON.stringify, // backup original stringify parse: JSON.parse, // backup original parse // --------------------------------------------------------------------------------------------------- configuration /** @type {boolean} */ debug: false, // enable debug logging /** @type {boolean} */ circulars: false, // enable output of circular references (stringify) /** @type {boolean} */ nothrows: false, // disable exceptions returning undefined /** @type {string[]} */ replacersNames: [], // vector of replacers names /** @type {string[]} */ replacersInfos: [], // vector of replacers infos /** @type {number} */ replacersMaxLen: 0, // replacers max string len of key names /** @type {string[]} */ reviversNames: [], // vector of revivers names /** @type {string[]} */ reviversInfos: [], // vector of revivers infos /** @type {number} */ reviversMaxLen: 0, // revivers max string len of key names /** @type {number} */ rpAndRevMaxLen: 0, // replacers and revivers max string len of key names /** @type {JSONKeyValueFunction[]} */ replacers: [], // vector of replacers /** @type {JSONKeyValueFunction[]} */ revivers: [], // vector of revivers /** @type {Map<string,function>} */ patterns: new Map(), // map of pattern used for regular expression // --------------------------------------------------------------------------------------------------- replacers manager replacersManager: (replacer)=>{ var cache = []; /** @type {JSONKeyValueFunction} */ var fn = function(key,value) { if (typeof value === "object" && value != null) { let idx = cache.indexOf(value); if (idx >= 0) return (JSON.µ.circulars) ? "[Circular~"+idx+"]" : undefined; cache.push(value); } for(var i=0;i<JSON.µ.replacers.length;i++) { try { value = JSON.µ.replacers[i](key,value); } catch(e) { if (JSON.µ.debug) console.error("JSON-EXCEPTION",`error on replacer id ${i} named «${JSON.µ.replacersNames[i]}»`,e); if (JSON.µ.nothrows) return undefined; throw e; } } if (typeof replacer == "function") { try { value = replacer(key,value); } catch(e) { if (JSON.µ.debug) console.error("JSON-EXCEPTION",`error on replacer given by user code`,e); if (JSON.µ.nothrows) return undefined; throw e; } } return value; } return fn; }, // --------------------------------------------------------------------------------------------------- revivers manager reviversManager: (reviver)=>{ /** @type {JSONKeyValueFunction} */ var fn = function(key,value) { for(var i=0;i<JSON.µ.revivers.length;i++) { try { value = JSON.µ.revivers[i](key,value); } catch(e) { if (JSON.µ.debug) console.error("JSON-EXCEPTION",`error on reviver id ${i} named «${JSON.µ.reviversNames[i]}»`,e); if (JSON.µ.nothrows) return undefined; throw e; } } if (typeof reviver == "function") { try { value = reviver(key,value); } catch(e) { if (JSON.µ.debug) console.error("JSON-EXCEPTION",`error on reviver given by user code`,e); if (JSON.µ.nothrows) return undefined; throw e; } } return value; } return fn; }, // --------------------------------------------------------------------------------------------------- stringify extended stringifyExtended: function(value,replacer,space) { try { return JSON.µ.stringify(value,JSON.µ.replacersManager(replacer),space); } catch(e) { if (JSON.µ.debug) console.error("JSON-EXCEPTION",`error on stringify`,e); if (JSON.µ.nothrows) return undefined; throw e; } }, // --------------------------------------------------------------------------------------------------- parse extended parseExtended: function(text,reviver) { try { return JSON.µ.parse(text,JSON.µ.reviversManager(reviver)); } catch(e) { try { let textWrapper = JSON.stringify({payload:text}); let objectParse = JSON.µ.parse(textWrapper,JSON.µ.reviversManager(reviver)); return objectParse.payload; } catch { if (JSON.µ.debug) console.error("JSON-EXCEPTION",`error on parse`,e); if (JSON.µ.nothrows) return undefined; throw e; } } } }; //#endregion //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– define patterns JSON.µ.patterns.set("DATE",()=> /(^([0-9]{4}-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[12][0-9]))))[Tt]([01][0-9]|2[0123]):[0-5][0-9](:[0-5][0-9](.[0-9]{1,9})?)?([Zz]|([+-])([01][0-9]|2[0123]):?[0-5][0-9])?$)|(^\s*(([0-9]{4}(-\s*|[/\s]+)(((0?[13578]|1[02])(-\s*|[/\s]+)(0?[1-9]|[12][0-9]|3[01]))|((0?[469]|11)(-\s*|[/\s]+)(0?[1-9]|[12][0-9]|30))|(0?2(-\s*|[/\s]+)(0?[1-9]|[12][0-9]))))|((((0?[13578]|1[02])(-\s*|[/\s]+)(0?[1-9]|[12][0-9]|3[01]))|((0?[469]|11)(-\s*|[/\s]+)(0?[1-9]|[12][0-9]|30))|(0?2(-\s*|[/\s]+)(0?[1-9]|[12][0-9])))(-\s*|[/\s]+)[0-9]{4}))\s*(( ([01]?[0-9]|2[0123]):\s*[0-5]?[0-9](:\s*[0-5]?[0-9](.[0-9]{1,9})?)?\s*[Zz]?\s*(([+-])([01]?[0-9]|2[0123]):?\s*[0-5]?[0-9]?)?)|( ?[Zz]\s*([+-]([01]?[0-9]|2[0123]):?\s*[0-5]?[0-9]?)?))?\s*$)/ ); JSON.µ.patterns.set("FEBRUARY29",()=> /^\s*((?<Y1>[0-9]{4})[^0-9]*)?0?2[^0-9]*29([^0-9]*(?<Y2>[0-9]{4}))?/ ); JSON.µ.patterns.set("SYMBOL",()=> /^\s*Symbol\((?<DATA>[\s\S]+(?=\)))\)\s*$/ ); JSON.µ.patterns.set("BIGINT",()=> /^\s*(?<NUMBER>[0-9]+)n\s*/ ); JSON.µ.patterns.set("ERROR",()=> /(^\s* at )|([^\S\n]*$)/gm ); JSON.µ.patterns.set("FIXFLOAT",()=> /(?<FIX>(?<TRIPLE>(?<DIGIT>[0-9])(\k<DIGIT>){2,})(?!.*\k<TRIPLE>)[0-9]*$)/ ); JSON.µ.patterns.set("REGEXP",()=> /^\/(?<DEF>.+)\/(?<OPT>[gmiyusd]*)$/ ); //#endregion //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– reviver for Date object JSON.µ.reviversNames.push("DateObjectReviver"); JSON.µ.reviversInfos.push("convert various types of date string to date object, in other cases return original string"); JSON.µ.revivers.push((key,value)=>{ if (typeof value != "string") return value; // skip if is not a string let regex = JSON.µ.patterns.get("DATE")(); if (regex.exec(value)==null) return value; // skip if not match regurar expression for Date object // --- checking if date contains february 29 regex = JSON.µ.patterns.get("FEBRUARY29")(); let match = regex.exec(value); if (match != null) { // --- checking a valid year let year = parseInt(match.groups.Y1||match.groups.Y2); if (Number.isInteger(year) && year >= 0) { // --- verify if have february 29 for given year let february = new Date(Date.UTC(year,2,0,0,0,0,0)); if (february.getUTCDate() != 29) { // --- fix february 29 with 28 when not have in year regex = JSON.µ.patterns.get("FEBRUARY29")(); value = value.replace(regex,"$<Y1>$<Y2>-02-28"); } } } // --- try to transforming string in a date object and return try { return new Date(value); } catch(e) { if (JSON.µ.debug) console.debug("JSON-EXCEPTION",e); return value; // skip for bad conversion } }); //#endregion //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– reviver for Symbol JSON.µ.reviversNames.push("SymbolReviver"); JSON.µ.reviversInfos.push("include symbol string decoder"); JSON.µ.revivers.push((key,value)=>{ if (typeof value != "string") return value; // skip if is not a string let regex = JSON.µ.patterns.get("SYMBOL")(); let match = regex.exec(value); if (match==null) return value; // skip if not match regurar expression for symbol value = Symbol(match.groups.DATA); return value; }); //#endregion //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– reviver for BigInt JSON.µ.reviversNames.push("BigIntReviver"); JSON.µ.reviversInfos.push("include bigint string decoder"); JSON.µ.revivers.push((key,value)=>{ if (typeof value != "string") return value; // skip if is not a string let regex = JSON.µ.patterns.get("BIGINT")(); let match = regex.exec(value); if (match==null) return value; // skip if not match regurar expression for bigint value = BigInt(match.groups.NUMBER); return value; }); //#endregion //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– reviver for Error object JSON.µ.reviversNames.push("ErrorObjectReviver"); JSON.µ.reviversInfos.push("include Error object string decoder"); JSON.µ.revivers.push((key,value)=>{ if (typeof value != "object") return value; // skip if is not an object if (value == null) return value; // skip if is null object if (Array.isArray(value)) return value; // skip if is an array if (value instanceof Boolean || value instanceof Number || value instanceof String) return value; // skip if is a primitive object if (value instanceof Date || value instanceof Error) return value; // skip if is a Date or Error object if (typeof value.errorObject == "boolean" && value.errorObject == true) { let errorObject = new Error(value.message); errorObject.stack = value.errorClassName + ": " + value.message + "\n at " + value.stack.join("\n at "); errorObject.message = value.message; let props = Object.getOwnPropertyNames(value); while (props.length > 0) { let prop = props.shift(); if (prop != "errorObject" && prop != "errorClassName" && prop != "stack" && prop != "message") errorObject[prop] = value[prop]; } value = errorObject; } return value; }); //#endregion //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– reviver for RegExp object JSON.µ.reviversNames.push("RegularExpressionReviver"); JSON.µ.reviversInfos.push("include RegExp object string decoder"); JSON.µ.revivers.push((key,value)=>{ if (typeof value != "string") return value; // skip if is not a string let regex = JSON.µ.patterns.get("REGEXP")(); let match = regex.exec(value); if (match==null) return value; // skip if not match regurar expression for RegExp object value = new RegExp(match.groups.DEF,match.groups.OPT||""); return value; }); //#endregion //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– replacer for Symbol JSON.µ.replacersNames.push("SymbolReplacer"); JSON.µ.replacersInfos.push("include symbol encoder to string"); JSON.µ.replacers.push((key,value)=>{ if (typeof value == "symbol") { value = "Symbol(" + value.description + ")"; } return value; }); //#endregion //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– replacer for BigInt JSON.µ.replacersNames.push("BigIntReplacer"); JSON.µ.replacersInfos.push("include bigint encoder to string"); JSON.µ.replacers.push((key,value)=>{ if (typeof value == "bigint") { value = value.toString()+"n" } return value; }); //#endregion //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– replacer for Error object JSON.µ.replacersNames.push("ErrorObjectReplacer"); JSON.µ.replacersInfos.push("include Error object encoder to string"); JSON.µ.replacers.push((key,value)=>{ if (value instanceof Error) { let errorObject = { errorObject: true }; let props = Object.getOwnPropertyNames(value); while (props.length > 0) { let prop = props.shift(); errorObject[prop] = value[prop]; } if (typeof errorObject.stack == "string") { errorObject.stack = errorObject.stack.trim().replace(JSON.µ.patterns.get("ERROR")(),"").split("\n"); errorObject.errorClassName = errorObject.stack.shift(); errorObject.errorClassName = errorObject.errorClassName.substring(0,errorObject.errorClassName.indexOf(":")).trim(); } value = errorObject; } return value; }); //#endregion //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– replacer for RegExp object JSON.µ.replacersNames.push("RegularExpressionReplacer"); JSON.µ.replacersInfos.push("include RegExp object encoder to string"); JSON.µ.replacers.push((key,value)=>{ if (value instanceof RegExp) value = value.toString(); return value; }); //#endregion //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-––––––––––––––———–––––––––––––– finalize JSON object stringify and parse JSON.stringify = function(value,replacer,spaces) { return JSON.µ.stringifyExtended(value,replacer,spaces); }; JSON.parse = function(text,reviver) { return JSON.µ.parseExtended(text,reviver); }; // --------------------------------------------------------------------------------------------------- calculate max strings lenghts const JSONRefreshInformations = function() { JSON.µ.replacersMaxLen = 0; JSON.µ.reviversMaxLen = 0; JSON.µ.rpAndRevMaxLen = 0; for (var i=0;i<JSON.µ.replacersNames.length;i++) { let len = JSON.µ.replacersNames[i].length; if (JSON.µ.replacersMaxLen<len) JSON.µ.replacersMaxLen = len; if (JSON.µ.rpAndRevMaxLen<len) JSON.µ.rpAndRevMaxLen = len; } for (var i=0;i<JSON.µ.reviversNames.length;i++) { let len = JSON.µ.reviversNames[i].length; if (JSON.µ.reviversMaxLen<len) JSON.µ.reviversMaxLen = len; if (JSON.µ.rpAndRevMaxLen<len) JSON.µ.rpAndRevMaxLen = len; } }; JSONRefreshInformations(); // --------------------------------------------------------------------------------------------------- adding extension for manage revivers and replacers /** @return {string[]} */ JSON.replacersKeys = function() { return [...JSON.µ.replacersNames]; }; /** @return {string[]} */ JSON.reviversKeys = function() { return [...JSON.µ.reviversNames]; }; // --------------------------------------------------------------------------------------------------- replacers informations text /** @return {string[]} */ JSON.replacersInfos = function() { let infos = JSON.replacersKeys(); for (var i=0;i<infos.length;i++) infos[i]= infos[i].padEnd(JSON.µ.replacersMaxLen + 5," ") + " • " + JSON.µ.replacersInfos[i]; return infos; }; // --------------------------------------------------------------------------------------------------- revivers informations text /** @return {string[]} */ JSON.reviversInfos = function() { let infos = JSON.reviversKeys(); for (var i=0;i<infos.length;i++) { infos[i]= infos[i].padEnd(JSON.µ.reviversMaxLen + 5," ") + " • " + JSON.µ.reviversInfos[i]; } return infos; }; // --------------------------------------------------------------------------------------------------- replacers and revivers informations text /** @return {string[]} */ JSON.repAndRevInfos = function() { let infos = JSON.replacersKeys(); for (var i=0;i<infos.length;i++) infos[i]= infos[i].padEnd(JSON.µ.rpAndRevMaxLen + 5," ") + "🧾 ⏺ replacer • " + JSON.µ.replacersInfos[i]; let adder = infos; infos = JSON.reviversKeys(); for (var i=0;i<infos.length;i++) { infos[i]= infos[i].padEnd(JSON.µ.rpAndRevMaxLen + 5," ") + "🧮 ⏺ reviver • " + JSON.µ.reviversInfos[i]; } adder.push(...infos); infos = adder; return infos; }; //#endregion //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-––––––––––––––———–––––––––––––– JSONex static class for extension class JSONex { /** * Current additional replacers informational data * @returns {string[]} * */ static replacersInformations() { return JSON.replacersInfos(); } /** * Current additional revivers informational data * @returns {string[]} * */ static reviversInformations() { return JSON.reviversInfos(); } /** * Current additional replacers and revivers informational data * @returns {string[]} * */ static repAndRevInformations() { return JSON.repAndRevInfos(); } /** * Add additional replacer function * @param {string} key * @param {string} info * @param {JSONKeyValueFunction} fn */ static addReplacer(key,info,fn) { if (JSON.µ.replacersNames.indexOf(key)>=0) throw new Error(`JSON-EXCEPTION: replacer «${key}» already exists`); JSON.µ.replacersNames.push(key); JSON.µ.replacersInfos.push(info); JSON.µ.replacers.push(fn); JSONRefreshInformations(); } /** * Add additional reviver function * @param {string} key * @param {string} info * @param {JSONKeyValueFunction} fn */ static addReviver(key,info,fn) { if (JSON.µ.reviversNames.indexOf(key)>=0) throw new Error(`JSON-EXCEPTION: reviver «${key}» already exists`); JSON.µ.reviversNames.push(key); JSON.µ.reviversInfos.push(info); JSON.µ.revivers.push(fn); JSONRefreshInformations(); } /** * Return current JSON configuration * @returns {JSONConfigurationObject} */ static getConfiguration() { return { debug: JSON.µ.debug, circulars: JSON.µ.circulars, nothrows: JSON.µ.nothrows }; } /** * Update current JSON configuration * @param {JSONConfigurationObject} configuration */ static setConfiguration(configuration) { if (typeof configuration == "object" && configuration != null) { JSON.µ.debug = (typeof configuration.debug == "boolean") ? configuration.debug : JSON.µ.debug; JSON.µ.circulars = (typeof configuration.circulars == "boolean") ? configuration.circulars : JSON.µ.circulars; JSON.µ.nothrows = (typeof configuration.nothrows == "boolean") ? configuration.nothrows : JSON.µ.nothrows; } } } //#endregion //#region –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––-–––––––––––––––-––––––––––––––– module exporting require('./extensions'); module.exports = { JSONex }; //#endregion