index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. // These properties are special and can open client libraries to security
  2. // issues
  3. var ignoreProperties = ['__proto__', 'constructor', 'prototype'];
  4. /**
  5. * Returns the value of object `o` at the given `path`.
  6. *
  7. * ####Example:
  8. *
  9. * var obj = {
  10. * comments: [
  11. * { title: 'exciting!', _doc: { title: 'great!' }}
  12. * , { title: 'number dos' }
  13. * ]
  14. * }
  15. *
  16. * mpath.get('comments.0.title', o) // 'exciting!'
  17. * mpath.get('comments.0.title', o, '_doc') // 'great!'
  18. * mpath.get('comments.title', o) // ['exciting!', 'number dos']
  19. *
  20. * // summary
  21. * mpath.get(path, o)
  22. * mpath.get(path, o, special)
  23. * mpath.get(path, o, map)
  24. * mpath.get(path, o, special, map)
  25. *
  26. * @param {String} path
  27. * @param {Object} o
  28. * @param {String} [special] When this property name is present on any object in the path, walking will continue on the value of this property.
  29. * @param {Function} [map] Optional function which receives each individual found value. The value returned from `map` is used in the original values place.
  30. */
  31. exports.get = function (path, o, special, map) {
  32. var lookup;
  33. if ('function' == typeof special) {
  34. if (special.length < 2) {
  35. map = special;
  36. special = undefined;
  37. } else {
  38. lookup = special;
  39. special = undefined;
  40. }
  41. }
  42. map || (map = K);
  43. var parts = 'string' == typeof path
  44. ? path.split('.')
  45. : path
  46. if (!Array.isArray(parts)) {
  47. throw new TypeError('Invalid `path`. Must be either string or array');
  48. }
  49. var obj = o
  50. , part;
  51. for (var i = 0; i < parts.length; ++i) {
  52. part = parts[i];
  53. if (Array.isArray(obj) && !/^\d+$/.test(part)) {
  54. // reading a property from the array items
  55. var paths = parts.slice(i);
  56. // Need to `concat()` to avoid `map()` calling a constructor of an array
  57. // subclass
  58. return [].concat(obj).map(function (item) {
  59. return item
  60. ? exports.get(paths, item, special || lookup, map)
  61. : map(undefined);
  62. });
  63. }
  64. if (lookup) {
  65. obj = lookup(obj, part);
  66. } else {
  67. var _from = special && obj[special] ? obj[special] : obj;
  68. obj = _from instanceof Map ?
  69. _from.get(part) :
  70. _from[part];
  71. }
  72. if (!obj) return map(obj);
  73. }
  74. return map(obj);
  75. };
  76. /**
  77. * Returns true if `in` returns true for every piece of the path
  78. *
  79. * @param {String} path
  80. * @param {Object} o
  81. */
  82. exports.has = function (path, o) {
  83. var parts = typeof path === 'string' ?
  84. path.split('.') :
  85. path;
  86. if (!Array.isArray(parts)) {
  87. throw new TypeError('Invalid `path`. Must be either string or array');
  88. }
  89. var len = parts.length;
  90. var cur = o;
  91. for (var i = 0; i < len; ++i) {
  92. if (cur == null || typeof cur !== 'object' || !(parts[i] in cur)) {
  93. return false;
  94. }
  95. cur = cur[parts[i]];
  96. }
  97. return true;
  98. };
  99. /**
  100. * Deletes the last piece of `path`
  101. *
  102. * @param {String} path
  103. * @param {Object} o
  104. */
  105. exports.unset = function (path, o) {
  106. var parts = typeof path === 'string' ?
  107. path.split('.') :
  108. path;
  109. if (!Array.isArray(parts)) {
  110. throw new TypeError('Invalid `path`. Must be either string or array');
  111. }
  112. var len = parts.length;
  113. var cur = o;
  114. for (var i = 0; i < len; ++i) {
  115. if (cur == null || typeof cur !== 'object' || !(parts[i] in cur)) {
  116. return false;
  117. }
  118. // Disallow any updates to __proto__ or special properties.
  119. if (ignoreProperties.indexOf(parts[i]) !== -1) {
  120. return false;
  121. }
  122. if (i === len - 1) {
  123. delete cur[parts[i]];
  124. return true;
  125. }
  126. cur = cur instanceof Map ? cur.get(parts[i]) : cur[parts[i]];
  127. }
  128. return true;
  129. };
  130. /**
  131. * Sets the `val` at the given `path` of object `o`.
  132. *
  133. * @param {String} path
  134. * @param {Anything} val
  135. * @param {Object} o
  136. * @param {String} [special] When this property name is present on any object in the path, walking will continue on the value of this property.
  137. * @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.
  138. */
  139. exports.set = function (path, val, o, special, map, _copying) {
  140. var lookup;
  141. if ('function' == typeof special) {
  142. if (special.length < 2) {
  143. map = special;
  144. special = undefined;
  145. } else {
  146. lookup = special;
  147. special = undefined;
  148. }
  149. }
  150. map || (map = K);
  151. var parts = 'string' == typeof path
  152. ? path.split('.')
  153. : path
  154. if (!Array.isArray(parts)) {
  155. throw new TypeError('Invalid `path`. Must be either string or array');
  156. }
  157. if (null == o) return;
  158. for (var i = 0; i < parts.length; ++i) {
  159. // Silently ignore any updates to `__proto__`, these are potentially
  160. // dangerous if using mpath with unsanitized data.
  161. if (ignoreProperties.indexOf(parts[i]) !== -1) {
  162. return;
  163. }
  164. }
  165. // the existance of $ in a path tells us if the user desires
  166. // the copying of an array instead of setting each value of
  167. // the array to the one by one to matching positions of the
  168. // current array. Unless the user explicitly opted out by passing
  169. // false, see Automattic/mongoose#6273
  170. var copy = _copying || (/\$/.test(path) && _copying !== false)
  171. , obj = o
  172. , part
  173. for (var i = 0, len = parts.length - 1; i < len; ++i) {
  174. part = parts[i];
  175. if ('$' == part) {
  176. if (i == len - 1) {
  177. break;
  178. } else {
  179. continue;
  180. }
  181. }
  182. if (Array.isArray(obj) && !/^\d+$/.test(part)) {
  183. var paths = parts.slice(i);
  184. if (!copy && Array.isArray(val)) {
  185. for (var j = 0; j < obj.length && j < val.length; ++j) {
  186. // assignment of single values of array
  187. exports.set(paths, val[j], obj[j], special || lookup, map, copy);
  188. }
  189. } else {
  190. for (var j = 0; j < obj.length; ++j) {
  191. // assignment of entire value
  192. exports.set(paths, val, obj[j], special || lookup, map, copy);
  193. }
  194. }
  195. return;
  196. }
  197. if (lookup) {
  198. obj = lookup(obj, part);
  199. } else {
  200. var _to = special && obj[special] ? obj[special] : obj;
  201. obj = _to instanceof Map ?
  202. _to.get(part) :
  203. _to[part];
  204. }
  205. if (!obj) return;
  206. }
  207. // process the last property of the path
  208. part = parts[len];
  209. // use the special property if exists
  210. if (special && obj[special]) {
  211. obj = obj[special];
  212. }
  213. // set the value on the last branch
  214. if (Array.isArray(obj) && !/^\d+$/.test(part)) {
  215. if (!copy && Array.isArray(val)) {
  216. _setArray(obj, val, part, lookup, special, map);
  217. } else {
  218. for (var j = 0; j < obj.length; ++j) {
  219. item = obj[j];
  220. if (item) {
  221. if (lookup) {
  222. lookup(item, part, map(val));
  223. } else {
  224. if (item[special]) item = item[special];
  225. item[part] = map(val);
  226. }
  227. }
  228. }
  229. }
  230. } else {
  231. if (lookup) {
  232. lookup(obj, part, map(val));
  233. } else if (obj instanceof Map) {
  234. obj.set(part, map(val));
  235. } else {
  236. obj[part] = map(val);
  237. }
  238. }
  239. }
  240. /*!
  241. * Recursively set nested arrays
  242. */
  243. function _setArray(obj, val, part, lookup, special, map) {
  244. for (var item, j = 0; j < obj.length && j < val.length; ++j) {
  245. item = obj[j];
  246. if (Array.isArray(item) && Array.isArray(val[j])) {
  247. _setArray(item, val[j], part, lookup, special, map);
  248. } else if (item) {
  249. if (lookup) {
  250. lookup(item, part, map(val[j]));
  251. } else {
  252. if (item[special]) item = item[special];
  253. item[part] = map(val[j]);
  254. }
  255. }
  256. }
  257. }
  258. /*!
  259. * Returns the value passed to it.
  260. */
  261. function K (v) {
  262. return v;
  263. }