index.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. // Make sure Map exists for old Node.js versions
  2. var Map = global.Map != null ? global.Map : function() {};
  3. // These properties are special and can open client libraries to security
  4. // issues
  5. var ignoreProperties = ['__proto__', 'constructor', 'prototype'];
  6. /**
  7. * Returns the value of object `o` at the given `path`.
  8. *
  9. * ####Example:
  10. *
  11. * var obj = {
  12. * comments: [
  13. * { title: 'exciting!', _doc: { title: 'great!' }}
  14. * , { title: 'number dos' }
  15. * ]
  16. * }
  17. *
  18. * mpath.get('comments.0.title', o) // 'exciting!'
  19. * mpath.get('comments.0.title', o, '_doc') // 'great!'
  20. * mpath.get('comments.title', o) // ['exciting!', 'number dos']
  21. *
  22. * // summary
  23. * mpath.get(path, o)
  24. * mpath.get(path, o, special)
  25. * mpath.get(path, o, map)
  26. * mpath.get(path, o, special, map)
  27. *
  28. * @param {String} path
  29. * @param {Object} o
  30. * @param {String} [special] When this property name is present on any object in the path, walking will continue on the value of this property.
  31. * @param {Function} [map] Optional function which receives each individual found value. The value returned from `map` is used in the original values place.
  32. */
  33. exports.get = function (path, o, special, map) {
  34. var lookup;
  35. if ('function' == typeof special) {
  36. if (special.length < 2) {
  37. map = special;
  38. special = undefined;
  39. } else {
  40. lookup = special;
  41. special = undefined;
  42. }
  43. }
  44. map || (map = K);
  45. var parts = 'string' == typeof path
  46. ? path.split('.')
  47. : path
  48. if (!Array.isArray(parts)) {
  49. throw new TypeError('Invalid `path`. Must be either string or array');
  50. }
  51. var obj = o
  52. , part;
  53. for (var i = 0; i < parts.length; ++i) {
  54. part = parts[i];
  55. if (Array.isArray(obj) && !/^\d+$/.test(part)) {
  56. // reading a property from the array items
  57. var paths = parts.slice(i);
  58. return 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. for (var item, j = 0; j < obj.length && j < val.length; ++j) {
  217. item = obj[j];
  218. if (item) {
  219. if (lookup) {
  220. lookup(item, part, map(val[j]));
  221. } else {
  222. if (item[special]) item = item[special];
  223. item[part] = map(val[j]);
  224. }
  225. }
  226. }
  227. } else {
  228. for (var j = 0; j < obj.length; ++j) {
  229. item = obj[j];
  230. if (item) {
  231. if (lookup) {
  232. lookup(item, part, map(val));
  233. } else {
  234. if (item[special]) item = item[special];
  235. item[part] = map(val);
  236. }
  237. }
  238. }
  239. }
  240. } else {
  241. if (lookup) {
  242. lookup(obj, part, map(val));
  243. } else if (obj instanceof Map) {
  244. obj.set(part, map(val));
  245. } else {
  246. obj[part] = map(val);
  247. }
  248. }
  249. }
  250. /*!
  251. * Returns the value passed to it.
  252. */
  253. function K (v) {
  254. return v;
  255. }