| ; | |
| /* ! | |
| * Chai - pathval utility | |
| * Copyright(c) 2012-2014 Jake Luer <jake@alogicalparadox.com> | |
| * @see https://github.com/logicalparadox/filtr | |
| * MIT Licensed | |
| */ | |
| /** | |
| * ### .hasProperty(object, name) | |
| * | |
| * This allows checking whether an object has own | |
| * or inherited from prototype chain named property. | |
| * | |
| * Basically does the same thing as the `in` | |
| * operator but works properly with null/undefined values | |
| * and other primitives. | |
| * | |
| * var obj = { | |
| * arr: ['a', 'b', 'c'] | |
| * , str: 'Hello' | |
| * } | |
| * | |
| * The following would be the results. | |
| * | |
| * hasProperty(obj, 'str'); // true | |
| * hasProperty(obj, 'constructor'); // true | |
| * hasProperty(obj, 'bar'); // false | |
| * | |
| * hasProperty(obj.str, 'length'); // true | |
| * hasProperty(obj.str, 1); // true | |
| * hasProperty(obj.str, 5); // false | |
| * | |
| * hasProperty(obj.arr, 'length'); // true | |
| * hasProperty(obj.arr, 2); // true | |
| * hasProperty(obj.arr, 3); // false | |
| * | |
| * @param {Object} object | |
| * @param {String|Symbol} name | |
| * @returns {Boolean} whether it exists | |
| * @namespace Utils | |
| * @name hasProperty | |
| * @api public | |
| */ | |
| function hasProperty(obj, name) { | |
| if (typeof obj === 'undefined' || obj === null) { | |
| return false; | |
| } | |
| // The `in` operator does not work with primitives. | |
| return name in Object(obj); | |
| } | |
| /* ! | |
| * ## parsePath(path) | |
| * | |
| * Helper function used to parse string object | |
| * paths. Use in conjunction with `internalGetPathValue`. | |
| * | |
| * var parsed = parsePath('myobject.property.subprop'); | |
| * | |
| * ### Paths: | |
| * | |
| * * Can be infinitely deep and nested. | |
| * * Arrays are also valid using the formal `myobject.document[3].property`. | |
| * * Literal dots and brackets (not delimiter) must be backslash-escaped. | |
| * | |
| * @param {String} path | |
| * @returns {Object} parsed | |
| * @api private | |
| */ | |
| function parsePath(path) { | |
| var str = path.replace(/([^\\])\[/g, '$1.['); | |
| var parts = str.match(/(\\\.|[^.]+?)+/g); | |
| return parts.map(function mapMatches(value) { | |
| if ( | |
| value === 'constructor' || | |
| value === '__proto__' || | |
| value === 'prototype' | |
| ) { | |
| return {}; | |
| } | |
| var regexp = /^\[(\d+)\]$/; | |
| var mArr = regexp.exec(value); | |
| var parsed = null; | |
| if (mArr) { | |
| parsed = { i: parseFloat(mArr[1]) }; | |
| } else { | |
| parsed = { p: value.replace(/\\([.[\]])/g, '$1') }; | |
| } | |
| return parsed; | |
| }); | |
| } | |
| /* ! | |
| * ## internalGetPathValue(obj, parsed[, pathDepth]) | |
| * | |
| * Helper companion function for `.parsePath` that returns | |
| * the value located at the parsed address. | |
| * | |
| * var value = getPathValue(obj, parsed); | |
| * | |
| * @param {Object} object to search against | |
| * @param {Object} parsed definition from `parsePath`. | |
| * @param {Number} depth (nesting level) of the property we want to retrieve | |
| * @returns {Object|Undefined} value | |
| * @api private | |
| */ | |
| function internalGetPathValue(obj, parsed, pathDepth) { | |
| var temporaryValue = obj; | |
| var res = null; | |
| pathDepth = typeof pathDepth === 'undefined' ? parsed.length : pathDepth; | |
| for (var i = 0; i < pathDepth; i++) { | |
| var part = parsed[i]; | |
| if (temporaryValue) { | |
| if (typeof part.p === 'undefined') { | |
| temporaryValue = temporaryValue[part.i]; | |
| } else { | |
| temporaryValue = temporaryValue[part.p]; | |
| } | |
| if (i === pathDepth - 1) { | |
| res = temporaryValue; | |
| } | |
| } | |
| } | |
| return res; | |
| } | |
| /* ! | |
| * ## internalSetPathValue(obj, value, parsed) | |
| * | |
| * Companion function for `parsePath` that sets | |
| * the value located at a parsed address. | |
| * | |
| * internalSetPathValue(obj, 'value', parsed); | |
| * | |
| * @param {Object} object to search and define on | |
| * @param {*} value to use upon set | |
| * @param {Object} parsed definition from `parsePath` | |
| * @api private | |
| */ | |
| function internalSetPathValue(obj, val, parsed) { | |
| var tempObj = obj; | |
| var pathDepth = parsed.length; | |
| var part = null; | |
| // Here we iterate through every part of the path | |
| for (var i = 0; i < pathDepth; i++) { | |
| var propName = null; | |
| var propVal = null; | |
| part = parsed[i]; | |
| // If it's the last part of the path, we set the 'propName' value with the property name | |
| if (i === pathDepth - 1) { | |
| propName = typeof part.p === 'undefined' ? part.i : part.p; | |
| // Now we set the property with the name held by 'propName' on object with the desired val | |
| tempObj[propName] = val; | |
| } else if (typeof part.p !== 'undefined' && tempObj[part.p]) { | |
| tempObj = tempObj[part.p]; | |
| } else if (typeof part.i !== 'undefined' && tempObj[part.i]) { | |
| tempObj = tempObj[part.i]; | |
| } else { | |
| // If the obj doesn't have the property we create one with that name to define it | |
| var next = parsed[i + 1]; | |
| // Here we set the name of the property which will be defined | |
| propName = typeof part.p === 'undefined' ? part.i : part.p; | |
| // Here we decide if this property will be an array or a new object | |
| propVal = typeof next.p === 'undefined' ? [] : {}; | |
| tempObj[propName] = propVal; | |
| tempObj = tempObj[propName]; | |
| } | |
| } | |
| } | |
| /** | |
| * ### .getPathInfo(object, path) | |
| * | |
| * This allows the retrieval of property info in an | |
| * object given a string path. | |
| * | |
| * The path info consists of an object with the | |
| * following properties: | |
| * | |
| * * parent - The parent object of the property referenced by `path` | |
| * * name - The name of the final property, a number if it was an array indexer | |
| * * value - The value of the property, if it exists, otherwise `undefined` | |
| * * exists - Whether the property exists or not | |
| * | |
| * @param {Object} object | |
| * @param {String} path | |
| * @returns {Object} info | |
| * @namespace Utils | |
| * @name getPathInfo | |
| * @api public | |
| */ | |
| function getPathInfo(obj, path) { | |
| var parsed = parsePath(path); | |
| var last = parsed[parsed.length - 1]; | |
| var info = { | |
| parent: | |
| parsed.length > 1 ? | |
| internalGetPathValue(obj, parsed, parsed.length - 1) : | |
| obj, | |
| name: last.p || last.i, | |
| value: internalGetPathValue(obj, parsed), | |
| }; | |
| info.exists = hasProperty(info.parent, info.name); | |
| return info; | |
| } | |
| /** | |
| * ### .getPathValue(object, path) | |
| * | |
| * This allows the retrieval of values in an | |
| * object given a string path. | |
| * | |
| * var obj = { | |
| * prop1: { | |
| * arr: ['a', 'b', 'c'] | |
| * , str: 'Hello' | |
| * } | |
| * , prop2: { | |
| * arr: [ { nested: 'Universe' } ] | |
| * , str: 'Hello again!' | |
| * } | |
| * } | |
| * | |
| * The following would be the results. | |
| * | |
| * getPathValue(obj, 'prop1.str'); // Hello | |
| * getPathValue(obj, 'prop1.att[2]'); // b | |
| * getPathValue(obj, 'prop2.arr[0].nested'); // Universe | |
| * | |
| * @param {Object} object | |
| * @param {String} path | |
| * @returns {Object} value or `undefined` | |
| * @namespace Utils | |
| * @name getPathValue | |
| * @api public | |
| */ | |
| function getPathValue(obj, path) { | |
| var info = getPathInfo(obj, path); | |
| return info.value; | |
| } | |
| /** | |
| * ### .setPathValue(object, path, value) | |
| * | |
| * Define the value in an object at a given string path. | |
| * | |
| * ```js | |
| * var obj = { | |
| * prop1: { | |
| * arr: ['a', 'b', 'c'] | |
| * , str: 'Hello' | |
| * } | |
| * , prop2: { | |
| * arr: [ { nested: 'Universe' } ] | |
| * , str: 'Hello again!' | |
| * } | |
| * }; | |
| * ``` | |
| * | |
| * The following would be acceptable. | |
| * | |
| * ```js | |
| * var properties = require('tea-properties'); | |
| * properties.set(obj, 'prop1.str', 'Hello Universe!'); | |
| * properties.set(obj, 'prop1.arr[2]', 'B'); | |
| * properties.set(obj, 'prop2.arr[0].nested.value', { hello: 'universe' }); | |
| * ``` | |
| * | |
| * @param {Object} object | |
| * @param {String} path | |
| * @param {Mixed} value | |
| * @api private | |
| */ | |
| function setPathValue(obj, path, val) { | |
| var parsed = parsePath(path); | |
| internalSetPathValue(obj, val, parsed); | |
| return obj; | |
| } | |
| module.exports = { | |
| hasProperty: hasProperty, | |
| getPathInfo: getPathInfo, | |
| getPathValue: getPathValue, | |
| setPathValue: setPathValue, | |
| }; | |