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.
302 lines
7.6 KiB
302 lines
7.6 KiB
2 years ago
|
'use strict';
|
||
|
|
||
|
/* !
|
||
|
* 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,
|
||
|
};
|