You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
292 lines
7.5 KiB
292 lines
7.5 KiB
/* ! |
|
* 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 |
|
*/ |
|
|
|
export 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) { |
|
const str = path.replace(/([^\\])\[/g, '$1.['); |
|
const parts = str.match(/(\\\.|[^.]+?)+/g); |
|
return parts.map((value) => { |
|
if ( |
|
value === 'constructor' || |
|
value === '__proto__' || |
|
value === 'prototype' |
|
) { |
|
return {}; |
|
} |
|
const regexp = /^\[(\d+)\]$/; |
|
const mArr = regexp.exec(value); |
|
let 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) { |
|
let temporaryValue = obj; |
|
let res = null; |
|
pathDepth = typeof pathDepth === 'undefined' ? parsed.length : pathDepth; |
|
|
|
for (let i = 0; i < pathDepth; i++) { |
|
const 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) { |
|
let tempObj = obj; |
|
const pathDepth = parsed.length; |
|
let part = null; |
|
// Here we iterate through every part of the path |
|
for (let i = 0; i < pathDepth; i++) { |
|
let propName = null; |
|
let 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 |
|
const 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 |
|
*/ |
|
|
|
export function getPathInfo(obj, path) { |
|
const parsed = parsePath(path); |
|
const last = parsed[parsed.length - 1]; |
|
const 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 |
|
*/ |
|
|
|
export function getPathValue(obj, path) { |
|
const 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 |
|
*/ |
|
|
|
export function setPathValue(obj, path, val) { |
|
const parsed = parsePath(path); |
|
internalSetPathValue(obj, val, parsed); |
|
return obj; |
|
}
|
|
|