index.js 2.6 KB

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