index.js 7.7 KB

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