immutable-set
Advanced tools
Comparing version 1.1.0 to 2.0.0
132
lib/set.js
@@ -19,2 +19,10 @@ 'use strict'; | ||
/** | ||
* Recursive equality tester. Return true if the top down element of the path in the base is equal to the given value | ||
* @param base current base | ||
* @param path current path | ||
* @param value value to verify | ||
* @param equality override the equality assertion | ||
* @returns {boolean} true if the value is in the base at the end of the path | ||
*/ | ||
function recursiveEqual(base, path, value, equality) { | ||
@@ -36,2 +44,92 @@ if (!base || (typeof base === 'undefined' ? 'undefined' : _typeof(base)) !== 'object') { | ||
/** | ||
* Compute next base in recursion | ||
* @param base current base | ||
* @param key current key in the path | ||
* @param nextKey next key in the path | ||
* @param withArrays create arrays instead of object when nextKey is a number and key has no value in the current base | ||
*/ | ||
var nextBase = function nextBase(base, key, nextKey, withArrays) { | ||
return base[key] || (withArrays && typeof nextKey === 'number' ? [] : {}); | ||
}; | ||
/** | ||
* Reduce multiple elements in the accumulator using the setFunction when base is an Array | ||
* @param setFunction | ||
*/ | ||
var reduceWithArray = function reduceWithArray(setFunction) { | ||
return ( | ||
/** | ||
* Reduce multiple elements in the accumulator | ||
* @param base current base | ||
* @param path current path | ||
* @param keys list of keys ; we know there that keys is an array or set | ||
* @param nextKey next key in the path | ||
* @param values list of values, has to be an array | ||
* @param withArrays | ||
* @param accumulator new instance of the array at the current level | ||
* @returns {*} the accumulator with the new elements | ||
*/ | ||
function (base, path, keys, nextKey, values, withArrays, accumulator) { | ||
if (!Array.isArray(values)) { | ||
throw new Error('Can not use object values with array in path'); | ||
} | ||
return keys.reduce(function (acc, key, index) { | ||
var newValue = setFunction(nextBase(base, key, nextKey, withArrays), path.slice(1), values[index], withArrays); | ||
if (key < acc.length) { | ||
acc[key] = newValue; | ||
} else { | ||
acc.push(newValue); | ||
} | ||
return acc; | ||
}, accumulator); | ||
} | ||
); | ||
}; | ||
/** | ||
* Reduce multiple elements in an new Object using the setFunction when base is an Object | ||
* @param setFunction | ||
*/ | ||
var reduceWithObject = function reduceWithObject(fn) { | ||
return ( | ||
/** | ||
* Reduce multiple elements in an new Object | ||
* @param base current base | ||
* @param path current path | ||
* @param keys list of keys ; we know there that keys is an array or set | ||
* @param nextKey next key in the path | ||
* @param values list of values, could be an array or an object | ||
* @param withArrays | ||
* @returns {*} a set of the new elements | ||
*/ | ||
function (base, path, keys, nextKey, values, withArrays) { | ||
if (Array.isArray(values)) { | ||
return keys.reduce(function (acc, key, index) { | ||
acc[key] = fn(nextBase(base, key, nextKey, withArrays), path.slice(1), values[index], withArrays); | ||
return acc; | ||
}, {}); | ||
} | ||
return keys.reduce(function (acc, key) { | ||
acc[key] = fn(nextBase(base, key, nextKey, withArrays), path.slice(1), values[key], withArrays); | ||
return acc; | ||
}, {}); | ||
} | ||
); | ||
}; | ||
/** | ||
* Recursive immutable set | ||
* @param base current base | ||
* @param path current path | ||
* @param value current value | ||
* @param withArrays create arrays instead of object when nextKey is a number and key has no value in the current base | ||
* @returns {*} a new instance of the given level | ||
*/ | ||
function set(base, path, value, withArrays) { | ||
@@ -46,20 +144,42 @@ if (path.length === 0) { | ||
var isArrayKeys = Array.isArray(key); | ||
var currentBase = base; | ||
if (!base || (typeof base === 'undefined' ? 'undefined' : _typeof(base)) !== 'object') { | ||
currentBase = withArrays && typeof key === 'number' ? [] : {}; | ||
currentBase = isArrayKeys && typeof key[0] === 'number' || withArrays && typeof key === 'number' ? [] : {}; | ||
} | ||
if (isArrayKeys) { | ||
if (Array.isArray(currentBase)) { | ||
return reduceWithArray(set)(currentBase, path, key, nextKey, value, withArrays, [].concat(_toConsumableArray(currentBase))); | ||
} | ||
return _extends({}, currentBase, reduceWithObject(set)(currentBase, path, key, nextKey, value, withArrays, {})); | ||
} | ||
if (Array.isArray(currentBase)) { | ||
return [].concat(_toConsumableArray(currentBase.slice(0, key)), [set(currentBase[key] || (withArrays && typeof nextKey === 'number' ? [] : {}), path.slice(1), value, withArrays)], _toConsumableArray(currentBase.slice(key + 1))); | ||
return [].concat(_toConsumableArray(currentBase.slice(0, key)), [set(nextBase(currentBase, key, nextKey, withArrays), path.slice(1), value, withArrays)], _toConsumableArray(currentBase.slice(key + 1))); | ||
} | ||
return _extends({}, currentBase, _defineProperty({}, key, set(currentBase[key] || (withArrays && typeof nextKey === 'number' ? [] : {}), path.slice(1), value, withArrays))); | ||
return _extends({}, currentBase, _defineProperty({}, key, set(nextBase(currentBase, key, nextKey, withArrays), path.slice(1), value, withArrays))); | ||
} | ||
/** | ||
* Main function, recursive immutable set | ||
* @param base base object | ||
* @param initialPath path to the place where the value is going to be set | ||
* @param value value to set | ||
* @param options options | ||
* @returns {*} new instance of the base if it has been modified, the base otherwise | ||
*/ | ||
function safeSet(base, initialPath, value) { | ||
var withArrays = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; | ||
var equality = arguments[4]; | ||
var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; | ||
var _options$withArrays = options.withArrays, | ||
withArrays = _options$withArrays === undefined ? false : _options$withArrays, | ||
equality = options.equality, | ||
safe = options.safe; | ||
var path = initialPath; | ||
// Handle string path | ||
if (typeof path === 'string' && path.length > 0) { | ||
@@ -85,3 +205,3 @@ path = path.split('.'); | ||
// If the value is already here we just need to return the base | ||
if (recursiveEqual(base, path, value, equality)) { | ||
if (safe && recursiveEqual(base, path, value, equality)) { | ||
return base; | ||
@@ -88,0 +208,0 @@ } |
{ | ||
"name": "immutable-set", | ||
"version": "1.1.0", | ||
"version": "2.0.0", | ||
"description": "Set nested properties of an object while respecting the principles of immutability", | ||
@@ -5,0 +5,0 @@ "main": "lib/set.js", |
@@ -32,10 +32,17 @@ ![build status](https://travis-ci.org/M6Web/immutable-set.svg) | ||
name | description | type | requiered | ||
---- | ----------- | ---- | --------- | ||
base | object to modify | object | ✅ | ||
path | list of keys to access the value to being modified | array or string | ✅ | ||
value | value to set | any | ✅ | ||
options | options... | object | ||
### Options | ||
name | description | type | default | ||
---- | ----------- | ---- | ------- | ||
base | object to modify | object | ||
path | list of keys to access the value to being modified | array or string | ||
value | value to set | any | ||
withArray (optional)| if set to `true` number will be interpreted has array indexes | boolean | false | ||
equality (optional) | if provided, the function will be used to determine if the value at the path is equal to the value provided | function | `===` | ||
withArray | if set to `true` number will be interpreted has array indexes | boolean | false | ||
equality | if provided, the function will be used to determine if the value at the path is equal to the value provided | function | `===` | ||
safe | verify if the value does not already exist | boolean | false | ||
## Usage | ||
@@ -51,12 +58,10 @@ Import `set` function | ||
```js | ||
const newState = set(state, ['a', 0, 'b'], 42, true); | ||
const newState = set(state, ['a', 'b'], 42); | ||
// or | ||
const newState = set(state, 'a[0].b', 42, true); | ||
const newState = set(state, 'a.b', 42); | ||
/* | ||
newState => { | ||
a: [ | ||
{ | ||
b: 42, | ||
}, | ||
], | ||
a: { | ||
b: 42, | ||
}, | ||
... | ||
@@ -67,2 +72,48 @@ } | ||
The function mutates the object only if the value is not already present in the object | ||
The function mutates the object only if the value is not already present in the object. | ||
## Advanced usage | ||
### with arrays | ||
The option `withArrays` allow to dynamically create arrays when the current level is empty and the current path key is a number. | ||
```js | ||
let base = set({}, ['a', 0], 12, { withArrays: true }); | ||
// will return { a: [12] } | ||
``` | ||
### safe | ||
The option `safe` will verify if the value is not already in the object. | ||
```js | ||
const base = { a: 2 }; | ||
set(base, 'a', 2, { safe: true }) | ||
// will return the base unmodified | ||
``` | ||
### equality | ||
The option `equality` allow to use an other equality function instead of `===`. It has to be used with `safe` option. | ||
```js | ||
const base = { a: { id: 1, v: 0 } }; | ||
const equality = (a, b) => a.id === b.id && a.v === b.v }; | ||
set(base, 'a', { id: 1, v: 0 }, { safe: true, equality); | ||
// will return the base unmodified | ||
set(base, 'a', { id: 1, v: 1 }, { safe: true, equality); | ||
// will return { a: { id: 1, v: 1 } } | ||
``` | ||
### multiple set | ||
It is possible to set multiple elements at once, providing multiple keys in the path and an array (or an object) in value. | ||
```js | ||
set({}, ['a', ['b', 'c']], [12, 13]); | ||
// or | ||
set({}, ['a', ['b', 'c']], { b: 12, c: 13 }); | ||
// will return { a: { b: 12, c: 13 } } | ||
set({}, ['a', [0, 1 ]], [12, 13], { withArrays: true }); | ||
// will return { a: [12, 13] } | ||
``` | ||
- :warning: If the array of keys is not the last element of the path, the rest of the path will be used for each sub tree. | ||
- :warning: It's not possible to set objects in array with object sub values, this will throw an error. | ||
- :warning: For now safe mode does not work with multiple set. |
129
src/set.js
@@ -0,1 +1,9 @@ | ||
/** | ||
* Recursive equality tester. Return true if the top down element of the path in the base is equal to the given value | ||
* @param base current base | ||
* @param path current path | ||
* @param value value to verify | ||
* @param equality override the equality assertion | ||
* @returns {boolean} true if the value is in the base at the end of the path | ||
*/ | ||
function recursiveEqual(base, path, value, equality) { | ||
@@ -14,2 +22,84 @@ if (!base || typeof base !== 'object') { | ||
/** | ||
* Compute next base in recursion | ||
* @param base current base | ||
* @param key current key in the path | ||
* @param nextKey next key in the path | ||
* @param withArrays create arrays instead of object when nextKey is a number and key has no value in the current base | ||
*/ | ||
const nextBase = (base, key, nextKey, withArrays) => base[key] || (withArrays && typeof nextKey === 'number' ? [] : {}); | ||
/** | ||
* Reduce multiple elements in the accumulator using the setFunction when base is an Array | ||
* @param setFunction | ||
*/ | ||
const reduceWithArray = setFunction => | ||
/** | ||
* Reduce multiple elements in the accumulator | ||
* @param base current base | ||
* @param path current path | ||
* @param keys list of keys ; we know there that keys is an array or set | ||
* @param nextKey next key in the path | ||
* @param values list of values, has to be an array | ||
* @param withArrays | ||
* @param accumulator new instance of the array at the current level | ||
* @returns {*} the accumulator with the new elements | ||
*/ | ||
(base, path, keys, nextKey, values, withArrays, accumulator) => { | ||
if (!Array.isArray(values)) { | ||
throw new Error('Can not use object values with array in path'); | ||
} | ||
return keys.reduce((acc, key, index) => { | ||
const newValue = setFunction(nextBase(base, key, nextKey, withArrays), path.slice(1), values[index], withArrays); | ||
if (key < acc.length) { | ||
acc[key] = newValue; | ||
} else { | ||
acc.push(newValue); | ||
} | ||
return acc; | ||
}, accumulator); | ||
}; | ||
/** | ||
* Reduce multiple elements in an new Object using the setFunction when base is an Object | ||
* @param setFunction | ||
*/ | ||
const reduceWithObject = fn => | ||
/** | ||
* Reduce multiple elements in an new Object | ||
* @param base current base | ||
* @param path current path | ||
* @param keys list of keys ; we know there that keys is an array or set | ||
* @param nextKey next key in the path | ||
* @param values list of values, could be an array or an object | ||
* @param withArrays | ||
* @returns {*} a set of the new elements | ||
*/ | ||
(base, path, keys, nextKey, values, withArrays) => { | ||
if (Array.isArray(values)) { | ||
return keys.reduce((acc, key, index) => { | ||
acc[key] = fn(nextBase(base, key, nextKey, withArrays), path.slice(1), values[index], withArrays); | ||
return acc; | ||
}, {}); | ||
} | ||
return keys.reduce((acc, key) => { | ||
acc[key] = fn(nextBase(base, key, nextKey, withArrays), path.slice(1), values[key], withArrays); | ||
return acc; | ||
}, {}); | ||
}; | ||
/** | ||
* Recursive immutable set | ||
* @param base current base | ||
* @param path current path | ||
* @param value current value | ||
* @param withArrays create arrays instead of object when nextKey is a number and key has no value in the current base | ||
* @returns {*} a new instance of the given level | ||
*/ | ||
function set(base, path, value, withArrays) { | ||
@@ -21,12 +111,24 @@ if (path.length === 0) { | ||
const [key, nextKey] = path; | ||
const isArrayKeys = Array.isArray(key); | ||
let currentBase = base; | ||
let currentBase = base; | ||
if (!base || typeof base !== 'object') { | ||
currentBase = withArrays && typeof key === 'number' ? [] : {}; | ||
currentBase = (isArrayKeys && typeof key[0] === 'number') || (withArrays && typeof key === 'number') ? [] : {}; | ||
} | ||
if (isArrayKeys) { | ||
if (Array.isArray(currentBase)) { | ||
return reduceWithArray(set)(currentBase, path, key, nextKey, value, withArrays, [...currentBase]); | ||
} | ||
return { | ||
...currentBase, | ||
...reduceWithObject(set)(currentBase, path, key, nextKey, value, withArrays, {}), | ||
}; | ||
} | ||
if (Array.isArray(currentBase)) { | ||
return [ | ||
...currentBase.slice(0, key), | ||
set(currentBase[key] || (withArrays && typeof nextKey === 'number' ? [] : {}), path.slice(1), value, withArrays), | ||
set(nextBase(currentBase, key, nextKey, withArrays), path.slice(1), value, withArrays), | ||
...currentBase.slice(key + 1), | ||
@@ -38,14 +140,19 @@ ]; | ||
...currentBase, | ||
[key]: set( | ||
currentBase[key] || (withArrays && typeof nextKey === 'number' ? [] : {}), | ||
path.slice(1), | ||
value, | ||
withArrays, | ||
), | ||
[key]: set(nextBase(currentBase, key, nextKey, withArrays), path.slice(1), value, withArrays), | ||
}; | ||
} | ||
export default function safeSet(base, initialPath, value, withArrays = false, equality) { | ||
/** | ||
* Main function, recursive immutable set | ||
* @param base base object | ||
* @param initialPath path to the place where the value is going to be set | ||
* @param value value to set | ||
* @param options options | ||
* @returns {*} new instance of the base if it has been modified, the base otherwise | ||
*/ | ||
export default function safeSet(base, initialPath, value, options = {}) { | ||
const { withArrays = false, equality, safe } = options; | ||
let path = initialPath; | ||
// Handle string path | ||
if (typeof path === 'string' && path.length > 0) { | ||
@@ -71,3 +178,3 @@ path = path.split('.'); | ||
// If the value is already here we just need to return the base | ||
if (recursiveEqual(base, path, value, equality)) { | ||
if (safe && recursiveEqual(base, path, value, equality)) { | ||
return base; | ||
@@ -74,0 +181,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18797
327
117
1