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