UNPKG

@jaisocx/jpath

Version:

JPath accepts queries of datatype (string|number)[] and performs lookup in js objects and arrays

397 lines (313 loc) 11.9 kB
class JPath { static JPATH_EXPRESSION_MAX_SIZE = 8; _jpathExpressionMaxSize; constructor() { this._jpathExpressionMaxSize = JPath.JPATH_EXPRESSION_MAX_SIZE; } // jpath string exression as "subtree[1].opened" => [ "subtree", 1, "opened" ] // with this art of array of properties names of javascript object tree // it is easier to get the property value of any datatype in javascript objects and arrays. // later usage of the jpath array: // let jpath = JPath.parse( "subtree[1].opened" ); // let obj = { "subtree": [{ "opened": false }, { "opened": true }] }; // let valueFound = JPath.getByJPath( jpath, obj ); // console.log( valueFound ); // prints out => true parse(jpathExpression) { const jpathArray = []; const jpathExpressionSubstrings_ByLeftEnbraces = jpathExpression.split("["); let jpathKeyString = ""; let jpathKeyNumeric = 0; let counter_left_enbraces = 1; let jpathExpressionArray_ByLeftEnbracesLen = jpathExpressionSubstrings_ByLeftEnbraces.length; let maxNumber_left_enbraces = (jpathExpressionArray_ByLeftEnbracesLen + 2); let id_left_enbraces = 0; let splittedBy_left_enbraces_substr = ""; let firstLeftBracePos = jpathExpression.indexOf("["); //@ts-ignore let jpathItemsAdded = 0; whl_left_enbraces: while (counter_left_enbraces <= maxNumber_left_enbraces) { counter_left_enbraces++; if (counter_left_enbraces >= maxNumber_left_enbraces) { break whl_left_enbraces; } id_left_enbraces = (counter_left_enbraces - 2); splittedBy_left_enbraces_substr = jpathExpressionSubstrings_ByLeftEnbraces[id_left_enbraces]; if (splittedBy_left_enbraces_substr.length === 0) { continue whl_left_enbraces; } if ((counter_left_enbraces === 2) && (firstLeftBracePos !== 0)) { jpathItemsAdded = this.parseDotted( splittedBy_left_enbraces_substr, jpathArray ); continue whl_left_enbraces; } const jpathExpressionSubstrings_ByRightEnbraces = splittedBy_left_enbraces_substr.split("]"); let jpathExpressionArray_ByRightEnbracesLen = jpathExpressionSubstrings_ByRightEnbraces.length; if (jpathExpressionArray_ByRightEnbracesLen === 0) { continue whl_left_enbraces; } jpathKeyString = jpathExpressionSubstrings_ByRightEnbraces[0]; jpathKeyNumeric = +jpathKeyString; if (jpathExpressionArray_ByRightEnbracesLen === 1) { if (Number.isInteger(jpathKeyNumeric) === true) { jpathArray.push(jpathKeyNumeric); } else { jpathArray.push(jpathKeyString); } continue whl_left_enbraces; } else if (jpathExpressionArray_ByRightEnbracesLen === 2) { if (Number.isInteger(jpathKeyNumeric) === true) { jpathArray.push(jpathKeyNumeric); } else { jpathArray.push(jpathKeyString); } if (jpathExpressionSubstrings_ByRightEnbraces[1] === ".") { continue whl_left_enbraces; } } else if ((jpathExpressionArray_ByRightEnbracesLen === 3)) { console.error("JPath Expression square braces wrong."); continue whl_left_enbraces; } let substr_ByRightEnbraces = jpathExpressionSubstrings_ByRightEnbraces[1]; jpathItemsAdded = this.parseDotted( substr_ByRightEnbraces, jpathArray ); continue whl_left_enbraces; } return jpathArray; } // @method protected parseDotted( ... @args ): number // @inokedBy method: public parse ( jpathExpression: string ): (string|number)[] // splitted with symbol point, adds to jpathArray, numeric array elem id or textual field name. parseDotted( jpathExpressionDotted, jpathArray ) { let addedNumber = 0; let jpathExpressionSubstrings = jpathExpressionDotted.split("."); let splittedBy_dot_substr = ""; let jpathKeyString = ""; let jpathKeyNumeric = 0; let counter_dotted = 1; let jpathExpressionArray_dottedLen = jpathExpressionSubstrings.length; let maxNumber_dotted = (jpathExpressionArray_dottedLen + 2); let id_dotted = 0; whl_dotted: while (counter_dotted <= maxNumber_dotted) { counter_dotted++; if (counter_dotted >= maxNumber_dotted) { break whl_dotted; } id_dotted = (counter_dotted - 2); splittedBy_dot_substr = jpathExpressionSubstrings[id_dotted]; if (splittedBy_dot_substr.length === 0) { continue whl_dotted; } jpathKeyString = splittedBy_dot_substr; jpathKeyNumeric = +jpathKeyString; if (Number.isInteger(jpathKeyNumeric) === true) { jpathArray.push(jpathKeyNumeric); } else { jpathArray.push(jpathKeyString); } continue whl_dotted; } return addedNumber; } serialize( jpath, concatenator, start, finish ) { // @retVal let jpathExpression = ""; let jpathJoined = jpath.join(concatenator); let locJpath = new Array(); let startNotZerolen = ((start !== undefined) && (start !== null) && (start.length > 0)); let finishNotZerolen = ((finish !== undefined) && (finish !== null) && (finish.length > 0)); if ((startNotZerolen === false) && (finishNotZerolen === false)) { // @retVal jpathExpression = jpathJoined; } else if (startNotZerolen === true) { locJpath = [start, jpathJoined]; } if (finishNotZerolen === true) { locJpath.push(finish); } if (locJpath.length > 0) { // @retVal jpathExpression = locJpath.join(""); } return jpathExpression; } getByJPathExpression(obj, jpathExpression) { const jpath = this.parse(jpathExpression); return this.getByJPath(obj, jpath); } // faster than JPath.getByJPathExpression( jpathExpression: string, value: any ); // recommended when the lookup more than once with the same jpathExpression, // or when You already at once build the jpath array variables to perform lookups // like this: let jpath: (string|number)[] = [ "tokens", "startTokens", 0, "length" ]; // let jpath = JPath.parse( "subtree[1].opened" ); => [ "subtree", 1, "opened" ] // let obj = { "subtree": [{ "opened": false }, { "opened": true }] }; // let valueFound = JPath.getByJPath( jpath, obj ); // console.log( valueFound ); // prints out => true getByJPath(obj, jpath) { if ((obj === undefined) || (obj === null)) { return null; } if (!jpath || jpath.length === 0) { return obj; } let targetValue = obj; let jpathValueFound = {}; let jpathPropertyKey = ""; let jpathPropLevel = 0; let jpathLevelMax = Math.min( jpath.length, JPath.JPATH_EXPRESSION_MAX_SIZE ); for (jpathPropLevel = 0; jpathPropLevel < jpathLevelMax; jpathPropLevel++) { if (!targetValue) { break; } jpathPropertyKey = jpath[jpathPropLevel]; jpathValueFound = targetValue[jpathPropertyKey]; if (typeof jpathValueFound === "object") { if (Array.isArray(jpathValueFound) === true) { targetValue = [...jpathValueFound]; } else { targetValue = { ...jpathValueFound }; } } else { targetValue = jpathValueFound; } } return targetValue; } setByJPathExpression(obj, jpathExpression, value) { const jpath = this.parse(jpathExpression); this.setByJPath(obj, jpath, value); return this; } setByJPath(obj, jpath, value) { let locObj = obj; const jpathLen = jpath.length; let jpathLastIx = (jpathLen - 1); let datatypeJsonKey = ""; let id = 0; let nextId = 0; let key = ""; let lastKey = jpath[jpathLastIx]; // let prevKey: any = ""; let nextKey = ""; // @loop_counter let counter = 1; let maxCounter = (jpathLen + 2); whl_jpath: while (counter < maxCounter) { // @inc_loop_counter counter++; // @loop_counter if (counter >= maxCounter) { break whl_jpath; } id = (counter - 2); nextId = (id + 1); // prevKey = key; key = jpath[id]; if (nextId >= jpathLen) { nextKey = ""; } else { nextKey = jpath[nextId]; } if (id === jpathLastIx) { locObj[lastKey] = value; break whl_jpath; } if (!locObj[key]) { datatypeJsonKey = typeof (nextKey); if (datatypeJsonKey === "number") { locObj[key] = new Array(); } else { locObj[key] = new Object(); } } locObj = locObj[key]; continue whl_jpath; } return this; } setByJPathWalkFlatRebuild( obj, jpath, value, nameHolderId, nameId, branchName ) { const jpathLen = jpath.length; let jpathIx; let jpathLastIx = jpathLen - 1; let id = null; let holderId = jpath[0]; let foundNode = null; let newItem = null; id = jpath[0]; obj[nameId] = id; for (jpathIx = 1; jpathIx < jpathLen; jpathIx++) { id = jpath[jpathIx]; foundNode = false; let toGetById = []; if (Array.isArray(obj)) { toGetById = obj; } else if (obj[branchName]) { toGetById = obj[branchName]; } else if (!obj[branchName]) { obj[branchName] = new Array(); toGetById = obj[branchName]; } foundNode = toGetById.find((node) => { const matches = (node[nameId] === id); return matches; }); if (!foundNode) { if (jpathIx === jpathLastIx) { newItem = { ...value, [nameId]: id, [nameHolderId]: holderId }; } else { newItem = { [nameId]: id, [nameHolderId]: holderId }; } toGetById.push(newItem); obj[nameId] = holderId; const lastIx = (toGetById.length - 1); foundNode = toGetById[lastIx]; } obj = foundNode; holderId = id; } return this; } }