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.
		
		
		
		
		
			
		
			
				
					326 lines
				
				8.4 KiB
			
		
		
			
		
	
	
					326 lines
				
				8.4 KiB
			| 
											3 years ago
										 | /* eslint strict:off */ | ||
|  | /* eslint no-var: off */ | ||
|  | /* eslint no-redeclare: off */ | ||
|  | 
 | ||
|  | var stringToParts = require('./stringToParts'); | ||
|  | 
 | ||
|  | // These properties are special and can open client libraries to security
 | ||
|  | // issues
 | ||
|  | var ignoreProperties = ['__proto__', 'constructor', 'prototype']; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Returns the value of object `o` at the given `path`. | ||
|  |  * | ||
|  |  * ####Example: | ||
|  |  * | ||
|  |  *     var obj = { | ||
|  |  *         comments: [ | ||
|  |  *             { title: 'exciting!', _doc: { title: 'great!' }} | ||
|  |  *           , { title: 'number dos' } | ||
|  |  *         ] | ||
|  |  *     } | ||
|  |  * | ||
|  |  *     mpath.get('comments.0.title', o)         // 'exciting!'
 | ||
|  |  *     mpath.get('comments.0.title', o, '_doc') // 'great!'
 | ||
|  |  *     mpath.get('comments.title', o)           // ['exciting!', 'number dos']
 | ||
|  |  * | ||
|  |  *     // summary
 | ||
|  |  *     mpath.get(path, o) | ||
|  |  *     mpath.get(path, o, special) | ||
|  |  *     mpath.get(path, o, map) | ||
|  |  *     mpath.get(path, o, special, map) | ||
|  |  * | ||
|  |  * @param {String} path | ||
|  |  * @param {Object} o | ||
|  |  * @param {String} [special] When this property name is present on any object in the path, walking will continue on the value of this property. | ||
|  |  * @param {Function} [map] Optional function which receives each individual found value. The value returned from `map` is used in the original values place. | ||
|  |  */ | ||
|  | 
 | ||
