Source: index.js

/**
 * @module nestedcrud
 */


/**
 * @desc Get a property from an object, whatever its depth in the object.
 *
 * @param {Object} object - The concerned object.
 * @param {String} path   - The path of the targeted property (i.e "companies.main.name").
 *
 * @return {*} Your targeted property, or `undefined`.
 */
function get(object, path) {
	var properties = path.split('.');
	var currentProperty = properties.shift();
	var tmpObject = object;

	while (properties.length > 0) {
		tmpObject = tmpObject[currentProperty];
		if (tmpObject === undefined) {
			return undefined;
		}
		currentProperty = properties.shift();
	}
	return tmpObject[currentProperty];
}

/**
 * @desc Set a given value to an object at a given path, regardless the existence of intermediate properties normally
 *       necessary for JavaScript.
 *
 * @param {Object}   object     - The concerned object.
 * @param {String}   path       - The concerned path.
 * @param {*}        value      - The value to set
 * @param {Boolean}  [override] - Tell whether we should override existing value (false) or not (true)
 *                                Default: true
 */
function set(object, path, value, override) {
	var properties = path.split('.');
	var tmpObject = object;

	while (properties.length > 0) {
		var currentProperty = properties.shift();

		// The info we need for each iteration
		var noMoreProperty = properties.length === 0;
		var endOfObject = !tmpObject[currentProperty];
		var canSet = (endOfObject || (!endOfObject && override));

		// Do we write the value and exit ?
		if (canSet && noMoreProperty) {
			tmpObject[currentProperty] = value;
			return;
		}

		// Do we need to create an empty object in order to progress ?
		if (!noMoreProperty && endOfObject) {
			tmpObject[currentProperty] = {};
		}

		// Move forward within the object
		tmpObject = tmpObject[currentProperty];
	}
}

/**
 * @desc Delete a given property of an object, taking care of deleting the empty objects possible generated by the
 * deletion too.
 *
 * @param  {Object}  object      - The concerned object.
 * @param  {String}  path        - The concerned path.
 * @param  {Boolean} [cleanup]   - Delete the empty objects generated by the deletion.
 *                                 Default: true
 */
function del(object, path, cleanup) {
	if (cleanup === undefined) {
		cleanup = true;
	}

	// The process for deleting a property from an object and an element from an array are not the same
	function deleteProperty(objectOrArray, key) {
		if (Array.isArray(objectOrArray)) {
			objectOrArray.splice(key, 1);
		} else {
			delete objectOrArray[key];
		}
	}

	// This object will work as a stack and store the path we will got through in order to delete recursively all empty
	// objects at the end of the process
	var deleters = [];

	var properties = path.split('.');
	var tmpObject = object;

	while (properties.length > 0) {
		var currentProperty = properties.shift();

		// Trying to delete an unexisting property ?
		if (tmpObject[currentProperty] === undefined) {
			break;
		}

		// Delete if last property
		if (properties.length === 0) {
			deleteProperty(tmpObject, currentProperty);
			break;
		}

		// Keep trace of the path we go through
		deleters.push({ base: tmpObject, property: currentProperty});

		// Move deeper into the object
		tmpObject = tmpObject[currentProperty];
	}

	// Delete all empty objects
	if (cleanup) {
		for (var i = 0, iMax = deleters.length; i < iMax ; i += 1) {
			var deleter = deleters.pop();
			var baseObject = deleter.base;
			var objectProperty = deleter.property;

			// Quit the process if there is at least one non-empty object
			if (Object.keys(baseObject[objectProperty]).length > 0) {
				break;
			}

			deleteProperty(baseObject, objectProperty);
		}
	}
}

// #####################################################################################################################

// -- EXPOSED FUNCTIONS --

exports.get = get;
exports.set = set;
exports.del = del;