; | |
/* ! | |
* 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, | |
}; | |