|  | exports.get = function(path, o, special, map) { | ||
|  |   var lookup; | ||
|  | 
 | ||
|  |   if ('function' == typeof special) { | ||
|  |     if (special.length < 2) { | ||
|  |       map = special; | ||
|  |       special = undefined; | ||
|  |     } else { | ||
|  |       lookup = special; | ||
|  |       special = undefined; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   map || (map = K); | ||
|  | 
 | ||
|  |   var parts = 'string' == typeof path | ||
|  |     ? stringToParts(path) | ||
|  |     : path; | ||
|  | 
 | ||
|  |   if (!Array.isArray(parts)) { | ||
|  |     throw new TypeError('Invalid `path`. Must be either string or array'); | ||
|  |   } | ||
|  | 
 | ||
|  |   var obj = o, | ||
|  |       part; | ||
|  | 
 | ||
|  |   for (var i = 0; i < parts.length; ++i) { | ||
|  |     part = parts[i]; | ||
|  |     if (typeof parts[i] !== 'string' && typeof parts[i] !== 'number') { | ||
|  |       throw new TypeError('Each segment of path to `get()` must be a string or number, got ' + typeof parts[i]); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (Array.isArray(obj) && !/^\d+$/.test(part)) { | ||
|  |       // reading a property from the array items
 | ||
|  |       var paths = parts.slice(i); | ||
|  | 
 | ||
|  |       // Need to `concat()` to avoid `map()` calling a constructor of an array
 | ||
|  |       // subclass
 | ||
|  |       return [].concat(obj).map(function(item) { | ||
|  |         return item | ||
|  |           ? exports.get(paths, item, special || lookup, map) | ||
|  |           : map(undefined); | ||
|  |       }); | ||
|  |     } | ||
|  | 
 | ||
|  |     if (lookup) { | ||
|  |       obj = lookup(obj, part); | ||
|  |     } else { | ||
|  |       var _from = special && obj[special] ? obj[special] : obj; | ||
|  |       obj = _from instanceof Map ? | ||
|  |         _from.get(part) : | ||
|  |         _from[part]; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!obj) return map(obj); | ||
|  |   } | ||
|  | 
 | ||
|  |   return map(obj); | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Returns true if `in` returns true for every piece of the path | ||
|  |  * | ||
|  |  * @param {String} path | ||
|  |  * @param {Object} o | ||
|  |  */ | ||
|  | 
 | ||
|  | exports.has = function(path, o) { | ||
|  |   var parts = typeof path === 'string' ? | ||
|  |     stringToParts(path) : | ||
|  |     path; | ||
|  | 
 | ||
|  |   if (!Array.isArray(parts)) { | ||
|  |     throw new TypeError('Invalid `path`. Must be either string or array'); | ||
|  |   } | ||
|  | 
 | ||
|  |   var len = parts.length; | ||
|  |   var cur = o; | ||
|  |   for (var i = 0; i < len; ++i) { | ||
|  |     if (typeof parts[i] !== 'string' && typeof parts[i] !== 'number') { | ||
|  |       throw new TypeError('Each segment of path to `has()` must be a string or number, got ' + typeof parts[i]); | ||
|  |     } | ||
|  |     if (cur == null || typeof cur !== 'object' || !(parts[i] in cur)) { | ||
|  |       return false; | ||
|  |     } | ||
|  |     cur = cur[parts[i]]; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Deletes the last piece of `path` | ||
|  |  * | ||
|  |  * @param {String} path | ||
|  |  * @param {Object} o | ||
|  |  */ | ||
|  | 
 | ||
|  | exports.unset = function(path, o) { | ||
|  |   var parts = typeof path === 'string' ? | ||
|  |     stringToParts(path) : | ||
|  |     path; | ||
|  | 
 | ||
|  |   if (!Array.isArray(parts)) { | ||
|  |     throw new TypeError('Invalid `path`. Must be either string or array'); | ||
|  |   } | ||
|  | 
 | ||
|  |   var len = parts.length; | ||
|  |   var cur = o; | ||
|  |   for (var i = 0; i < len; ++i) { | ||
|  |     if (cur == null || typeof cur !== 'object' || !(parts[i] in cur)) { | ||
|  |       return false; | ||
|  |     } | ||
|  |     if (typeof parts[i] !== 'string' && typeof parts[i] !== 'number') { | ||
|  |       throw new TypeError('Each segment of path to `unset()` must be a string or number, got ' + typeof parts[i]); | ||
|  |     } | ||
|  |     // Disallow any updates to __proto__ or special properties.
 | ||
|  |     if (ignoreProperties.indexOf(parts[i]) !== -1) { | ||
|  |       return false; | ||
|  |     } | ||
|  |     if (i === len - 1) { | ||
|  |       delete cur[parts[i]]; | ||
|  |       return true; | ||
|  |     } | ||
|  |     cur = cur instanceof Map ? cur.get(parts[i]) : cur[parts[i]]; | ||
|  |   } | ||
|  | 
 | ||
|  |   return true; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Sets the `val` at the given `path` of object `o`. | ||
|  |  * | ||
|  |  * @param {String} path | ||
|  |  * @param {Anything} val | ||
|  |  * @param {Object} o | ||
|  |  * @param {String} [special] When this property name is present on any object in the path, walking will continue on the value of this property. | ||
|  |  * @param {Function} [map] Optional function which is passed each individual value before setting it. The value returned from `map` is used in the original values place. | ||
|  |  */ | ||
|  | 
 | ||
|  | exports.set = function(path, val, o, special, map, _copying) { | ||
|  |   var lookup; | ||
|  | 
 | ||
|  |   if ('function' == typeof special) { | ||
|  |     if (special.length < 2) { | ||
|  |       map = special; | ||
|  |       special = undefined; | ||
|  |     } else { | ||
|  |       lookup = special; | ||
|  |       special = undefined; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   map || (map = K); | ||
|  | 
 | ||
|  |   var parts = 'string' == typeof path | ||
|  |     ? stringToParts(path) | ||
|  |     : path; | ||
|  | 
 | ||
|  |   if (!Array.isArray(parts)) { | ||
|  |     throw new TypeError('Invalid `path`. Must be either string or array'); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (null == o) return; | ||
|  | 
 | ||
|  |   for (var i = 0; i < parts.length; ++i) { | ||
|  |     if (typeof parts[i] !== 'string' && typeof parts[i] !== 'number') { | ||
|  |       throw new TypeError('Each segment of path to `set()` must be a string or number, got ' + typeof parts[i]); | ||
|  |     } | ||
|  |     // Silently ignore any updates to `__proto__`, these are potentially
 | ||
|  |     // dangerous if using mpath with unsanitized data.
 | ||
|  |     if (ignoreProperties.indexOf(parts[i]) !== -1) { | ||
|  |       return; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   // the existance of $ in a path tells us if the user desires
 | ||
|  |   // the copying of an array instead of setting each value of
 | ||
|  |   // the array to the one by one to matching positions of the
 | ||
|  |   // current array. Unless the user explicitly opted out by passing
 | ||
|  |   // false, see Automattic/mongoose#6273
 | ||
|  |   var copy = _copying || (/\$/.test(path) && _copying !== false), | ||
|  |       obj = o, | ||
|  |       part; | ||
|  | 
 | ||
|  |   for (var i = 0, len = parts.length - 1; i < len; ++i) { | ||
|  |     part = parts[i]; | ||
|  | 
 | ||
|  |     if ('$' == part) { | ||
|  |       if (i == len - 1) { | ||
|  |         break; | ||
|  |       } else { | ||
|  |         continue; | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     if (Array.isArray(obj) && !/^\d+$/.test(part)) { | ||
|  |       var paths = parts.slice(i); | ||
|  |       if (!copy && Array.isArray(val)) { | ||
|  |         for (var j = 0; j < obj.length && j < val.length; ++j) { | ||
|  |           // assignment of single values of array
 | ||
|  |           exports.set(paths, val[j], obj[j], special || lookup, map, copy); | ||
|  |         } | ||
|  |       } else { | ||
|  |         for (var j = 0; j < obj.length; ++j) { | ||
|  |           // assignment of entire value
 | ||
|  |           exports.set(paths, val, obj[j], special || lookup, map, copy); | ||
|  |         } | ||
|  |       } | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (lookup) { | ||
|  |       obj = lookup(obj, part); | ||
|  |     } else { | ||
|  |       var _to = special && obj[special] ? obj[special] : obj; | ||
|  |       obj = _to instanceof Map ? | ||
|  |         _to.get(part) : | ||
|  |         _to[part]; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!obj) return; | ||
|  |   } | ||
|  | 
 | ||
|  |   // process the last property of the path
 | ||
|  | 
 | ||
|  |   part = parts[len]; | ||
|  | 
 | ||
|  |   // use the special property if exists
 | ||
|  |   if (special && obj[special]) { | ||
|  |     obj = obj[special]; | ||
|  |   } | ||
|  | 
 | ||
|  |   // set the value on the last branch
 | ||
|  |   if (Array.isArray(obj) && !/^\d+$/.test(part)) { | ||
|  |     if (!copy && Array.isArray(val)) { | ||
|  |       _setArray(obj, val, part, lookup, special, map); | ||
|  |     } else { | ||
|  |       for (var j = 0; j < obj.length; ++j) { | ||
|  |         var item = obj[j]; | ||
|  |         if (item) { | ||
|  |           if (lookup) { | ||
|  |             lookup(item, part, map(val)); | ||
|  |           } else { | ||
|  |             if (item[special]) item = item[special]; | ||
|  |             item[part] = map(val); | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  |   } else { | ||
|  |     if (lookup) { | ||
|  |       lookup(obj, part, map(val)); | ||
|  |     } else if (obj instanceof Map) { | ||
|  |       obj.set(part, map(val)); | ||
|  |     } else { | ||
|  |       obj[part] = map(val); | ||
|  |     } | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | /*! | ||
|  |  * Recursively set nested arrays | ||
|  |  */ | ||
|  | 
 | ||
|  | function _setArray(obj, val, part, lookup, special, map) { | ||
|  |   for (var item, j = 0; j < obj.length && j < val.length; ++j) { | ||
|  |     item = obj[j]; | ||
|  |     if (Array.isArray(item) && Array.isArray(val[j])) { | ||
|  |       _setArray(item, val[j], part, lookup, special, map); | ||
|  |     } else if (item) { | ||
|  |       if (lookup) { | ||
|  |         lookup(item, part, map(val[j])); | ||
|  |       } else { | ||
|  |         if (item[special]) item = item[special]; | ||
|  |         item[part] = map(val[j]); | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | /*! | ||
|  |  * Returns the value passed to it. | ||
|  |  */ | ||
|  | 
 | ||
|  | function K(v) { | ||
|  |   return v; | ||
|  | } |