UNPKG

dotty

Version:

Access properties of nested objects using dot-path notation

288 lines (234 loc) 6.48 kB
// // Dotty makes it easy to programmatically access arbitrarily nested objects and // their properties. // // // `object` is an object, `path` is the path to the property you want to check // for existence of. // // `path` can be provided as either a `"string.separated.with.dots"` or as // `["an", "array"]`. // // Returns `true` if the path can be completely resolved, `false` otherwise. // var exists = (module.exports.exists = function exists(object, path) { if (typeof path === "string") { path = path.split("."); } if (!(path instanceof Array) || path.length === 0) { return false; } path = path.slice(); var key = path.shift(); if (typeof object !== "object" || object === null) { return false; } if (path.length === 0) { return Object.hasOwnProperty.apply(object, [key]); } else { return exists(object[key], path); } }); // // These arguments are the same as those for `exists`. // // The return value, however, is the property you're trying to access, or // `undefined` if it can't be found. This means you won't be able to tell // the difference between an unresolved path and an undefined property, so you // should not use `get` to check for the existence of a property. Use `exists` // instead. // var get = (module.exports.get = function get(object, path) { if (typeof path === "string") { path = path.split("."); } if (!(path instanceof Array) || path.length === 0) { return; } path = path.slice(); var key = path.shift(); if (typeof object !== "object" || object === null) { return; } if (path.length === 0) { return object[key]; } if (path.length) { return get(object[key], path); } }); // // Arguments are similar to `exists` and `get`, with the exception that path // components are regexes with some special cases. If a path component is `"*"` // on its own, it'll be converted to `/.*/`. // // The return value is an array of values where the key path matches the // specified criterion. If none match, an empty array will be returned. // // If an action function is specified, that action will be applied to each // match. Action params are value, parent and key. // var search = (module.exports.search = function search(object, path, action) { if (typeof path === "string") { path = path.split("."); } if (!(path instanceof Array) || path.length === 0) { return; } path = path.slice(); var key = path.shift(); if (typeof object !== "object" || object === null) { return; } if (key === "*") { key = ".*"; } if (typeof key === "string") { key = new RegExp(key); } if (path.length === 0) { return Object.keys(object) .filter(key.test.bind(key)) .map(function (k) { var value = object[k]; if (action) { action(value, object, k); } return value; }); } else { return Array.prototype.concat.apply( [], Object.keys(object) .filter(key.test.bind(key)) .map(function (k) { return search(object[k], path, action); }) ); } }); // // Perform a search and remove the matched keys. // The return value is the same object argument with modifications. // var removeSearch = (module.exports.removeSearch = function removeSearch( object, path ) { search(object, path, function (value, object, key) { delete object[key]; }); return object; }); // // The first two arguments for `put` are the same as `exists` and `get`. // // The third argument is a value to `put` at the `path` of the `object`. // Objects in the middle will be created if they don't exist, or added to if // they do. If a value is encountered in the middle of the path that is *not* // an object, it will not be overwritten. // // The return value is `true` in the case that the value was `put` // successfully, or `false` otherwise. // var put = (module.exports.put = function put(object, path, value) { if (typeof path === "string") { path = path.split("."); } if (!(path instanceof Array) || path.length === 0) { return false; } path = path.slice(); var key = "" + path.shift(); if (typeof object !== "object" || object === null || key === "__proto__") { return false; } if (path.length === 0) { object[key] = value; } else { if (typeof object[key] === "undefined") { object[key] = {}; } if (typeof object[key] !== "object" || object[key] === null) { return false; } return put(object[key], path, value); } return true; }); // // `remove` is like `put` in reverse! // // The return value is `true` in the case that the value existed and was removed // successfully, or `false` otherwise. // var remove = (module.exports.remove = function remove(object, path, value) { if (typeof path === "string") { path = path.split("."); } if (!(path instanceof Array) || path.length === 0) { return false; } path = path.slice(); var key = path.shift(); if (typeof object !== "object" || object === null) { return false; } if (path.length === 0) { if (!Object.hasOwnProperty.call(object, key)) { return false; } delete object[key]; return true; } else { return remove(object[key], path, value); } }); // // `deepKeys` creates a list of all possible key paths for a given object. // // The return value is always an array, the members of which are paths in array // format. If you want them in dot-notation format, do something like this: // // ```js // dotty.deepKeys(obj).map(function(e) { // return e.join("."); // }); // ``` // // *Note: this will probably explode on recursive objects. Be careful.* // var deepKeys = (module.exports.deepKeys = function deepKeys( object, options, prefix ) { options = options || {}; if (typeof prefix === "undefined") { prefix = []; } var keys = []; for (var k in object) { if (!Object.hasOwnProperty.call(object, k)) { continue; } if (!options.leavesOnly || typeof object[k] !== "object") { keys.push(prefix.concat([k])); } if (typeof object[k] === "object" && object[k] !== null) { keys = keys.concat( deepKeys( object[k], { leavesOnly: options.leavesOnly }, prefix.concat([k]) ) ); } } if (options.asStrings) { keys = keys.map(function (e) { return e.join("."); }); } return keys; });