object-path-immutable
Advanced tools
Comparing version 1.0.3 to 2.0.0
400
index.js
@@ -1,254 +0,260 @@ | ||
/* globals define */ | ||
var deepmerge = require('deepmerge') | ||
var _hasOwnProperty = Object.prototype.hasOwnProperty | ||
(function (root, factory) { | ||
'use strict' | ||
/* istanbul ignore next:cant test */ | ||
if (typeof module === 'object' && typeof module.exports === 'object') { | ||
module.exports = factory() | ||
} else if (typeof define === 'function' && define.amd) { | ||
// AMD. Register as an anonymous module. | ||
define([], factory) | ||
} else { | ||
// Browser globals | ||
root.objectPath = factory() | ||
function isEmpty (value) { | ||
if (isNumber(value)) { | ||
return false | ||
} | ||
})(this, function () { | ||
'use strict' | ||
var _hasOwnProperty = Object.prototype.hasOwnProperty | ||
function isEmpty (value) { | ||
if (isNumber(value)) { | ||
return false | ||
} | ||
if (!value) { | ||
return true | ||
} | ||
if (isArray(value) && value.length === 0) { | ||
return true | ||
} else if (!isString(value)) { | ||
for (var i in value) { | ||
if (_hasOwnProperty.call(value, i)) { | ||
return false | ||
} | ||
if (!value) { | ||
return true | ||
} | ||
if (isArray(value) && value.length === 0) { | ||
return true | ||
} else if (!isString(value)) { | ||
for (var i in value) { | ||
if (_hasOwnProperty.call(value, i)) { | ||
return false | ||
} | ||
return true | ||
} | ||
return false | ||
return true | ||
} | ||
return false | ||
} | ||
function isNumber (value) { | ||
return typeof value === 'number' | ||
} | ||
function isNumber (value) { | ||
return typeof value === 'number' | ||
} | ||
function isString (obj) { | ||
return typeof obj === 'string' | ||
} | ||
function isString (obj) { | ||
return typeof obj === 'string' | ||
} | ||
function isArray (obj) { | ||
return Array.isArray(obj) | ||
} | ||
function isArray (obj) { | ||
return Array.isArray(obj) | ||
} | ||
function assignToObj (target, source) { | ||
for (var key in source) { | ||
if (_hasOwnProperty.call(source, key)) { | ||
target[key] = source[key] | ||
} | ||
function assignToObj (target, source) { | ||
for (var key in source) { | ||
if (_hasOwnProperty.call(source, key)) { | ||
target[key] = source[key] | ||
} | ||
return target | ||
} | ||
return target | ||
} | ||
function getKey (key) { | ||
var intKey = parseInt(key) | ||
if (intKey.toString() === key) { | ||
return intKey | ||
} | ||
return key | ||
function getKey (key) { | ||
var intKey = parseInt(key) | ||
if (intKey.toString() === key) { | ||
return intKey | ||
} | ||
return key | ||
} | ||
var objectPathImmutable = function (src) { | ||
var dest = src | ||
var committed = false | ||
function overwriteMerge (destinationArray, sourceArray) { | ||
return sourceArray | ||
} | ||
var transaction = Object.keys(api).reduce(function (proxy, prop) { | ||
/* istanbul ignore else */ | ||
if (typeof api[prop] === 'function') { | ||
proxy[prop] = function () { | ||
var args = [dest, src].concat(Array.prototype.slice.call(arguments)) | ||
var objectPathImmutable = function (src) { | ||
var dest = src | ||
var committed = false | ||
if (committed) { | ||
throw new Error('Cannot call ' + prop + ' after `value`') | ||
} | ||
var transaction = Object.keys(api).reduce(function (proxy, prop) { | ||
/* istanbul ignore else */ | ||
if (typeof api[prop] === 'function') { | ||
proxy[prop] = function () { | ||
var args = [dest, src].concat(Array.prototype.slice.call(arguments)) | ||
dest = api[prop].apply(null, args) | ||
return transaction | ||
if (committed) { | ||
throw new Error('Cannot call ' + prop + ' after `value`') | ||
} | ||
} | ||
return proxy | ||
}, {}) | ||
dest = api[prop].apply(null, args) | ||
transaction.value = function () { | ||
committed = true | ||
return dest | ||
return transaction | ||
} | ||
} | ||
return transaction | ||
return proxy | ||
}, {}) | ||
transaction.value = function () { | ||
committed = true | ||
return dest | ||
} | ||
function clone (obj, createIfEmpty, assumeArray) { | ||
if (obj == null) { | ||
if (createIfEmpty) { | ||
if (assumeArray) { | ||
return [] | ||
} | ||
return transaction | ||
} | ||
return {} | ||
function clone (obj, createIfEmpty, assumeArray) { | ||
if (obj == null) { | ||
if (createIfEmpty) { | ||
if (assumeArray) { | ||
return [] | ||
} | ||
return obj | ||
} else if (isArray(obj)) { | ||
return obj.slice() | ||
return {} | ||
} | ||
return assignToObj({}, obj) | ||
return obj | ||
} else if (isArray(obj)) { | ||
return obj.slice() | ||
} | ||
function changeImmutable (dest, src, path, changeCallback) { | ||
if (isNumber(path)) { | ||
path = [path] | ||
} | ||
if (isEmpty(path)) { | ||
return src | ||
} | ||
if (isString(path)) { | ||
return changeImmutable(dest, src, path.split('.').map(getKey), changeCallback) | ||
} | ||
var currentPath = path[0] | ||
return assignToObj({}, obj) | ||
} | ||
if (!dest || dest === src) { | ||
dest = clone(src, true, isNumber(currentPath)) | ||
} | ||
function changeImmutable (dest, src, path, changeCallback) { | ||
if (isNumber(path)) { | ||
path = [path] | ||
} | ||
if (isEmpty(path)) { | ||
return src | ||
} | ||
if (isString(path)) { | ||
return changeImmutable(dest, src, path.split('.').map(getKey), changeCallback) | ||
} | ||
var currentPath = path[0] | ||
if (path.length === 1) { | ||
return changeCallback(dest, currentPath) | ||
} | ||
if (!dest || dest === src) { | ||
dest = clone(src, true, isNumber(currentPath)) | ||
} | ||
if (src != null) { | ||
src = src[currentPath] | ||
} | ||
if (path.length === 1) { | ||
return changeCallback(dest, currentPath) | ||
} | ||
dest[currentPath] = changeImmutable(dest[currentPath], src, path.slice(1), changeCallback) | ||
if (src != null) { | ||
src = src[currentPath] | ||
} | ||
return dest | ||
dest[currentPath] = changeImmutable(dest[currentPath], src, path.slice(1), changeCallback) | ||
return dest | ||
} | ||
var api = {} | ||
api.set = function set (dest, src, path, value) { | ||
if (isEmpty(path)) { | ||
return value | ||
} | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
clonedObj[finalPath] = value | ||
return clonedObj | ||
}) | ||
} | ||
var api = {} | ||
api.set = function set (dest, src, path, value) { | ||
if (isEmpty(path)) { | ||
return value | ||
} | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
clonedObj[finalPath] = value | ||
return clonedObj | ||
}) | ||
api.update = function update (dest, src, path, updater) { | ||
if (isEmpty(path)) { | ||
return updater(clone(src)) | ||
} | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
clonedObj[finalPath] = updater(clonedObj[finalPath]) | ||
return clonedObj | ||
}) | ||
} | ||
api.update = function update (dest, src, path, updater) { | ||
if (isEmpty(path)) { | ||
return updater(clone(src)) | ||
api.push = function push (dest, src, path /*, values */) { | ||
var values = Array.prototype.slice.call(arguments, 3) | ||
if (isEmpty(path)) { | ||
if (!isArray(src)) { | ||
return values | ||
} else { | ||
return src.concat(values) | ||
} | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
clonedObj[finalPath] = updater(clonedObj[finalPath]) | ||
return clonedObj | ||
}) | ||
} | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
if (!isArray(clonedObj[finalPath])) { | ||
clonedObj[finalPath] = values | ||
} else { | ||
clonedObj[finalPath] = clonedObj[finalPath].concat(values) | ||
} | ||
return clonedObj | ||
}) | ||
} | ||
api.push = function push (dest, src, path /*, values */) { | ||
var values = Array.prototype.slice.call(arguments, 3) | ||
if (isEmpty(path)) { | ||
if (!isArray(src)) { | ||
return values | ||
} else { | ||
return src.concat(values) | ||
} | ||
api.insert = function insert (dest, src, path, value, at) { | ||
at = ~~at | ||
if (isEmpty(path)) { | ||
if (!isArray(src)) { | ||
return [value] | ||
} | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
if (!isArray(clonedObj[finalPath])) { | ||
clonedObj[finalPath] = values | ||
} else { | ||
clonedObj[finalPath] = clonedObj[finalPath].concat(values) | ||
} | ||
return clonedObj | ||
}) | ||
var first = src.slice(0, at) | ||
first.push(value) | ||
return first.concat(src.slice(at)) | ||
} | ||
api.insert = function insert (dest, src, path, value, at) { | ||
at = ~~at | ||
if (isEmpty(path)) { | ||
if (!isArray(src)) { | ||
return [value] | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
var arr = clonedObj[finalPath] | ||
if (!isArray(arr)) { | ||
if (arr != null && typeof arr !== 'undefined') { | ||
throw new Error('Expected ' + path + 'to be an array. Instead got ' + typeof path) | ||
} | ||
var first = src.slice(0, at) | ||
first.push(value) | ||
return first.concat(src.slice(at)) | ||
arr = [] | ||
} | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
var arr = clonedObj[finalPath] | ||
if (!isArray(arr)) { | ||
if (arr != null && typeof arr !== 'undefined') { | ||
throw new Error('Expected ' + path + 'to be an array. Instead got ' + typeof path) | ||
} | ||
arr = [] | ||
} | ||
var first = arr.slice(0, at) | ||
first.push(value) | ||
clonedObj[finalPath] = first.concat(arr.slice(at)) | ||
return clonedObj | ||
}) | ||
var first = arr.slice(0, at) | ||
first.push(value) | ||
clonedObj[finalPath] = first.concat(arr.slice(at)) | ||
return clonedObj | ||
}) | ||
} | ||
api.del = function del (dest, src, path) { | ||
if (isEmpty(path)) { | ||
return void 0 | ||
} | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
if (Array.isArray(clonedObj)) { | ||
if (clonedObj[finalPath] !== undefined) { | ||
clonedObj.splice(finalPath, 1) | ||
} | ||
} else { | ||
if (clonedObj.hasOwnProperty(finalPath)) { | ||
delete clonedObj[finalPath] | ||
} | ||
} | ||
return clonedObj | ||
}) | ||
} | ||
api.del = function del (dest, src, path) { | ||
if (isEmpty(path)) { | ||
return void 0 | ||
api.assign = function assign (dest, src, path, source) { | ||
if (isEmpty(path)) { | ||
if (isEmpty(source)) { | ||
return src | ||
} | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
if (Array.isArray(clonedObj)) { | ||
if (clonedObj[finalPath] !== undefined) { | ||
clonedObj.splice(finalPath, 1) | ||
} | ||
} else { | ||
if (clonedObj.hasOwnProperty(finalPath)) { | ||
delete clonedObj[finalPath] | ||
} | ||
} | ||
return clonedObj | ||
}) | ||
return assignToObj(clone(src), source) | ||
} | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
source = Object(source) | ||
var target = clone(clonedObj[finalPath], true) | ||
assignToObj(target, source) | ||
api.assign = function assign (dest, src, path, source) { | ||
if (isEmpty(path)) { | ||
if (isEmpty(source)) { | ||
return src | ||
} | ||
return assignToObj(clone(src), source) | ||
} | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
source = Object(source) | ||
var target = clone(clonedObj[finalPath], true) | ||
assignToObj(target, source) | ||
clonedObj[finalPath] = target | ||
return clonedObj | ||
}) | ||
} | ||
clonedObj[finalPath] = target | ||
return clonedObj | ||
}) | ||
api.merge = function assign (dest, src, path, source, options) { | ||
options = options || {} | ||
if (options.arrayMerge === void 0) { | ||
options.arrayMerge = overwriteMerge | ||
} | ||
return Object.keys(api).reduce(function (objectPathImmutable, method) { | ||
objectPathImmutable[method] = api[method].bind(null, null) | ||
if (isEmpty(path)) { | ||
if (isEmpty(source)) { | ||
return src | ||
} | ||
return deepmerge(src, source, options) | ||
} | ||
return changeImmutable(dest, src, path, function (clonedObj, finalPath) { | ||
source = Object(source) | ||
clonedObj[finalPath] = deepmerge(clonedObj[finalPath], source, options) | ||
return clonedObj | ||
}) | ||
} | ||
return objectPathImmutable | ||
}, objectPathImmutable) | ||
}) | ||
module.exports = Object.keys(api).reduce(function (objectPathImmutable, method) { | ||
objectPathImmutable[method] = api[method].bind(null, null) | ||
return objectPathImmutable | ||
}, objectPathImmutable) |
{ | ||
"name": "object-path-immutable", | ||
"version": "1.0.3", | ||
"version": "2.0.0", | ||
"description": "Modify deep object properties without modifying the original object (immutability). Works great with React and Redux.", | ||
"author": "Mario Casciaro <mariocasciaro@gmail.com>", | ||
"author": "Mario Casciaro <m@mario.fyi>", | ||
"homepage": "https://github.com/mariocasciaro/object-path-immutable", | ||
@@ -15,8 +15,12 @@ "repository": { | ||
}, | ||
"main": "index.js", | ||
"scripts": { | ||
"build": "rollup -c", | ||
"pretest": "standard", | ||
"test": "istanbul cover _mocha test.js --report html -- -R spec", | ||
"test": "npm run build && istanbul cover _mocha test.js --report html -- -R spec", | ||
"prepublish": "npm run test" | ||
}, | ||
"dependencies": {}, | ||
"dependencies": { | ||
"deepmerge": "^2.1.1" | ||
}, | ||
"devDependencies": { | ||
@@ -28,2 +32,5 @@ "chai": "^3.5.0", | ||
"mocha-lcov-reporter": "^1.2.0", | ||
"rollup": "^0.61.1", | ||
"rollup-plugin-commonjs": "^9.1.3", | ||
"rollup-plugin-node-resolve": "^3.3.0", | ||
"standard": "^8.3.0" | ||
@@ -51,3 +58,8 @@ }, | ||
"state" | ||
] | ||
], | ||
"standard": { | ||
"ignore": [ | ||
"dist" | ||
] | ||
} | ||
} |
@@ -17,12 +17,4 @@ [![build](https://img.shields.io/travis/mariocasciaro/object-path-immutable.svg?style=flat-square)](https://travis-ci.org/mariocasciaro/object-path-immutable) | ||
### 1.0 | ||
[View Changelog](CHANGELOG.md) | ||
- **Breaking change**: The way the library handles empty paths has changed. Before this change,all the methods were returning the original object. The new behavior is as follows. | ||
- `set(src, path, value)`: `value` is returned | ||
- `update(src, path, updater)`: `value` will be passed to `updater()` and the result returned | ||
- `set(src, path, ...values)`: `values` will be concatenated to `src` if `src` is an array, otherwise `values` will be returned | ||
- `insert(src, path, value, at)`: if `src` is an array then it will be cloned and `value` will be inserted at `at`, otherwise `[value]` will be returned | ||
- `del(src, path)`: returns `undefined` | ||
- `assign(src, path, target)`: Target is assigned to a clone of `src` and returned | ||
## Install | ||
@@ -118,4 +110,4 @@ | ||
a: { | ||
b: 1, | ||
}, | ||
b: 1 | ||
} | ||
} | ||
@@ -147,3 +139,3 @@ | ||
#### delete (initialObject, path) | ||
#### del (initialObject, path) | ||
@@ -173,3 +165,3 @@ Deletes a property. | ||
##### assign (initialObject, path, payload) | ||
#### assign (initialObject, path, payload) | ||
@@ -189,3 +181,3 @@ Shallow copy properties. | ||
##### insert (initialObject, path, payload, position) | ||
#### insert (initialObject, path, payload, position) | ||
@@ -204,2 +196,15 @@ Insert property at the specific array index. | ||
#### merge (initialObject, path, value, options) | ||
Deep merge properties. Uses the `deepmarge` package. | ||
One thing to notice is that `object-path-immutable` overwrites arrays by default | ||
instead of concatenating them. You can change this behavior by passing the `arrayMerge` | ||
option to `merge`. Please refer to the [deepmerge documentation](https://github.com/KyleAMathews/deepmerge) | ||
for the details. | ||
```javascript | ||
const newObj = immutable.merge(obj, 'a.c', {b: 'd'}) | ||
``` | ||
### Equivalent library with side effects | ||
@@ -209,4 +214,1 @@ | ||
### Credits | ||
* [Mario Casciaro](https://github.com/mariocasciaro) - Author |
86
test.js
@@ -6,3 +6,3 @@ /* globals describe, it */ | ||
var expect = require('chai').expect | ||
var op = require('./index.js') | ||
var op = require('./') | ||
@@ -405,2 +405,86 @@ describe('set', function () { | ||
describe('merge', function () { | ||
it('should merge an object without modifying the original object', function () { | ||
var obj = { | ||
a: { | ||
b: 1 | ||
}, | ||
c: { | ||
d: 2 | ||
} | ||
} | ||
var newObj = op.merge(obj, 'a', {b: 3}) | ||
expect(newObj).not.to.be.equal(obj) | ||
expect(newObj.a).not.to.be.equal(obj.a) | ||
expect(obj.a).to.be.eql({b: 1}) | ||
expect(newObj.c).to.be.equal(obj.c) | ||
expect(newObj.a.b).to.be.equal(3) | ||
}) | ||
it('should properly merge objects', function () { | ||
var obj = { | ||
a: { | ||
b: 1, | ||
c: { | ||
d: 2, | ||
e: 3 | ||
} | ||
} | ||
} | ||
var newObj = op.merge(obj, 'a', {c: {e: 4}}) | ||
expect(newObj).not.to.be.equal(obj) | ||
expect(newObj.a).not.to.be.equal(obj.a) | ||
expect(obj.a.b).to.be.eql(1) | ||
expect(newObj.a.c).to.be.eql({ | ||
d: 2, | ||
e: 4 | ||
}) | ||
}) | ||
it('should not merge arrays by default', function () { | ||
var obj = { | ||
a: { | ||
b: 1, | ||
c: { | ||
d: 2, | ||
e: [1] | ||
} | ||
} | ||
} | ||
var newObj = op.merge(obj, 'a', {c: {e: [2]}}) | ||
expect(obj.a.b).to.be.eql(1) | ||
expect(newObj.a.c).to.be.eql({ | ||
d: 2, | ||
e: [2] | ||
}) | ||
}) | ||
it('should not merge objects without a path', function () { | ||
var obj = { | ||
a: { | ||
b: 1, | ||
c: { | ||
d: 2, | ||
e: [1] | ||
} | ||
} | ||
} | ||
var newObj = op.merge(obj, null, {a: {c: {e: [2]}}}) | ||
expect(obj.a.b).to.be.eql(1) | ||
expect(newObj.a.c).to.be.eql({ | ||
d: 2, | ||
e: [2] | ||
}) | ||
}) | ||
}) | ||
describe('bind', function () { | ||
@@ -407,0 +491,0 @@ it('should execute all methods on the bound object', function () { |
Sorry, the diff of this file is not supported yet
36633
10
1003
208
1
9
+ Addeddeepmerge@^2.1.1
+ Addeddeepmerge@2.2.1(transitive)