index.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. 'use strict';
  2. const isObj = require('is-obj');
  3. const disallowedKeys = [
  4. '__proto__',
  5. 'prototype',
  6. 'constructor'
  7. ];
  8. const isValidPath = pathSegments => !pathSegments.some(segment => disallowedKeys.includes(segment));
  9. function getPathSegments(path) {
  10. const pathArr = path.split('.');
  11. const parts = [];
  12. for (let i = 0; i < pathArr.length; i++) {
  13. let p = pathArr[i];
  14. while (p[p.length - 1] === '\\' && pathArr[i + 1] !== undefined) {
  15. p = p.slice(0, -1) + '.';
  16. p += pathArr[++i];
  17. }
  18. parts.push(p);
  19. }
  20. if (!isValidPath(parts)) {
  21. return [];
  22. }
  23. return parts;
  24. }
  25. module.exports = {
  26. get(obj, path, value) {
  27. if (!isObj(obj) || typeof path !== 'string') {
  28. return value === undefined ? obj : value;
  29. }
  30. const pathArr = getPathSegments(path);
  31. if (pathArr.length === 0) {
  32. return;
  33. }
  34. for (let i = 0; i < pathArr.length; i++) {
  35. if (!Object.prototype.propertyIsEnumerable.call(obj, pathArr[i])) {
  36. return value;
  37. }
  38. obj = obj[pathArr[i]];
  39. if (obj === undefined || obj === null) {
  40. // `obj` is either `undefined` or `null` so we want to stop the loop, and
  41. // if this is not the last bit of the path, and
  42. // if it did't return `undefined`
  43. // it would return `null` if `obj` is `null`
  44. // but we want `get({foo: null}, 'foo.bar')` to equal `undefined`, or the supplied value, not `null`
  45. if (i !== pathArr.length - 1) {
  46. return value;
  47. }
  48. break;
  49. }
  50. }
  51. return obj;
  52. },
  53. set(obj, path, value) {
  54. if (!isObj(obj) || typeof path !== 'string') {
  55. return obj;
  56. }
  57. const root = obj;
  58. const pathArr = getPathSegments(path);
  59. if (pathArr.length === 0) {
  60. return;
  61. }
  62. for (let i = 0; i < pathArr.length; i++) {
  63. const p = pathArr[i];
  64. if (!isObj(obj[p])) {
  65. obj[p] = {};
  66. }
  67. if (i === pathArr.length - 1) {
  68. obj[p] = value;
  69. }
  70. obj = obj[p];
  71. }
  72. return root;
  73. },
  74. delete(obj, path) {
  75. if (!isObj(obj) || typeof path !== 'string') {
  76. return;
  77. }
  78. const pathArr = getPathSegments(path);
  79. for (let i = 0; i < pathArr.length; i++) {
  80. const p = pathArr[i];
  81. if (i === pathArr.length - 1) {
  82. delete obj[p];
  83. return;
  84. }
  85. obj = obj[p];
  86. if (!isObj(obj)) {
  87. return;
  88. }
  89. }
  90. },
  91. has(obj, path) {
  92. if (!isObj(obj) || typeof path !== 'string') {
  93. return false;
  94. }
  95. const pathArr = getPathSegments(path);
  96. for (let i = 0; i < pathArr.length; i++) {
  97. if (isObj(obj)) {
  98. if (!(pathArr[i] in obj)) {
  99. return false;
  100. }
  101. obj = obj[pathArr[i]];
  102. } else {
  103. return false;
  104. }
  105. }
  106. return true;
  107. }
  108. };