object-path-immutable
Version:
Modify deep object properties without modifying the original object (immutability). Works great with React and Redux.
651 lines (551 loc) • 17 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.objectPathImmutable = {}));
}(this, (function (exports) { 'use strict';
/*!
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/
function isObject(o) {
return Object.prototype.toString.call(o) === '[object Object]';
}
function isPlainObject(o) {
var ctor,prot;
if (isObject(o) === false) return false;
// If has modified constructor
ctor = o.constructor;
if (ctor === undefined) return true;
// If has modified prototype
prot = ctor.prototype;
if (isObject(prot) === false) return false;
// If constructor does not have an Object-specific method
if (prot.hasOwnProperty('isPrototypeOf') === false) {
return false;
}
// Most likely a plain Object
return true;
}
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var objectPath = createCommonjsModule(function (module) {
(function (root, factory) {
/*istanbul ignore next:cant test*/
{
module.exports = factory();
}
})(commonjsGlobal, function () {
var toStr = Object.prototype.toString;
function hasOwnProperty (obj, prop) {
if (obj == null) {
return false
}
//to handle objects with null prototypes (too edge case?)
return Object.prototype.hasOwnProperty.call(obj, prop)
}
function isEmpty (value) {
if (!value) {
return true
}
if (isArray(value) && value.length === 0) {
return true
} else if (typeof value !== 'string') {
for (var i in value) {
if (hasOwnProperty(value, i)) {
return false
}
}
return true
}
return false
}
function toString (type) {
return toStr.call(type)
}
function isObject (obj) {
return typeof obj === 'object' && toString(obj) === '[object Object]'
}
var isArray = Array.isArray || function (obj) {
/*istanbul ignore next:cant test*/
return toStr.call(obj) === '[object Array]'
};
function isBoolean (obj) {
return typeof obj === 'boolean' || toString(obj) === '[object Boolean]'
}
function getKey (key) {
var intKey = parseInt(key);
if (intKey.toString() === key) {
return intKey
}
return key
}
function factory (options) {
options = options || {};
var objectPath = function (obj) {
return Object.keys(objectPath).reduce(function (proxy, prop) {
if (prop === 'create') {
return proxy
}
/*istanbul ignore else*/
if (typeof objectPath[prop] === 'function') {
proxy[prop] = objectPath[prop].bind(objectPath, obj);
}
return proxy
}, {})
};
var hasShallowProperty;
if (options.includeInheritedProps) {
hasShallowProperty = function () {
return true
};
} else {
hasShallowProperty = function (obj, prop) {
return (typeof prop === 'number' && Array.isArray(obj)) || hasOwnProperty(obj, prop)
};
}
function getShallowProperty (obj, prop) {
if (hasShallowProperty(obj, prop)) {
return obj[prop]
}
}
var getShallowPropertySafely;
if (options.includeInheritedProps) {
getShallowPropertySafely = function (obj, currentPath) {
if (typeof currentPath !== 'string' && typeof currentPath !== 'number') {
currentPath = String(currentPath);
}
var currentValue = getShallowProperty(obj, currentPath);
if (currentPath === '__proto__' || currentPath === 'prototype' ||
(currentPath === 'constructor' && typeof currentValue === 'function')) {
throw new Error('For security reasons, object\'s magic properties cannot be set')
}
return currentValue
};
} else {
getShallowPropertySafely = function (obj, currentPath) {
return getShallowProperty(obj, currentPath)
};
}
function set (obj, path, value, doNotReplace) {
if (typeof path === 'number') {
path = [path];
}
if (!path || path.length === 0) {
return obj
}
if (typeof path === 'string') {
return set(obj, path.split('.').map(getKey), value, doNotReplace)
}
var currentPath = path[0];
var currentValue = getShallowPropertySafely(obj, currentPath);
if (path.length === 1) {
if (currentValue === void 0 || !doNotReplace) {
obj[currentPath] = value;
}
return currentValue
}
if (currentValue === void 0) {
//check if we assume an array
if (typeof path[1] === 'number') {
obj[currentPath] = [];
} else {
obj[currentPath] = {};
}
}
return set(obj[currentPath], path.slice(1), value, doNotReplace)
}
objectPath.has = function (obj, path) {
if (typeof path === 'number') {
path = [path];
} else if (typeof path === 'string') {
path = path.split('.');
}
if (!path || path.length === 0) {
return !!obj
}
for (var i = 0; i < path.length; i++) {
var j = getKey(path[i]);
if ((typeof j === 'number' && isArray(obj) && j < obj.length) ||
(options.includeInheritedProps ? (j in Object(obj)) : hasOwnProperty(obj, j))) {
obj = obj[j];
} else {
return false
}
}
return true
};
objectPath.ensureExists = function (obj, path, value) {
return set(obj, path, value, true)
};
objectPath.set = function (obj, path, value, doNotReplace) {
return set(obj, path, value, doNotReplace)
};
objectPath.insert = function (obj, path, value, at) {
var arr = objectPath.get(obj, path);
at = ~~at;
if (!isArray(arr)) {
arr = [];
objectPath.set(obj, path, arr);
}
arr.splice(at, 0, value);
};
objectPath.empty = function (obj, path) {
if (isEmpty(path)) {
return void 0
}
if (obj == null) {
return void 0
}
var value, i;
if (!(value = objectPath.get(obj, path))) {
return void 0
}
if (typeof value === 'string') {
return objectPath.set(obj, path, '')
} else if (isBoolean(value)) {
return objectPath.set(obj, path, false)
} else if (typeof value === 'number') {
return objectPath.set(obj, path, 0)
} else if (isArray(value)) {
value.length = 0;
} else if (isObject(value)) {
for (i in value) {
if (hasShallowProperty(value, i)) {
delete value[i];
}
}
} else {
return objectPath.set(obj, path, null)
}
};
objectPath.push = function (obj, path /*, values */) {
var arr = objectPath.get(obj, path);
if (!isArray(arr)) {
arr = [];
objectPath.set(obj, path, arr);
}
arr.push.apply(arr, Array.prototype.slice.call(arguments, 2));
};
objectPath.coalesce = function (obj, paths, defaultValue) {
var value;
for (var i = 0, len = paths.length; i < len; i++) {
if ((value = objectPath.get(obj, paths[i])) !== void 0) {
return value
}
}
return defaultValue
};
objectPath.get = function (obj, path, defaultValue) {
if (typeof path === 'number') {
path = [path];
}
if (!path || path.length === 0) {
return obj
}
if (obj == null) {
return defaultValue
}
if (typeof path === 'string') {
return objectPath.get(obj, path.split('.'), defaultValue)
}
var currentPath = getKey(path[0]);
var nextObj = getShallowPropertySafely(obj, currentPath);
if (nextObj === void 0) {
return defaultValue
}
if (path.length === 1) {
return nextObj
}
return objectPath.get(obj[currentPath], path.slice(1), defaultValue)
};
objectPath.del = function del (obj, path) {
if (typeof path === 'number') {
path = [path];
}
if (obj == null) {
return obj
}
if (isEmpty(path)) {
return obj
}
if (typeof path === 'string') {
return objectPath.del(obj, path.split('.'))
}
var currentPath = getKey(path[0]);
getShallowPropertySafely(obj, currentPath);
if (!hasShallowProperty(obj, currentPath)) {
return obj
}
if (path.length === 1) {
if (isArray(obj)) {
obj.splice(currentPath, 1);
} else {
delete obj[currentPath];
}
} else {
return objectPath.del(obj[currentPath], path.slice(1))
}
return obj
};
return objectPath
}
var mod = factory();
mod.create = factory;
mod.withInheritedProps = factory({includeInheritedProps: true});
return mod
});
});
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
}
}
return true
}
return false
}
function isNumber (value) {
return typeof value === 'number'
}
function isString (obj) {
return typeof obj === 'string'
}
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];
}
}
return target
}
function getKey (key) {
var intKey = parseInt(key);
if (intKey.toString() === key) {
return intKey
}
return key
}
function clone (obj, createIfEmpty, assumeArray) {
if (obj == null) {
if (createIfEmpty) {
if (assumeArray) {
return []
}
return {}
}
return obj
} else if (isArray(obj)) {
return obj.slice()
}
return assignToObj({}, obj)
}
function _deepMerge (dest, src) {
if (dest !== src && isPlainObject(dest) && isPlainObject(src)) {
var merged = {};
for (var key in dest) {
if (_hasOwnProperty.call(dest, key)) {
if (_hasOwnProperty.call(src, key)) {
merged[key] = _deepMerge(dest[key], src[key]);
} else {
merged[key] = dest[key];
}
}
}
for (key in src) {
if (_hasOwnProperty.call(src, key)) {
merged[key] = _deepMerge(dest[key], src[key]);
}
}
return merged
}
return src
}
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 (!dest || dest === src) {
dest = clone(src, true, isNumber(currentPath));
}
if (path.length === 1) {
return changeCallback(dest, currentPath)
}
if (src != null) {
src = src[currentPath];
}
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
})
};
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.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) {
if (!isArray(clonedObj[finalPath])) {
clonedObj[finalPath] = values;
} else {
clonedObj[finalPath] = clonedObj[finalPath].concat(values);
}
return clonedObj
})
};
api.insert = function insert (dest, src, path, value, at) {
at = ~~at;
if (isEmpty(path)) {
if (!isArray(src)) {
return [value]
}
var first = src.slice(0, at);
first.push(value);
return first.concat(src.slice(at))
}
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
})
};
api.del = function del (dest, src, path) {
if (isEmpty(path)) {
return undefined
}
return _changeImmutable(dest, src, path, function (clonedObj, finalPath) {
if (Array.isArray(clonedObj)) {
if (clonedObj[finalPath] !== undefined) {
clonedObj.splice(finalPath, 1);
}
} else {
if (_hasOwnProperty.call(clonedObj, finalPath)) {
delete clonedObj[finalPath];
}
}
return clonedObj
})
};
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
})
};
api.merge = function assign (dest, src, path, source) {
if (isEmpty(path)) {
if (isEmpty(source)) {
return src
}
return _deepMerge(src, source)
}
return _changeImmutable(dest, src, path, function (clonedObj, finalPath) {
source = Object(source);
clonedObj[finalPath] = _deepMerge(clonedObj[finalPath], source);
return clonedObj
})
};
function wrap (src) {
var dest = src;
var committed = false;
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));
if (committed) {
throw new Error('Cannot call ' + prop + ' after `value`')
}
dest = api[prop].apply(null, args);
return transaction
};
}
return proxy
}, {});
transaction.value = function () {
committed = true;
return dest
};
return transaction
}
var set = api.set.bind(null, null);
var update = api.update.bind(null, null);
var push = api.push.bind(null, null);
var insert = api.insert.bind(null, null);
var del = api.del.bind(null, null);
var assign = api.assign.bind(null, null);
var merge = api.merge.bind(null, null);
var get = objectPath.get;
exports.assign = assign;
exports.del = del;
exports.get = get;
exports.insert = insert;
exports.merge = merge;
exports.push = push;
exports.set = set;
exports.update = update;
exports.wrap = wrap;
Object.defineProperty(exports, '__esModule', { value: true });
})));