Skip to content

Commit 53b05a4

Browse files
Merge pull request #3 from dutchenkoOleg/master
0.1.0-beta
2 parents 48d9bfe + 5161605 commit 53b05a4

File tree

8 files changed

+258
-8
lines changed

8 files changed

+258
-8
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55
[![NPM package badge](https://img.shields.io/badge/npm-install-orange.svg)](https://www.npmjs.com/package/plain-object-helpers)
66
![Test and Build status badge](https://github.com/JS-Helpers/plain-object-helpers/workflows/Test%20and%20Build/badge.svg)
77

8+
## Helpers
9+
10+
- `getIn()`
11+
- `setIn()`
12+
- `toPath()`
13+
- `isPlaingObject()`
14+
- `isEmptyObject()`
15+
16+
817
## Coverage
918

1019
| Statements | Branches | Functions | Lines |

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "plain-object-helpers",
3-
"version": "0.0.3-prealpha",
3+
"version": "0.1.0-beta",
44
"description": "description",
55
"main": "dist/index.js",
66
"files": [

src/get-in.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { isPlainObject } from './is-plain-object';
1010
// -----------------------------------------------------------------------------
1111

1212
/**
13-
* Convert string sample in to the path (array)
13+
* Getting value in Object or Array by path
1414
* Inspired by final-form's `getIn` helper
1515
* {@link https://github.com/final-form/final-form/blob/master/src/structure/getIn.js}
1616
* @param {*} sample

src/is-empty-object.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@
22
// Deps
33
// -----------------------------------------------------------------------------
44

5-
import { isPlainObject } from './is-plain-object';
5+
import { IPlainObject } from './types';
66

77
// -----------------------------------------------------------------------------
88
// Helper
99
// -----------------------------------------------------------------------------
1010

1111
/**
12-
* @param sample
12+
* @param {IPlainObject} sample
1313
* @return {boolean}
1414
*/
15-
export function isEmptyObject(sample: any) {
16-
return isPlainObject(sample) && Object.keys(sample).length === 0;
15+
export function isEmptyObject(sample: IPlainObject) {
16+
let empty = true;
17+
for (const key in sample) {
18+
if (sample.hasOwnProperty(key)) {
19+
empty = false;
20+
break;
21+
}
22+
}
23+
return empty;
1724
}

src/set-in.test.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// -----------------------------------------------------------------------------
2+
// Deps
3+
// -----------------------------------------------------------------------------
4+
5+
import { setIn } from './set-in';
6+
import { IPlainObject } from './types';
7+
8+
// -----------------------------------------------------------------------------
9+
// Tests
10+
// -----------------------------------------------------------------------------
11+
12+
const testCases: {
13+
parameters: Parameters<typeof setIn>;
14+
result: IPlainObject;
15+
}[] = [
16+
{
17+
parameters: [{}, 'key', 'value'],
18+
result: { key: 'value' }
19+
},
20+
{
21+
parameters: [{}, 'key1.key2.key3.key4', 'value'],
22+
result: {
23+
key1: {
24+
key2: {
25+
key3: {
26+
key4: 'value'
27+
}
28+
}
29+
}
30+
}
31+
},
32+
{
33+
parameters: [
34+
{
35+
key1: {
36+
key2: {
37+
xxx: true,
38+
zzz: false
39+
}
40+
}
41+
},
42+
'key1.key2.key3.key4',
43+
'value'
44+
],
45+
result: {
46+
key1: {
47+
key2: {
48+
xxx: true,
49+
zzz: false,
50+
key3: {
51+
key4: 'value'
52+
}
53+
}
54+
}
55+
}
56+
},
57+
{
58+
parameters: [{ list: [] }, 'list[2]', 'value'],
59+
result: {
60+
list: [undefined, undefined, 'value']
61+
}
62+
},
63+
{
64+
parameters: [{}, 'list[2]', 'value'],
65+
result: {
66+
list: [undefined, undefined, 'value']
67+
}
68+
},
69+
{
70+
parameters: [{}, 'key1[0].key3', 'value'],
71+
result: {
72+
key1: [
73+
{
74+
key3: 'value'
75+
}
76+
]
77+
}
78+
},
79+
{
80+
parameters: [
81+
{
82+
key1: [
83+
{
84+
key3: 'prev value'
85+
}
86+
]
87+
},
88+
'key1[0].key3',
89+
'new value'
90+
],
91+
result: {
92+
key1: [
93+
{
94+
key3: 'new value'
95+
}
96+
]
97+
}
98+
},
99+
{
100+
parameters: [
101+
{
102+
key1: [null]
103+
},
104+
'key1[0].key3',
105+
'new value'
106+
],
107+
result: {
108+
key1: [
109+
{
110+
key3: 'new value'
111+
}
112+
]
113+
}
114+
}
115+
];
116+
117+
describe('setIn tool', () => {
118+
testCases.forEach(({ parameters, result }, i) => {
119+
test(`test #${i + 1}: ${parameters.join(', ')}`, () => {
120+
if (typeof result === 'string') {
121+
} else {
122+
setIn(...parameters);
123+
const _expect = JSON.stringify(parameters[0], undefined, '\t');
124+
const _result = JSON.stringify(result, undefined, '\t');
125+
expect(_expect).toStrictEqual(_result);
126+
}
127+
});
128+
});
129+
});
130+
131+
// -----------------------------------------------------------------------------
132+
// Error handling
133+
// -----------------------------------------------------------------------------
134+
135+
const errorCases: {
136+
parameters: Parameters<typeof setIn>;
137+
error: string | RegExp;
138+
}[] = [
139+
{
140+
parameters: [{ list: [] }, 'list.item', 'value'],
141+
error: /must be an array index \(correct int\)/
142+
}
143+
];
144+
145+
describe('setIn tool: error handling', () => {
146+
errorCases.forEach(({ parameters, error }, i) => {
147+
test(`error #${i + 1}: ${error}`, () => {
148+
expect(() => setIn(...parameters)).toThrow(error);
149+
});
150+
});
151+
});

src/set-in.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// -----------------------------------------------------------------------------
2+
// Deps
3+
// -----------------------------------------------------------------------------
4+
5+
import { toPath } from './to-path';
6+
import { isPlainObject } from './is-plain-object';
7+
import { IPlainObject } from './types';
8+
9+
// -----------------------------------------------------------------------------
10+
// Helper
11+
// -----------------------------------------------------------------------------
12+
13+
const setInRecursive = (
14+
current: any | any[],
15+
value: any,
16+
path: (string | number)[],
17+
index: number
18+
) => {
19+
if (index >= path.length) {
20+
return;
21+
}
22+
const key = path[index];
23+
const nextIndex = index + 1;
24+
const numberKey = typeof key === 'number';
25+
const sampleIsArray = Array.isArray(current);
26+
27+
if (nextIndex === path.length) {
28+
if (sampleIsArray) {
29+
if (numberKey) {
30+
current[key] = value;
31+
} else {
32+
throw new Error(`Key \`${key}\` must be an array index (correct int)`);
33+
}
34+
} else if (isPlainObject(current)) {
35+
current[key] = value;
36+
} else {
37+
console.log(current);
38+
throw new Error('Last chain must be an object or array');
39+
}
40+
} else {
41+
if (sampleIsArray) {
42+
if (numberKey) {
43+
if (!(Array.isArray(current[key]) || isPlainObject(current[key]))) {
44+
current[key] = typeof path[nextIndex] === 'number' ? [] : {};
45+
}
46+
setInRecursive(current[key], value, path, nextIndex);
47+
} else {
48+
throw new Error(`Key \`${key}\` must be an array index (correct int)`);
49+
}
50+
} else if (isPlainObject(current)) {
51+
if (
52+
!current.hasOwnProperty(key) ||
53+
!(Array.isArray(current[key]) || isPlainObject(current[key]))
54+
) {
55+
// setting inner prop based on next path index
56+
current[key] = typeof path[nextIndex] === 'number' ? [] : {};
57+
}
58+
setInRecursive(current[key], value, path, nextIndex);
59+
} else {
60+
throw new Error('`setIn()` tool ends up with wrong result');
61+
}
62+
}
63+
};
64+
65+
/**
66+
* Setting value in Object by path
67+
* Inspired by final-form's `setIn` helper
68+
* {@link https://github.com/final-form/final-form/blob/master/src/structure/setIn.js}
69+
* @param {IPlainObject} sample
70+
* @param {string} keyPath
71+
* @param {*} value
72+
*/
73+
export function setIn(sample: IPlainObject, keyPath: string, value: any) {
74+
return setInRecursive(
75+
sample,
76+
value,
77+
toPath(keyPath).map((key) => {
78+
const index = parseInt(key);
79+
return isNaN(index) ? key : index;
80+
}),
81+
0
82+
);
83+
}

src/to-path.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const regex = /(\[(?=\d+])|(?<=\[\d+)])|\.+/;
1010
// -----------------------------------------------------------------------------
1111

1212
/**
13-
* Convert string sample in to the path (array)
13+
* Convert string sample in to the props path
1414
* Inspired by final-form's `toPath` helper
1515
* {@link https://github.com/final-form/final-form/blob/master/src/structure/toPath.js}
1616
* @param {string|null} [sample]

0 commit comments

Comments
 (0)