simple-update-in
Advanced tools
Comparing version 1.1.2-master.cf20461 to 1.2.0
@@ -9,4 +9,13 @@ # Changelog | ||
## [1.1.1] | ||
## [1.2.0] - 2018-04-14 | ||
### Added | ||
- If after `updater` result in nothing change (triple-equal `===`), will return untouched | ||
- `updater` returned `undefined` will be treated as removing the item | ||
### Fixed | ||
- Append not creating sub-structure correctly | ||
- `updateIn([1, 2], [-1, 0], 'Hello')` should return `[1, 2, ['Hello']]` instead of `[1, 2, 'Hello']` | ||
## [1.1.1] - 2018-04-06 | ||
### Fixed | ||
- Move `babel` and `gulp` into `devDependencies` | ||
@@ -13,0 +22,0 @@ |
@@ -13,7 +13,11 @@ 'use strict'; | ||
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } | ||
function setIn(obj, path, updater) { | ||
if (!Array.isArray(path)) { | ||
throw new Error('path must be an array'); | ||
} | ||
if (!path.length) { | ||
@@ -27,34 +31,58 @@ return updater(obj); | ||
var value = typeof obj !== 'undefined' && obj[accessor]; | ||
var nextObj = obj; | ||
if (typeof accessor === 'string' && ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) !== 'object' || Array.isArray(obj))) { | ||
obj = {}; | ||
} else if (typeof accessor === 'number' && !Array.isArray(obj)) { | ||
obj = []; | ||
if (typeof accessor === 'string' && ((typeof nextObj === 'undefined' ? 'undefined' : _typeof(nextObj)) !== 'object' || Array.isArray(nextObj))) { | ||
nextObj = {}; | ||
} else if (typeof accessor === 'number' && !Array.isArray(nextObj)) { | ||
nextObj = []; | ||
} | ||
if (typeof accessor === 'number') { | ||
obj = obj && obj.slice(); | ||
if (updater || path.length) { | ||
if (accessor === -1) { | ||
obj.push(updater()); | ||
} else { | ||
obj[accessor] = setIn(value, path, updater); | ||
return [].concat(_toConsumableArray(nextObj), [setIn([], path, updater)]); | ||
} | ||
} else { | ||
obj.splice(accessor, 1); | ||
var nextValue = setIn(value, path, updater); | ||
if (typeof nextValue !== 'undefined') { | ||
if (nextValue === value) { | ||
return obj; | ||
} else { | ||
nextObj = [].concat(_toConsumableArray(nextObj)); | ||
nextObj[accessor] = nextValue; | ||
return nextObj; | ||
} | ||
} | ||
} | ||
return obj; | ||
// If updater returned undefined or no updater at all, delete the item | ||
if (accessor in nextObj) { | ||
nextObj = [].concat(_toConsumableArray(nextObj)); | ||
nextObj.splice(accessor, 1); | ||
} | ||
return nextObj; | ||
} else { | ||
if (updater || path.length) { | ||
return _extends({}, obj, _defineProperty({}, accessor, setIn(value, path, updater))); | ||
} else { | ||
var _obj = obj, | ||
deleted = _obj[accessor], | ||
nextObj = _objectWithoutProperties(_obj, [accessor]); | ||
var _nextValue = setIn(value, path, updater); | ||
return nextObj; | ||
if (typeof _nextValue !== 'undefined') { | ||
if (_nextValue === value) { | ||
return obj; | ||
} else { | ||
return _extends({}, nextObj, _defineProperty({}, accessor, _nextValue)); | ||
} | ||
} | ||
} | ||
// If updater returned undefined or no updater at all, delete the key | ||
if (accessor in nextObj) { | ||
nextObj = _extends({}, nextObj); | ||
delete nextObj[accessor]; | ||
} | ||
return nextObj; | ||
} | ||
} |
{ | ||
"name": "simple-update-in", | ||
"version": "1.1.2-master.cf20461", | ||
"version": "1.2.0", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
@@ -19,10 +19,12 @@ # simple-update-in | ||
For example, `obj.one.two = 1.2`, call `updateIn(obj, ['one', 'two'], 1.2)`. It will return a new object with changes in deep clone. | ||
We share similar signature as [ImmutableJS.updateIn](https://facebook.github.io/immutable-js/docs/#/Map/updateIn): | ||
```js | ||
updateIn( | ||
target: Array|Map, | ||
updateIn<T: Array|Map>( | ||
target: T, | ||
path: (Number|String)[], | ||
updater?: (value: any) => any | ||
) | ||
): T | ||
``` | ||
@@ -32,2 +34,4 @@ | ||
Like other immutable framework, `updater` is expected to return a new object if there is a change. If the update do not result in a change (triple-equal `===`), then, the original object is returned. | ||
## Example | ||
@@ -65,3 +69,3 @@ | ||
You can also use `updateIn` to remove a key by passing a falsy value to the `updater` argument. | ||
You can also use `updateIn` to remove a key by passing a falsy value to the `updater` argument, or return `undefined`. | ||
@@ -78,2 +82,6 @@ ```js | ||
> When removing a non-existing key, the original object will be returned. | ||
The sample code above also works with `updater` returning `undefined`, for example, `updateIn(from, ['two'], () => undefined)`. | ||
## Remove an item in array | ||
@@ -88,2 +96,4 @@ | ||
> Also for `updater` returning `undefined` | ||
## Automatic expansion | ||
@@ -98,2 +108,4 @@ | ||
> If the `updater` return `undefined`, the object will be untouched. | ||
## Replace incompatible types | ||
@@ -121,2 +133,13 @@ | ||
### Corner case | ||
If the target value is of incompatible type, we will convert it to correct type before setting it. In the following sample, the actual value is an empty map instead of the original array. | ||
```js | ||
const from = [0, 1, 2]; | ||
const actual = updateIn(from, ['one']); | ||
expect(actual).toEqual({}); | ||
``` | ||
## Adding an item to array | ||
@@ -128,3 +151,3 @@ | ||
const from = [0, 1]; | ||
const actual = updateIn(from, [-1], 2); | ||
const actual = updateIn(from, [-1], () => 2); | ||
@@ -134,2 +157,4 @@ expect(actual).toEqual([0, 1, 2]); | ||
> If `updater` returned `undefined`, the value will not be appended. | ||
There is no support on prepend or insertion, however, you can use Rest Operator for array manipulation. | ||
@@ -136,0 +161,0 @@ |
export default function setIn(obj, path, updater) { | ||
if (!Array.isArray(path)) { | ||
throw new Error('path must be an array'); | ||
} | ||
if (!path.length) { | ||
@@ -9,36 +13,62 @@ return updater(obj); | ||
const accessor = path.shift(); | ||
let value = typeof obj !== 'undefined' && obj[accessor]; | ||
const value = typeof obj !== 'undefined' && obj[accessor]; | ||
let nextObj = obj; | ||
if (typeof accessor === 'string' && (typeof obj !== 'object' || Array.isArray(obj))) { | ||
obj = {}; | ||
} else if (typeof accessor === 'number' && !Array.isArray(obj)) { | ||
obj = []; | ||
if (typeof accessor === 'string' && (typeof nextObj !== 'object' || Array.isArray(nextObj))) { | ||
nextObj = {}; | ||
} else if (typeof accessor === 'number' && !Array.isArray(nextObj)) { | ||
nextObj = []; | ||
} | ||
if (typeof accessor === 'number') { | ||
obj = obj && obj.slice(); | ||
if (updater || path.length) { | ||
if (accessor === -1) { | ||
obj.push(updater()); | ||
} else { | ||
obj[accessor] = setIn(value, path, updater); | ||
return [...nextObj, setIn([], path, updater)]; | ||
} | ||
} else { | ||
obj.splice(accessor, 1); | ||
const nextValue = setIn(value, path, updater); | ||
if (typeof nextValue !== 'undefined') { | ||
if (nextValue === value) { | ||
return obj; | ||
} else { | ||
nextObj = [...nextObj]; | ||
nextObj[accessor] = nextValue; | ||
return nextObj; | ||
} | ||
} | ||
} | ||
return obj; | ||
// If updater returned undefined or no updater at all, delete the item | ||
if (accessor in nextObj) { | ||
nextObj = [...nextObj]; | ||
nextObj.splice(accessor, 1); | ||
} | ||
return nextObj; | ||
} else { | ||
if (updater || path.length) { | ||
return { | ||
...obj, | ||
[accessor]: setIn(value, path, updater) | ||
}; | ||
} else { | ||
const { [accessor]: deleted, ...nextObj } = obj; | ||
const nextValue = setIn(value, path, updater); | ||
return nextObj; | ||
if (typeof nextValue !== 'undefined') { | ||
if (nextValue === value) { | ||
return obj; | ||
} else { | ||
return { | ||
...nextObj, | ||
[accessor]: nextValue | ||
}; | ||
} | ||
} | ||
} | ||
// If updater returned undefined or no updater at all, delete the key | ||
if (accessor in nextObj) { | ||
nextObj = { ...nextObj }; | ||
delete nextObj[accessor]; | ||
} | ||
return nextObj; | ||
} | ||
} |
@@ -172,1 +172,85 @@ import updateIn from './index'; | ||
}); | ||
test('append to array 2', () => { | ||
const from = [0, 1, 2]; | ||
const actual = updateIn(from, [-1, 0, 0], () => 3); | ||
expect(from).not.toBe(actual); | ||
expect(actual).toEqual([0, 1, 2, [[3]]]); | ||
}); | ||
test('modifying undefined in map', () => { | ||
const from = { one: 1 }; | ||
const actual = updateIn(from, ['two', 'three'], value => value && value * 10); | ||
expect(from).toBe(actual); | ||
}); | ||
test('modifying undefined in array', () => { | ||
const from = [0, 1, 2, 3]; | ||
const actual = updateIn(from, [4], value => value && value * 10); | ||
expect(from).toBe(actual); | ||
}); | ||
test('untouched in map', () => { | ||
const from = { one: 1 }; | ||
const actual = updateIn(from, ['one'], value => value); | ||
expect(from).toBe(actual); | ||
}); | ||
test('untouched in array', () => { | ||
const from = [0, 1, 2]; | ||
const actual = updateIn(from, [1], value => value); | ||
expect(from).toBe(actual); | ||
}); | ||
test('removing non-existing key in map', () => { | ||
const from = { one: 1 }; | ||
const actual = updateIn(from, ['two']); | ||
expect(from).toBe(actual); | ||
}); | ||
test('removing non-existing key in array', () => { | ||
const from = [0]; | ||
const actual = updateIn(from, [1]); | ||
expect(from).toBe(actual); | ||
}); | ||
test('removing using undefined in map', () => { | ||
const from = { one: 1 }; | ||
const actual = updateIn(from, ['one'], value => undefined); | ||
expect(actual).toEqual({}); | ||
}); | ||
test('removing using undefined in array', () => { | ||
const from = [0]; | ||
const actual = updateIn(from, [0], value => undefined); | ||
expect(actual).toEqual([]); | ||
}); | ||
test('incompatible type and untouched map', () => { | ||
const from = { one: 1 }; | ||
const actual = updateIn(from, [0], value => undefined); | ||
expect(actual).toEqual([]); | ||
}) | ||
test('incompatible type and untouched array', () => { | ||
const from = [0]; | ||
const actual = updateIn(from, ['one'], value => undefined); | ||
expect(actual).toEqual({}); | ||
}) | ||
test('path not array', () => { | ||
expect(() => { | ||
updateIn({}, 'not valid path'); | ||
}).toThrow(); | ||
}); |
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
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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
22532
337
0
167