index.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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 (typeof parts[i] !== 'string' && typeof parts[i] !== 'number') {
  58. throw new TypeError('Each segment of path to `get()` must be a string or number, got ' + typeof parts[i]);
  59. }
  60. if (Array.isArray(obj) && !/^\d+$/.test(part)) {
  61. // reading a property from the array items
  62. var paths = parts.slice(i);
  63. // Need to `concat()` to avoid `map()` calling a constructor of an array
  64. // subclass
  65. return [].concat(obj).map(function(item) {
  66. return item
  67. ? exports.get(paths, item, special || lookup, map)
  68. : map(undefined);
  69. });
  70. }
  71. if (lookup) {
  72. obj = lookup(obj, part);
  73. } else {
  74. var _from = special && obj[special] ? obj[special] : obj;
  75. obj = _from instanceof Map ?
  76. _from.get(part) :
  77. _from[part];
  78. }
  79. if (!obj) return map(obj);
  80. }
  81. return map(obj);
  82. };
  83. /**
  84. * Returns true if `in` returns true for every piece of the path
  85. *
  86. * @param {String} path
  87. * @param {Object} o
  88. */
  89. exports.has = function(path, o) {
  90. var parts = typeof path === 'string' ?
  91. stringToParts(path) :
  92. path;
  93. if (!Array.isArray(parts)) {
  94. throw new TypeError('Invalid `path`. Must be either string or array');
  95. }
  96. var len = parts.length;
  97. var cur = o;
  98. for (var i = 0; i < len; ++i) {
  99. if (typeof parts[i] !== 'string' && typeof parts[i] !== 'number') {
  100. throw new TypeError('Each segment of path to `has()` must be a string or number, got ' + typeof parts[i]);
  101. }
  102. if (cur == null || typeof cur !== 'object' || !(parts[i] in cur)) {
  103. return false;
  104. }
  105. cur = cur[parts[i]];
  106. }
  107. return true;
  108. };
  109. /**
  110. * Deletes the last piece of `path`
  111. *
  112. * @param {String} path
  113. * @param {Object} o
  114. */
  115. exports.unset = function(path, o) {
  116. var parts = typeof path === 'string' ?
  117. stringToParts(path) :
  118. path;
  119. if (!Array.isArray(parts)) {
  120. throw new TypeError('Invalid `path`. Must be either string or array');
  121. }
  122. var len = parts.length;
  123. var cur = o;
  124. for (var i = 0; i < len; ++i) {
  125. if (cur == null || typeof cur !== 'object' || !(parts[i] in cur)) {
  126. return false;
  127. }
  128. if (typeof parts[i] !== 'string' && typeof parts[i] !== 'number') {
  129. throw new TypeError('Each segment of path to `unset()` must be a string or number, got ' + typeof parts[i]);
  130. }
  131. // Disallow any updates to __proto__ or special properties.
  132. if (ignoreProperties.indexOf(parts[i]) !== -1) {
  133. return false;
  134. }
  135. if (i === len - 1) {
  136. delete cur[parts[i]];
  137. return true;
  138. }
  139. cur = cur instanceof Map ? cur.get(parts[i]) : cur[parts[i]];
  140. }
  141. return true;
  142. };
  143. /**
  144. * Sets the `val` at the given `path` of object `o`.
  145. *
  146. * @param {String} path
  147. * @param {Anything} val
  148. * @param {Object} o
  149. * @param {String} [special] When this property name is present on any object in the path, walking will continue on the value of this property.
  150. * @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.
  151. */
  152. exports.set = function(path, val, o, special, map, _copying) {
  153. var lookup;
  154. if ('function' == typeof special) {
  155. if (special.length < 2) {
  156. map = special;
  157. special = undefined;
  158. } else {
  159. lookup = special;
  160. special = undefined;
  161. }
  162. }
  163. map || (map = K);
  164. var parts = 'string' == typeof path
  165. ? stringToParts(path)
  166. : path;
  167. if (!Array.isArray(parts)) {
  168. throw new TypeError('Invalid `path`. Must be either string or array');
  169. }
  170. if (null == o) return;
  171. for (var i = 0; i < parts.length; ++i) {
  172. if (typeof parts[i] !== 'string' && typeof parts[i] !== 'number') {
  173. throw new TypeError('Each segment of path to `set()` must be a string or number, got ' + typeof parts[i]);
  174. }
  175. // Silently ignore any updates to `__proto__`, these are potentially
  176. // dangerous if using mpath with unsanitized data.
  177. if (ignoreProperties.indexOf(parts[i]) !== -1) {
  178. return;
  179. }
  180. }
  181. // the existance of $ in a path tells us if the user desires
  182. // the copying of an array instead of setting each value of
  183. // the array to the one by one to matching positions of the
  184. // current array. Unless the user explicitly opted out by passing
  185. // false, see Automattic/mongoose#6273
  186. var copy = _copying || (/\$/.test(path) && _copying !== false),
  187. obj = o,
  188. part;
  189. for (var i = 0, len = parts.length - 1; i < len; ++i) {
  190. part = parts[i];
  191. if ('$' == part) {
  192. if (i == len - 1) {
  193. break;
  194. } else {
  195. continue;
  196. }
  197. }
  198. if (Array.isArray(obj) && !/^\d+$/.test(part)) {
  199. var paths = parts.slice(i);
  200. if (!copy && Array.isArray(val)) {
  201. for (var j = 0; j < obj.length && j < val.length; ++j) {
  202. // assignment of single values of array
  203. exports.set(paths, val[j], obj[j], special || lookup, map, copy);
  204. }
  205. } else {
  206. for (var j = 0; j < obj.length; ++j) {
  207. // assignment of entire value
  208. exports.set(paths, val, obj[j], special || lookup, map, copy);
  209. }
  210. }
  211. return;
  212. }
  213. if (lookup) {
  214. obj = lookup(obj, part);
  215. } else {
  216. var _to = special && obj[special] ? obj[special] : obj;
  217. obj = _to instanceof Map ?
  218. _to.get(part) :
  219. _to[part];
  220. }
  221. if (!obj) return;
  222. }
  223. // process the last property of the path
  224. part = parts[len];
  225. // use the special property if exists
  226. if (special && obj[special]) {
  227. obj = obj[special];
  228. }
  229. // set the value on the last branch
  230. if (Array.isArray(obj) && !/^\d+$/.test(part)) {
  231. if (!copy && Array.isArray(val)) {
  232. _setArray(obj, val, part, lookup, special, map);
  233. } else {
  234. for (var j = 0; j < obj.length; ++j) {
  235. var item = obj[j];
  236. if (item) {
  237. if (lookup) {
  238. lookup(item, part, map(val));
  239. } else {
  240. if (item[special]) item = item[special];
  241. item[part] = map(val);
  242. }
  243. }
  244. }
  245. }
  246. } else {
  247. if (lookup) {
  248. lookup(obj, part, map(val));
  249. } else if (obj instanceof Map) {
  250. obj.set(part, map(val));
  251. } else {
  252. obj[part] = map(val);
  253. }
  254. }
  255. };
  256. /*!
  257. * Recursively set nested arrays
  258. */
  259. function _setArray(obj, val, part, lookup, special, map) {
  260. for (var item, j = 0; j < obj.length && j < val.length; ++j) {
  261. item = obj[j];
  262. if (Array.isArray(item) && Array.isArray(val[j])) {
  263. _setArray(item, val[j], part, lookup, special, map);
  264. } else if (item) {
  265. if (lookup) {
  266. lookup(item, part, map(val[j]));
  267. } else {
  268. if (item[special]) item = item[special];
  269. item[part] = map(val[j]);
  270. }
  271. }
  272. }
  273. }
  274. /*!
  275. * Returns the value passed to it.
  276. */
  277. function K(v) {
  278. return v;
  279. }