Skip to content

Commit c30ed56

Browse files
authored
add set and construct to object module (#236)
1 parent 766347e commit c30ed56

File tree

9 files changed

+289
-44
lines changed

9 files changed

+289
-44
lines changed

Diff for: cdn/radash.esm.js

+39-12
Original file line numberDiff line numberDiff line change
@@ -647,8 +647,8 @@ const omit = (obj, keys2) => {
647647
{ ...obj }
648648
);
649649
};
650-
const get = (value, funcOrPath, defaultValue = null) => {
651-
const segments = funcOrPath.split(/[\.\[\]]/g);
650+
const get = (value, path, defaultValue = null) => {
651+
const segments = path.split(/[\.\[\]]/g);
652652
let current = value;
653653
for (const key of segments) {
654654
if (current === null)
@@ -663,20 +663,40 @@ const get = (value, funcOrPath, defaultValue = null) => {
663663
return defaultValue;
664664
return current;
665665
};
666-
const assign = (a, b) => {
667-
if (!a && !b)
666+
const set = (initial, path, value) => {
667+
if (!initial)
668668
return {};
669-
if (!a)
670-
return b;
671-
if (!b)
672-
return a;
673-
return Object.entries(a).reduce((acc, [key, value]) => {
669+
if (!path || !value)
670+
return initial;
671+
const segments = path.split(/[\.\[\]]/g).filter((x) => !!x.trim());
672+
const _set = (node) => {
673+
if (segments.length > 1) {
674+
const key = segments.shift();
675+
const nextIsNum = toInt(segments[0], null) === null ? false : true;
676+
node[key] = node[key] === void 0 ? nextIsNum ? [] : {} : node[key];
677+
_set(node[key]);
678+
} else {
679+
node[segments[0]] = value;
680+
}
681+
};
682+
const cloned = clone(initial);
683+
_set(cloned);
684+
return cloned;
685+
};
686+
const assign = (initial, override) => {
687+
if (!initial && !override)
688+
return {};
689+
if (!initial)
690+
return override;
691+
if (!override)
692+
return initial;
693+
return Object.entries(initial).reduce((acc, [key, value]) => {
674694
return {
675695
...acc,
676696
[key]: (() => {
677697
if (isObject(value))
678-
return assign(value, b[key]);
679-
return b[key];
698+
return assign(value, override[key]);
699+
return override[key];
680700
})()
681701
};
682702
}, {});
@@ -706,6 +726,13 @@ const crush = (value) => {
706726
(k) => get(value, k)
707727
);
708728
};
729+
const construct = (obj) => {
730+
if (!obj)
731+
return {};
732+
return Object.keys(obj).reduce((acc, path) => {
733+
return set(acc, path, obj[path]);
734+
}, {});
735+
};
709736

710737
const random = (min, max) => {
711738
return Math.floor(Math.random() * (max - min + 1) + min);
@@ -847,4 +874,4 @@ const trim = (str, charsToTrim = " ") => {
847874
return str.replace(regex, "");
848875
};
849876

850-
export { alphabetical, assign, boil, callable, camel, capitalize, chain, clone, cluster, compose, counting, crush, dash, debounce, defer, diff, draw, first, flat, fork, get, group, intersects, invert, isArray, isDate, isEmpty, isEqual, isFloat, isFunction, isInt, isNumber, isObject, isPrimitive, isString, isSymbol, iterate, keys, last, list, listify, lowerize, map, mapEntries, mapKeys, mapValues, max, memo, merge, min, objectify, omit, parallel, partial, partob, pascal, pick, proxied, random, range, reduce, replace, replaceOrAppend, retry, select, series, shake, shift, shuffle, sift, sleep, snake, sort, sum, template, throttle, title, toFloat, toInt, toggle, trim, tryit as try, tryit, uid, unique, upperize, zip, zipToObject };
877+
export { alphabetical, assign, boil, callable, camel, capitalize, chain, clone, cluster, compose, construct, counting, crush, dash, debounce, defer, diff, draw, first, flat, fork, get, group, intersects, invert, isArray, isDate, isEmpty, isEqual, isFloat, isFunction, isInt, isNumber, isObject, isPrimitive, isString, isSymbol, iterate, keys, last, list, listify, lowerize, map, mapEntries, mapKeys, mapValues, max, memo, merge, min, objectify, omit, parallel, partial, partob, pascal, pick, proxied, random, range, reduce, replace, replaceOrAppend, retry, select, series, set, shake, shift, shuffle, sift, sleep, snake, sort, sum, template, throttle, title, toFloat, toInt, toggle, trim, tryit as try, tryit, uid, unique, upperize, zip, zipToObject };

Diff for: cdn/radash.js

+40-11
Original file line numberDiff line numberDiff line change
@@ -650,8 +650,8 @@ var radash = (function (exports) {
650650
{ ...obj }
651651
);
652652
};
653-
const get = (value, funcOrPath, defaultValue = null) => {
654-
const segments = funcOrPath.split(/[\.\[\]]/g);
653+
const get = (value, path, defaultValue = null) => {
654+
const segments = path.split(/[\.\[\]]/g);
655655
let current = value;
656656
for (const key of segments) {
657657
if (current === null)
@@ -666,20 +666,40 @@ var radash = (function (exports) {
666666
return defaultValue;
667667
return current;
668668
};
669-
const assign = (a, b) => {
670-
if (!a && !b)
669+
const set = (initial, path, value) => {
670+
if (!initial)
671671
return {};
672-
if (!a)
673-
return b;
674-
if (!b)
675-
return a;
676-
return Object.entries(a).reduce((acc, [key, value]) => {
672+
if (!path || !value)
673+
return initial;
674+
const segments = path.split(/[\.\[\]]/g).filter((x) => !!x.trim());
675+
const _set = (node) => {
676+
if (segments.length > 1) {
677+
const key = segments.shift();
678+
const nextIsNum = toInt(segments[0], null) === null ? false : true;
679+
node[key] = node[key] === void 0 ? nextIsNum ? [] : {} : node[key];
680+
_set(node[key]);
681+
} else {
682+
node[segments[0]] = value;
683+
}
684+
};
685+
const cloned = clone(initial);
686+
_set(cloned);
687+
return cloned;
688+
};
689+
const assign = (initial, override) => {
690+
if (!initial && !override)
691+
return {};
692+
if (!initial)
693+
return override;
694+
if (!override)
695+
return initial;
696+
return Object.entries(initial).reduce((acc, [key, value]) => {
677697
return {
678698
...acc,
679699
[key]: (() => {
680700
if (isObject(value))
681-
return assign(value, b[key]);
682-
return b[key];
701+
return assign(value, override[key]);
702+
return override[key];
683703
})()
684704
};
685705
}, {});
@@ -709,6 +729,13 @@ var radash = (function (exports) {
709729
(k) => get(value, k)
710730
);
711731
};
732+
const construct = (obj) => {
733+
if (!obj)
734+
return {};
735+
return Object.keys(obj).reduce((acc, path) => {
736+
return set(acc, path, obj[path]);
737+
}, {});
738+
};
712739

713740
const random = (min, max) => {
714741
return Math.floor(Math.random() * (max - min + 1) + min);
@@ -860,6 +887,7 @@ var radash = (function (exports) {
860887
exports.clone = clone;
861888
exports.cluster = cluster;
862889
exports.compose = compose;
890+
exports.construct = construct;
863891
exports.counting = counting;
864892
exports.crush = crush;
865893
exports.dash = dash;
@@ -916,6 +944,7 @@ var radash = (function (exports) {
916944
exports.retry = retry;
917945
exports.select = select;
918946
exports.series = series;
947+
exports.set = set;
919948
exports.shake = shake;
920949
exports.shift = shift;
921950
exports.shuffle = shuffle;

Diff for: cdn/radash.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: docs/object/construct.mdx

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
title: construct
3+
description: Builds an object from key paths and values
4+
group: Object
5+
---
6+
7+
## Basic usage
8+
9+
The opposite of crush, given an object that was crushed into key paths and values will return the original object reconstructed.
10+
11+
```ts
12+
import { construct } from 'radash'
13+
14+
const flat = {
15+
name: 'ra',
16+
power: 100,
17+
'friend.name': 'loki',
18+
'friend.power': 80,
19+
'enemies.0.name': 'hathor',
20+
'enemies.0.power': 12
21+
}
22+
23+
construct(flat)
24+
// {
25+
// name: 'ra',
26+
// power: 100,
27+
// friend: {
28+
// name: 'loki',
29+
// power: 80
30+
// },
31+
// enemies: [
32+
// {
33+
// name: 'hathor',
34+
// power: 12
35+
// }
36+
// ]
37+
// }
38+
```

Diff for: docs/object/set.mdx

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
title: set
3+
description: Set a value on an object using a path key
4+
group: Object
5+
---
6+
7+
## Basic usage
8+
9+
Opposite of get, dynamically set a nested value into an object using a key path. Does not modify the given initial object.
10+
11+
```ts
12+
import { set } from 'radash'
13+
14+
set({}, 'name', 'ra')
15+
// => { name: 'ra' }
16+
17+
set({}, 'cards[0].value', 2)
18+
// => { cards: [{ value: 2 }] }
19+
```

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "radash",
3-
"version": "10.6.0",
3+
"version": "10.7.0",
44
"description": "Functional utility library - modern, simple, typed, powerful",
55
"main": "dist/cjs/index.cjs",
66
"module": "dist/esm/index.mjs",

Diff for: src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export { toFloat, toInt } from './number'
5555
export {
5656
assign,
5757
clone,
58+
construct,
5859
crush,
5960
get,
6061
invert,
@@ -66,6 +67,7 @@ export {
6667
mapValues,
6768
omit,
6869
pick,
70+
set,
6971
shake,
7072
upperize
7173
} from './object'

Diff for: src/object.ts

+69-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { objectify } from './array'
2+
import { toInt } from './number'
23
import { isArray, isObject, isPrimitive } from './typed'
34

45
type LowercasedKeys<T extends Record<string, any>> = {
@@ -128,17 +129,20 @@ export const clone = <T>(obj: T): T => {
128129
return obj
129130
}
130131

131-
// Binding a function to an empty object creates a copy function.
132+
// Binding a function to an empty object creates a
133+
// copy function.
132134
if (typeof obj === 'function') {
133135
return obj.bind({})
134136
}
135137

136-
// Access the constructor and create a new object. This method can create an array as well.
138+
// Access the constructor and create a new object.
139+
// This method can create an array as well.
137140
const newObj = new ((obj as Object).constructor as { new (): T })()
138141

139142
// Assign the props.
140143
Object.getOwnPropertyNames(obj).forEach(prop => {
141-
// Bypass type checking since the primitive cases are already checked in the beginning
144+
// Bypass type checking since the primitive cases
145+
// are already checked in the beginning
142146
;(newObj as any)[prop] = (obj as any)[prop]
143147
})
144148

@@ -209,10 +213,10 @@ export const omit = <T, TKeys extends keyof T>(
209213
*/
210214
export const get = <T, K>(
211215
value: T,
212-
funcOrPath: string,
216+
path: string,
213217
defaultValue: K | null = null
214218
): K | null => {
215-
const segments = (funcOrPath as string).split(/[\.\[\]]/g)
219+
const segments = path.split(/[\.\[\]]/g)
216220
let current: any = value
217221
for (const key of segments) {
218222
if (current === null) return defaultValue
@@ -224,24 +228,60 @@ export const get = <T, K>(
224228
return current
225229
}
226230

231+
/**
232+
* Opposite of get, dynamically set a nested value into
233+
* an object using a key path. Does not modify the given
234+
* initial object.
235+
*
236+
* @example
237+
* set({}, 'name', 'ra') // => { name: 'ra' }
238+
* set({}, 'cards[0].value', 2) // => { cards: [{ value: 2 }] }
239+
*/
240+
export const set = <T extends object, K>(
241+
initial: T,
242+
path: string,
243+
value: K
244+
): T => {
245+
if (!initial) return {} as T
246+
if (!path || !value) return initial
247+
const segments = path.split(/[\.\[\]]/g).filter(x => !!x.trim())
248+
const _set = (node: any) => {
249+
if (segments.length > 1) {
250+
const key = segments.shift() as string
251+
const nextIsNum = toInt(segments[0], null) === null ? false : true
252+
node[key] = node[key] === undefined ? (nextIsNum ? [] : {}) : node[key]
253+
_set(node[key])
254+
} else {
255+
node[segments[0]] = value
256+
}
257+
}
258+
// NOTE: One day, when structuredClone has more
259+
// compatability use it to clone the value
260+
// https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
261+
const cloned = clone(initial)
262+
_set(cloned)
263+
return cloned
264+
}
265+
227266
/**
228267
* Merges two objects together recursivly into a new
229268
* object applying values from right to left.
230269
* Recursion only applies to child object properties.
231270
*/
232271
export const assign = <X extends Record<string | symbol | number, any>>(
233-
a: X,
234-
b: X
272+
initial: X,
273+
override: X
235274
): X => {
236-
if (!a && !b) return {} as X
237-
if (!a) return b as X
238-
if (!b) return a as X
239-
return Object.entries(a).reduce((acc, [key, value]) => {
275+
if (!initial && !override) return {} as X
276+
if (!initial) return override as X
277+
if (!override) return initial as X
278+
return Object.entries(initial).reduce((acc, [key, value]) => {
240279
return {
241280
...acc,
242281
[key]: (() => {
243-
if (isObject(value)) return assign(value, b[key])
244-
return b[key]
282+
if (isObject(value)) return assign(value, override[key])
283+
// if (isArray(value)) return value.map(x => assign)
284+
return override[key]
245285
})()
246286
}
247287
}, {} as X)
@@ -287,3 +327,19 @@ export const crush = <TValue extends object>(value: TValue): object => {
287327
k => get(value, k)
288328
)
289329
}
330+
331+
/**
332+
* The opposite of crush, given an object that was
333+
* crushed into key paths and values will return
334+
* the original object reconstructed.
335+
*
336+
* @example
337+
* construct({ name: 'ra', 'children.0.name': 'hathor' })
338+
* // { name: 'ra', children: [{ name: 'hathor' }] }
339+
*/
340+
export const construct = <TObject extends object>(obj: TObject): object => {
341+
if (!obj) return {}
342+
return Object.keys(obj).reduce((acc, path) => {
343+
return set(acc, path, (obj as any)[path])
344+
}, {})
345+
}

0 commit comments

Comments
 (0)