/*
_ ____ _
/ \ _ __ _ _ / ___|___ __| | ___
/ _ \ | '_ \| | | | | | / _ \ / _` |/ _ \
/ ___ \| | | | |_| | | |__| (_) | (_| | __/
/_/ \_\_| |_|\__, | \____\___/ \__,_|\___|
|___/
javascript-object-paraphernalia
*/
/**
* @description A bare minimum set of utilities for working with objects
* @constructor
*/
function Obj() {}
/**
* @description Perform a deep clone of two objects
*
* @param {object} objectToClone the object to be cloned
* @returns {object} A clone of the objectToClone
*
* @tutorial obj.clone
*/
Obj.prototype.clone = function(objectToClone) {
return this._combineObjects({}, objectToClone, true, false);
};
/**
* @description Mutates the first object by deeply merging the properties of the second object into the first object,
* overwriting the properties already set on the first specifically passing properties by reference. If the desire
* isn't to mutate the first object, use clone(first).
*
* @param {object} first the first and mutable object
* @param {object} second the second object
*
* @returns {object} The first object with the properties from the second object merged into the properties of the first
*
* @tutorial obj.merge
*/
Obj.prototype.merge = function(first, second) {
return this._combineObjects(first, second);
};
/**
* @description Mutates the first object by deeply applying the properties from the second object onto the properties of
* the first object only if the properties already exist on the first object. This overwrites existing properties but
* ignores new ones. If the desire isn't to mutate the first object, use clone(first).
*
* @param {object} first The first and mutable object
* @param {object} second The second object
*
* @returns {object} The first object with the second objects properties applied to it
*
* @tutorial obj.apply
*/
Obj.prototype.apply = function(first, second) {
return this._combineObjects(first, second, false);
};
/**
* @description behaves identically to apply() apart from when the second value is undefined defaults will return the
* first value. No error will be thrown
*
* @param {object} first The first and mutable object
* @param {object} second The second object
*
* @returns {object} The first object with the second objects properties applied to it
*
* @tutorial obj.apply
*/
Obj.prototype.defaults = function(first, second) {
if (second === undefined) {
return first;
}
return this._combineObjects(first, second, false);
};
/**
* @description Returns true if the specified object is of type typeAssertion or it's constructor name is of the type
* specified by the typeAssertion
*
* @param {*} object Any value with a prototype
* @param {String} typeAssertion A string representing the name of the expected type
*
* @returns {boolean} True|False based on the truthiness of the typeAssertion
*
* @tutorial obj.is
*/
Obj.prototype.is = function(object, typeAssertion) {
if (object === undefined) {
return typeAssertion === 'undefined' || typeAssertion === undefined
}
if (Object.prototype.toString.call(object) === '[object Object]' &&
object.constructor.name === typeAssertion) {
return true
}
return Object.prototype.toString.call(object) === '[object ' +
typeAssertion[0].toUpperCase() + typeAssertion.substr(1).toLowerCase() + ']'
};
/**
* @description Combines second object with first object adhering th the flagged rules:
*
* Flags:
* allowNew (true|false) -
* with: any property from the second object will be merged into the first object.
* without: only properties that already exist on the first object will be updated to contain the values of the same
* properties on the second object.
*
* allowReferences (true|false) -
* with: any property from the second object that is an object will be referenced on the first object.
* without: any property from the second object that is an object will be a new unique object on the first object.
*
* @param {object} first The first and mutable object to be combined
* @param {object} second The second object to be combined
* @param {boolean} [allowNew] Are new properties allowed to be created on first
* @param {boolean} [allowReferences] Are the properties combined onto first references or clones of the second properties
*
* @returns {object}
*
* @private
*/
Obj.prototype._combineObjects = function(first, second, allowNew, allowReferences) {
if (!this.is(first, 'Object')) {
throw new Error("First value must be an Object but was: " + first);
}
if (second === undefined) {
second = {}
}
if (!this.is(second, 'Object') && second.constructor !== Array) {
throw new Error("Second value must be an Object but was: " + second);
}
if (allowNew === undefined) {
allowNew = true;
}
if (allowReferences === undefined) {
allowReferences = true;
}
for (var property in second) {
if (!allowNew && first[property] === undefined) {
continue;
}
if (second[property] && second[property].constructor == Object) {
if (first[property] === undefined) {
first[property] = !allowReferences ? {} : second[property];
}
first[property] = this._combineObjects(first[property], second[property], allowNew, allowReferences);
} else {
first[property] = second[property];
}
}
return first;
}
module.exports = new Obj();