├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── SECURITY.md ├── benchmark.js ├── bower.json ├── component.json ├── index.js ├── package-lock.json ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .settings 3 | .idea 4 | npm-debug.log 5 | generated 6 | coverage 7 | .DS_Store 8 | .nyc_output 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "10" 5 | - "12" 6 | - "14" 7 | after_script: NODE_ENV=test istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mario Casciaro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | object-path 4 | =========== 5 | 6 | Access deep properties using a path 7 | 8 | [![NPM version](https://badge.fury.io/js/object-path.png)](http://badge.fury.io/js/object-path) 9 | [![Build Status](https://travis-ci.org/mariocasciaro/object-path.png)](https://travis-ci.org/mariocasciaro/object-path) 10 | [![Coverage Status](https://coveralls.io/repos/mariocasciaro/object-path/badge.png)](https://coveralls.io/r/mariocasciaro/object-path) 11 | [![devDependency Status](https://david-dm.org/mariocasciaro/object-path/dev-status.svg)](https://david-dm.org/mariocasciaro/object-path#info=devDependencies) 12 | ![Downloads](http://img.shields.io/npm/dm/object-path.svg) 13 | 14 | ## Changelog 15 | 16 | ### 0.11.8 17 | 18 | * **SECURITY FIX**. Fix a prototype pollution vulnerability in the `del()`, `empty()`, `push()`, `insert()` functions when using the "inherited props" mode (e.g. when a new `object-path` instance is created with the `includeInheritedProps` option set to `true` or when using the `withInheritedProps` default instance. To help with preventing this type of vulnerability in the client code, also the `get()` function will now throw an exception if an object's magic properties are accessed. The vulnerability does not exist in the default instance exposed by object path (e.g `objectPath.del()`) if using version >= `0.11.0`. 19 | 20 | ### 0.11.6 21 | 22 | * **SECURITY FIX**. Fix a circumvention of the security fix released in 0.11.5 when non-string/non-numeric values are used in the path (e.g. `op.withInheritedProps.set({}, [['__proto__'], 'polluted'], true)`) 23 | 24 | ### 0.11.5 25 | 26 | * **SECURITY FIX**. Fix a prototype pollution vulnerability in the `set()` function when using the "inherited props" mode (e.g. when a new `object-path` instance is created with the `includeInheritedProps` option set to `true` or when using the `withInheritedProps` default instance. The vulnerability does not exist in the default instance exposed by object path (e.g `objectPath.set()`) if using version >= `0.11.0`. 27 | 28 | ### 0.11.0 29 | 30 | * Introduce ability to specify options and create new instances of `object-path` 31 | * Introduce option to control the way `object-path` deals with inherited properties (`includeInheritedProps`) 32 | * New default `object-path` instance already configured to handle not-own object properties (`withInheritedProps`) 33 | 34 | ### 0.10.0 35 | 36 | * Improved performance of `get`, `set`, and `push` by 2x-3x 37 | * Introduced a benchmarking test suite 38 | * **BREAKING CHANGE**: `del`, `empty`, `set` will not affect not-own object's properties (made them consistent with the other methods) 39 | 40 | ## Install 41 | 42 | ### Node.js 43 | 44 | ``` 45 | npm install object-path --save 46 | ``` 47 | 48 | ### Bower 49 | 50 | ``` 51 | bower install object-path --save 52 | ``` 53 | 54 | ### Typescript typings 55 | 56 | ``` 57 | typings install --save dt~object-path 58 | ``` 59 | 60 | ## Usage 61 | 62 | ```javascript 63 | 64 | var obj = { 65 | a: { 66 | b: "d", 67 | c: ["e", "f"], 68 | '\u1200': 'unicode key', 69 | 'dot.dot': 'key' 70 | } 71 | }; 72 | 73 | var objectPath = require("object-path"); 74 | 75 | //get deep property 76 | objectPath.get(obj, "a.b"); //returns "d" 77 | objectPath.get(obj, ["a", "dot.dot"]); //returns "key" 78 | objectPath.get(obj, 'a.\u1200'); //returns "unicode key" 79 | 80 | //get the first non-undefined value 81 | objectPath.coalesce(obj, ['a.z', 'a.d', ['a','b']], 'default'); 82 | 83 | //empty a given path (but do not delete it) depending on their type,so it retains reference to objects and arrays. 84 | //functions that are not inherited from prototype are set to null. 85 | //object instances are considered objects and just own property names are deleted 86 | objectPath.empty(obj, 'a.b'); // obj.a.b is now '' 87 | objectPath.empty(obj, 'a.c'); // obj.a.c is now [] 88 | objectPath.empty(obj, 'a'); // obj.a is now {} 89 | 90 | //works also with arrays 91 | objectPath.get(obj, "a.c.1"); //returns "f" 92 | objectPath.get(obj, ["a","c","1"]); //returns "f" 93 | 94 | //can return a default value with get 95 | objectPath.get(obj, ["a.c.b"], "DEFAULT"); //returns "DEFAULT", since a.c.b path doesn't exists, if omitted, returns undefined 96 | 97 | //set 98 | objectPath.set(obj, "a.h", "m"); // or objectPath.set(obj, ["a","h"], "m"); 99 | objectPath.get(obj, "a.h"); //returns "m" 100 | 101 | //set will create intermediate object/arrays 102 | objectPath.set(obj, "a.j.0.f", "m"); 103 | 104 | //will insert values in array 105 | objectPath.insert(obj, "a.c", "m", 1); // obj.a.c = ["e", "m", "f"] 106 | 107 | //push into arrays (and create intermediate objects/arrays) 108 | objectPath.push(obj, "a.k", "o"); 109 | 110 | //ensure a path exists (if it doesn't, set the default value you provide) 111 | objectPath.ensureExists(obj, "a.k.1", "DEFAULT"); 112 | var oldVal = objectPath.ensureExists(obj, "a.b", "DEFAULT"); // oldval === "d" 113 | 114 | //deletes a path 115 | objectPath.del(obj, "a.b"); // obj.a.b is now undefined 116 | objectPath.del(obj, ["a","c",0]); // obj.a.c is now ['f'] 117 | 118 | //tests path existence 119 | objectPath.has(obj, "a.b"); // true 120 | objectPath.has(obj, ["a","d"]); // false 121 | 122 | //bind object 123 | var model = objectPath({ 124 | a: { 125 | b: "d", 126 | c: ["e", "f"] 127 | } 128 | }); 129 | 130 | //now any method from above is supported directly w/o passing an object 131 | model.get("a.b"); //returns "d" 132 | model.get(["a.c.b"], "DEFAULT"); //returns "DEFAULT" 133 | model.del("a.b"); // obj.a.b is now undefined 134 | model.has("a.b"); // false 135 | 136 | ``` 137 | ### How `object-path` deals with inherited properties 138 | 139 | By default `object-path` will only access an object's own properties. Look at the following example: 140 | 141 | ```javascript 142 | var proto = { 143 | notOwn: {prop: 'a'} 144 | } 145 | var obj = Object.create(proto); 146 | 147 | //This will return undefined (or the default value you specified), because notOwn is 148 | //an inherited property 149 | objectPath.get(obj, 'notOwn.prop'); 150 | 151 | //This will set the property on the obj instance and not the prototype. 152 | //In other words proto.notOwn.prop === 'a' and obj.notOwn.prop === 'b' 153 | objectPath.set(obj, 'notOwn.prop', 'b'); 154 | ``` 155 | To configure `object-path` to also deal with inherited properties, you need to create a new instance and specify 156 | the `includeInheritedProps = true` in the options object: 157 | 158 | ```javascript 159 | var objectPath = require("object-path"); 160 | var objectPathWithInheritedProps = objectPath.create({includeInheritedProps: true}) 161 | ``` 162 | 163 | Alternatively, `object-path` exposes an instance already configured to handle inherited properties (`objectPath.withInheritedProps`): 164 | ```javascript 165 | var objectPath = require("object-path"); 166 | var objectPathWithInheritedProps = objectPath.withInheritedProps 167 | ``` 168 | 169 | Once you have the new instance, you can access inherited properties as you access other properties: 170 | ```javascript 171 | var proto = { 172 | notOwn: {prop: 'a'} 173 | } 174 | var obj = Object.create(proto); 175 | 176 | //This will return 'a' 177 | objectPath.withInheritedProps.get(obj, 'notOwn.prop'); 178 | 179 | //This will set proto.notOwn.prop to 'b' 180 | objectPath.set(obj, 'notOwn.prop', 'b'); 181 | ``` 182 | 183 | **NOTE**: For security reasons `object-path` will throw an exception when trying to access an object's magic properties (e.g. `__proto__`, `constructor`) when in "inherited props" mode. 184 | 185 | ### Immutability 186 | 187 | If you are looking for an *immutable* alternative of this library, you can take a look at: [object-path-immutable](https://github.com/mariocasciaro/object-path-immutable) 188 | 189 | 190 | ### Credits 191 | 192 | * [Mario Casciaro](https://github.com/mariocasciaro) - Author 193 | * [Paulo Cesar](https://github.com/pocesar) - Major contributor 194 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | Reporting a security issue 3 | =========== 4 | 5 | Please report any suspected security vulnerabilities responsibly to protect the users of this package. Try not share them publicly before the issue is confirmed and a fix is produced. 6 | 7 | Send us an email at report @ mario.fyi to privately report any security vulnerability to us. 8 | -------------------------------------------------------------------------------- /benchmark.js: -------------------------------------------------------------------------------- 1 | var Benchpress = require('@mariocasciaro/benchpress') 2 | var benchmark = new Benchpress() 3 | var op = require('./') 4 | 5 | var testObj = { 6 | level1_a: { 7 | level2_a: { 8 | level3_a: { 9 | level4_a: { 10 | } 11 | } 12 | } 13 | } 14 | } 15 | 16 | var testObj2 17 | 18 | benchmark 19 | .add('get existing', { 20 | iterations: 100000, 21 | fn: function() { 22 | op.get(testObj, ['level1_a', 'level2_a', 'level3_a', 'level4_a']) 23 | } 24 | }) 25 | .add('get non-existing', { 26 | iterations: 100000, 27 | fn: function() { 28 | op.get(testObj, ['level5_a']) 29 | } 30 | }) 31 | .add('push', { 32 | iterations: 100000, 33 | fn: function() { 34 | op.push(testObj, ['level1_a', 'level2_a', 'level3_a', 'level4_a', 'level5_a'], 'val') 35 | } 36 | }) 37 | .add('set non existing', { 38 | iterations: 100000, 39 | fn: function() { 40 | op.set(testObj2, ['level1_a', 'level2_b', 'level3_b', 'level4_b', 'level5_b'], 'val') 41 | }, 42 | beforeEach: function() { 43 | testObj2 = {} 44 | } 45 | }) 46 | .add('set existing', { 47 | iterations: 100000, 48 | fn: function() { 49 | op.set(testObj, ['level1_a', 'level2_a', 'level3_a', 'level4_a', 'level5_b'], 'val') 50 | } 51 | }) 52 | .run() 53 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "object-path", 3 | "main": "index.js", 4 | "keywords": [ 5 | "deep", 6 | "path", 7 | "access", 8 | "bean", 9 | "object" 10 | ], 11 | "ignore" : [ 12 | "test.js", 13 | "coverage", 14 | "*.yml" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "object-path", 3 | "description": "Access deep properties using a path", 4 | "version": "0.9.2", 5 | "author": { 6 | "name": "Mario Casciaro" 7 | }, 8 | "homepage": "https://github.com/mariocasciaro/object-path", 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/mariocasciaro/object-path.git" 12 | }, 13 | "main": "index.js", 14 | "scripts": ["index.js"], 15 | "keywords": [ 16 | "deep", 17 | "path", 18 | "access", 19 | "bean" 20 | ], 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | 'use strict' 3 | 4 | /*istanbul ignore next:cant test*/ 5 | if (typeof module === 'object' && typeof module.exports === 'object') { 6 | module.exports = factory() 7 | } else if (typeof define === 'function' && define.amd) { 8 | // AMD. Register as an anonymous module. 9 | define([], factory) 10 | } else { 11 | // Browser globals 12 | root.objectPath = factory() 13 | } 14 | })(this, function () { 15 | 'use strict' 16 | 17 | var toStr = Object.prototype.toString 18 | 19 | function hasOwnProperty (obj, prop) { 20 | if (obj == null) { 21 | return false 22 | } 23 | //to handle objects with null prototypes (too edge case?) 24 | return Object.prototype.hasOwnProperty.call(obj, prop) 25 | } 26 | 27 | function isEmpty (value) { 28 | if (!value) { 29 | return true 30 | } 31 | if (isArray(value) && value.length === 0) { 32 | return true 33 | } else if (typeof value !== 'string') { 34 | for (var i in value) { 35 | if (hasOwnProperty(value, i)) { 36 | return false 37 | } 38 | } 39 | return true 40 | } 41 | return false 42 | } 43 | 44 | function toString (type) { 45 | return toStr.call(type) 46 | } 47 | 48 | function isObject (obj) { 49 | return typeof obj === 'object' && toString(obj) === '[object Object]' 50 | } 51 | 52 | var isArray = Array.isArray || function (obj) { 53 | /*istanbul ignore next:cant test*/ 54 | return toStr.call(obj) === '[object Array]' 55 | } 56 | 57 | function isBoolean (obj) { 58 | return typeof obj === 'boolean' || toString(obj) === '[object Boolean]' 59 | } 60 | 61 | function getKey (key) { 62 | var intKey = parseInt(key) 63 | if (intKey.toString() === key) { 64 | return intKey 65 | } 66 | return key 67 | } 68 | 69 | function factory (options) { 70 | options = options || {} 71 | 72 | var objectPath = function (obj) { 73 | return Object.keys(objectPath).reduce(function (proxy, prop) { 74 | if (prop === 'create') { 75 | return proxy 76 | } 77 | 78 | /*istanbul ignore else*/ 79 | if (typeof objectPath[prop] === 'function') { 80 | proxy[prop] = objectPath[prop].bind(objectPath, obj) 81 | } 82 | 83 | return proxy 84 | }, {}) 85 | } 86 | 87 | var hasShallowProperty 88 | if (options.includeInheritedProps) { 89 | hasShallowProperty = function () { 90 | return true 91 | } 92 | } else { 93 | hasShallowProperty = function (obj, prop) { 94 | return (typeof prop === 'number' && Array.isArray(obj)) || hasOwnProperty(obj, prop) 95 | } 96 | } 97 | 98 | function getShallowProperty (obj, prop) { 99 | if (hasShallowProperty(obj, prop)) { 100 | return obj[prop] 101 | } 102 | } 103 | 104 | var getShallowPropertySafely 105 | if (options.includeInheritedProps) { 106 | getShallowPropertySafely = function (obj, currentPath) { 107 | if (typeof currentPath !== 'string' && typeof currentPath !== 'number') { 108 | currentPath = String(currentPath) 109 | } 110 | var currentValue = getShallowProperty(obj, currentPath) 111 | if (currentPath === '__proto__' || currentPath === 'prototype' || 112 | (currentPath === 'constructor' && typeof currentValue === 'function')) { 113 | throw new Error('For security reasons, object\'s magic properties cannot be set') 114 | } 115 | return currentValue 116 | } 117 | } else { 118 | getShallowPropertySafely = function (obj, currentPath) { 119 | return getShallowProperty(obj, currentPath) 120 | } 121 | } 122 | 123 | function set (obj, path, value, doNotReplace) { 124 | if (typeof path === 'number') { 125 | path = [path] 126 | } 127 | if (!path || path.length === 0) { 128 | return obj 129 | } 130 | if (typeof path === 'string') { 131 | return set(obj, path.split('.').map(getKey), value, doNotReplace) 132 | } 133 | var currentPath = path[0] 134 | var currentValue = getShallowPropertySafely(obj, currentPath) 135 | if (path.length === 1) { 136 | if (currentValue === void 0 || !doNotReplace) { 137 | obj[currentPath] = value 138 | } 139 | return currentValue 140 | } 141 | 142 | if (currentValue === void 0) { 143 | //check if we assume an array 144 | if (typeof path[1] === 'number') { 145 | obj[currentPath] = [] 146 | } else { 147 | obj[currentPath] = {} 148 | } 149 | } 150 | 151 | return set(obj[currentPath], path.slice(1), value, doNotReplace) 152 | } 153 | 154 | objectPath.has = function (obj, path) { 155 | if (typeof path === 'number') { 156 | path = [path] 157 | } else if (typeof path === 'string') { 158 | path = path.split('.') 159 | } 160 | 161 | if (!path || path.length === 0) { 162 | return !!obj 163 | } 164 | 165 | for (var i = 0; i < path.length; i++) { 166 | var j = getKey(path[i]) 167 | 168 | if ((typeof j === 'number' && isArray(obj) && j < obj.length) || 169 | (options.includeInheritedProps ? (j in Object(obj)) : hasOwnProperty(obj, j))) { 170 | obj = obj[j] 171 | } else { 172 | return false 173 | } 174 | } 175 | 176 | return true 177 | } 178 | 179 | objectPath.ensureExists = function (obj, path, value) { 180 | return set(obj, path, value, true) 181 | } 182 | 183 | objectPath.set = function (obj, path, value, doNotReplace) { 184 | return set(obj, path, value, doNotReplace) 185 | } 186 | 187 | objectPath.insert = function (obj, path, value, at) { 188 | var arr = objectPath.get(obj, path) 189 | at = ~~at 190 | if (!isArray(arr)) { 191 | arr = [] 192 | objectPath.set(obj, path, arr) 193 | } 194 | arr.splice(at, 0, value) 195 | } 196 | 197 | objectPath.empty = function (obj, path) { 198 | if (isEmpty(path)) { 199 | return void 0 200 | } 201 | if (obj == null) { 202 | return void 0 203 | } 204 | 205 | var value, i 206 | if (!(value = objectPath.get(obj, path))) { 207 | return void 0 208 | } 209 | 210 | if (typeof value === 'string') { 211 | return objectPath.set(obj, path, '') 212 | } else if (isBoolean(value)) { 213 | return objectPath.set(obj, path, false) 214 | } else if (typeof value === 'number') { 215 | return objectPath.set(obj, path, 0) 216 | } else if (isArray(value)) { 217 | value.length = 0 218 | } else if (isObject(value)) { 219 | for (i in value) { 220 | if (hasShallowProperty(value, i)) { 221 | delete value[i] 222 | } 223 | } 224 | } else { 225 | return objectPath.set(obj, path, null) 226 | } 227 | } 228 | 229 | objectPath.push = function (obj, path /*, values */) { 230 | var arr = objectPath.get(obj, path) 231 | if (!isArray(arr)) { 232 | arr = [] 233 | objectPath.set(obj, path, arr) 234 | } 235 | 236 | arr.push.apply(arr, Array.prototype.slice.call(arguments, 2)) 237 | } 238 | 239 | objectPath.coalesce = function (obj, paths, defaultValue) { 240 | var value 241 | 242 | for (var i = 0, len = paths.length; i < len; i++) { 243 | if ((value = objectPath.get(obj, paths[i])) !== void 0) { 244 | return value 245 | } 246 | } 247 | 248 | return defaultValue 249 | } 250 | 251 | objectPath.get = function (obj, path, defaultValue) { 252 | if (typeof path === 'number') { 253 | path = [path] 254 | } 255 | if (!path || path.length === 0) { 256 | return obj 257 | } 258 | if (obj == null) { 259 | return defaultValue 260 | } 261 | if (typeof path === 'string') { 262 | return objectPath.get(obj, path.split('.'), defaultValue) 263 | } 264 | 265 | var currentPath = getKey(path[0]) 266 | var nextObj = getShallowPropertySafely(obj, currentPath) 267 | if (nextObj === void 0) { 268 | return defaultValue 269 | } 270 | 271 | if (path.length === 1) { 272 | return nextObj 273 | } 274 | 275 | return objectPath.get(obj[currentPath], path.slice(1), defaultValue) 276 | } 277 | 278 | objectPath.del = function del (obj, path) { 279 | if (typeof path === 'number') { 280 | path = [path] 281 | } 282 | 283 | if (obj == null) { 284 | return obj 285 | } 286 | 287 | if (isEmpty(path)) { 288 | return obj 289 | } 290 | if (typeof path === 'string') { 291 | return objectPath.del(obj, path.split('.')) 292 | } 293 | 294 | var currentPath = getKey(path[0]) 295 | getShallowPropertySafely(obj, currentPath) 296 | if (!hasShallowProperty(obj, currentPath)) { 297 | return obj 298 | } 299 | 300 | if (path.length === 1) { 301 | if (isArray(obj)) { 302 | obj.splice(currentPath, 1) 303 | } else { 304 | delete obj[currentPath] 305 | } 306 | } else { 307 | return objectPath.del(obj[currentPath], path.slice(1)) 308 | } 309 | 310 | return obj 311 | } 312 | 313 | return objectPath 314 | } 315 | 316 | var mod = factory() 317 | mod.create = factory 318 | mod.withInheritedProps = factory({includeInheritedProps: true}) 319 | return mod 320 | }) 321 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "object-path", 3 | "description": "Access deep object properties using a path", 4 | "version": "0.11.8", 5 | "author": { 6 | "name": "Mario Casciaro" 7 | }, 8 | "homepage": "https://github.com/mariocasciaro/object-path", 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/mariocasciaro/object-path.git" 12 | }, 13 | "engines": { 14 | "node": ">= 10.12.0" 15 | }, 16 | "devDependencies": { 17 | "@mariocasciaro/benchpress": "^0.1.3", 18 | "chai": "^4.3.4", 19 | "coveralls": "^3.1.1", 20 | "mocha": "^9.1.0", 21 | "mocha-lcov-reporter": "^1.3.0", 22 | "nyc": "^15.1.0" 23 | }, 24 | "scripts": { 25 | "test": "mocha test.js", 26 | "coveralls": "nyc npm test && nyc report --reporter=text-lcov | coveralls", 27 | "coverage": "nyc npm test", 28 | "benchmark": "node benchmark.js" 29 | }, 30 | "keywords": [ 31 | "deep", 32 | "path", 33 | "access", 34 | "bean", 35 | "get", 36 | "property", 37 | "dot", 38 | "prop", 39 | "object", 40 | "obj", 41 | "notation", 42 | "segment", 43 | "value", 44 | "nested", 45 | "key" 46 | ], 47 | "license": "MIT" 48 | } 49 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var expect = require('chai').expect, 3 | objectPath = require('./index.js') 4 | 5 | 6 | function getTestObj () { 7 | return { 8 | a: 'b', 9 | b: { 10 | c: [], 11 | d: ['a', 'b'], 12 | e: [{}, {f: 'g'}], 13 | f: 'i' 14 | } 15 | } 16 | } 17 | 18 | describe('get', function () { 19 | it('should return the value using unicode key', function () { 20 | var obj = { 21 | '15\u00f8C': { 22 | '3\u0111': 1 23 | } 24 | } 25 | expect(objectPath.get(obj, '15\u00f8C.3\u0111')).to.be.equal(1) 26 | expect(objectPath.get(obj, ['15\u00f8C', '3\u0111'])).to.be.equal(1) 27 | }) 28 | 29 | it('should return the value using dot in key', function () { 30 | var obj = { 31 | 'a.b': { 32 | 'looks.like': 1 33 | } 34 | } 35 | expect(objectPath.get(obj, 'a.b.looks.like')).to.be.equal(void 0) 36 | expect(objectPath.get(obj, ['a.b', 'looks.like'])).to.be.equal(1) 37 | }) 38 | 39 | it('should return the value under shallow object', function () { 40 | var obj = getTestObj() 41 | expect(objectPath.get(obj, 'a')).to.be.equal('b') 42 | expect(objectPath.get(obj, ['a'])).to.be.equal('b') 43 | }) 44 | 45 | it('should work with number path', function () { 46 | var obj = getTestObj() 47 | expect(objectPath.get(obj.b.d, 0)).to.be.equal('a') 48 | expect(objectPath.get(obj.b, 0)).to.be.equal(void 0) 49 | }) 50 | 51 | it('should return the value under deep object', function () { 52 | var obj = getTestObj() 53 | expect(objectPath.get(obj, 'b.f')).to.be.equal('i') 54 | expect(objectPath.get(obj, ['b', 'f'])).to.be.equal('i') 55 | }) 56 | 57 | it('should return the value under array', function () { 58 | var obj = getTestObj() 59 | expect(objectPath.get(obj, 'b.d.0')).to.be.equal('a') 60 | expect(objectPath.get(obj, ['b', 'd', 0])).to.be.equal('a') 61 | }) 62 | 63 | it('should return the value under array deep', function () { 64 | var obj = getTestObj() 65 | expect(objectPath.get(obj, 'b.e.1.f')).to.be.equal('g') 66 | expect(objectPath.get(obj, ['b', 'e', 1, 'f'])).to.be.equal('g') 67 | }) 68 | 69 | it('should return undefined for missing values under object', function () { 70 | var obj = getTestObj() 71 | expect(objectPath.get(obj, 'a.b')).to.not.exist 72 | expect(objectPath.get(obj, ['a', 'b'])).to.not.exist 73 | }) 74 | 75 | it('should return undefined for missing values under array', function () { 76 | var obj = getTestObj() 77 | expect(objectPath.get(obj, 'b.d.5')).to.not.exist 78 | expect(objectPath.get(obj, ['b', 'd', '5'])).to.not.exist 79 | }) 80 | 81 | it('should return the value under integer-like key', function () { 82 | var obj = {'1a': 'foo'} 83 | expect(objectPath.get(obj, '1a')).to.be.equal('foo') 84 | expect(objectPath.get(obj, ['1a'])).to.be.equal('foo') 85 | }) 86 | 87 | it('should return the default value when the key doesnt exist', function () { 88 | var obj = {'1a': 'foo'} 89 | expect(objectPath.get(obj, '1b', null)).to.be.equal(null) 90 | expect(objectPath.get(obj, ['1b'], null)).to.be.equal(null) 91 | }) 92 | 93 | it('should return the default value when path is empty', function () { 94 | var obj = {'1a': 'foo'} 95 | expect(objectPath.get(obj, '', null)).to.be.deep.equal({'1a': 'foo'}) 96 | expect(objectPath.get(obj, [])).to.be.deep.equal({'1a': 'foo'}) 97 | expect(objectPath.get({}, ['1'])).to.be.equal(undefined) 98 | }) 99 | 100 | it('should return the default value when object is null or undefined', function () { 101 | expect(objectPath.get(null, 'test', 'a')).to.be.deep.equal('a') 102 | expect(objectPath.get(undefined, 'test', 'a')).to.be.deep.equal('a') 103 | }) 104 | 105 | it( 106 | 'should not fail on an object with a null prototype', 107 | function assertSuccessForObjWithNullProto () { 108 | var foo = 'FOO' 109 | var objWithNullProto = Object.create(null) 110 | objWithNullProto.foo = foo 111 | expect(objectPath.get(objWithNullProto, 'foo')).to.equal(foo) 112 | } 113 | ) 114 | 115 | it('should skip non own properties', function () { 116 | var Base = function (enabled) { 117 | } 118 | Base.prototype = { 119 | one: { 120 | two: true 121 | } 122 | } 123 | var Extended = function () { 124 | Base.call(this, true) 125 | } 126 | Extended.prototype = Object.create(Base.prototype) 127 | 128 | var extended = new Extended() 129 | 130 | expect(objectPath.get(extended, ['one', 'two'])).to.be.equal(undefined) 131 | extended.enabled = true 132 | 133 | expect(objectPath.get(extended, 'enabled')).to.be.equal(true) 134 | expect(objectPath.get(extended, 'one')).to.be.equal(undefined) 135 | }) 136 | 137 | it('[security] should not get magic properties in default mode', function () { 138 | expect(objectPath.get({}, '__proto__')).to.be.undefined 139 | expect(objectPath.get({}, [['__proto__']])).to.be.undefined 140 | 141 | function Clazz() {} 142 | Clazz.prototype.test = [] 143 | 144 | expect(objectPath.get(new Clazz(), '__proto__')).to.be.undefined 145 | expect(objectPath.get(new Clazz(), [['__proto__']])).to.be.undefined 146 | expect(objectPath.get(new Clazz(), ['constructor', 'prototype'])).to.be.undefined 147 | }) 148 | 149 | it('[security] should not get magic properties in inheritedProps mode', function () { 150 | expect(function() { 151 | objectPath.withInheritedProps.get({}, '__proto__') 152 | }).to.throw('For security reasons') 153 | 154 | expect(function() { 155 | objectPath.withInheritedProps.get({}, [['__proto__']]) 156 | }).to.throw('For security reasons') 157 | 158 | function Clazz() {} 159 | Clazz.prototype.test = 'original' 160 | 161 | expect(function() { 162 | objectPath.withInheritedProps.get(new Clazz(), '__proto__') 163 | }).to.throw('For security reasons') 164 | 165 | expect(function() { 166 | objectPath.withInheritedProps.get(new Clazz(), [['__proto__']]) 167 | }).to.throw('For security reasons') 168 | 169 | expect(function() { 170 | objectPath.withInheritedProps.get(new Clazz(), ['constructor', 'prototype']) 171 | }).to.throw('For security reasons') 172 | }) 173 | }) 174 | 175 | 176 | describe('set', function () { 177 | it('should set the value using unicode key', function () { 178 | var obj = { 179 | '15\u00f8C': { 180 | '3\u0111': 1 181 | } 182 | } 183 | objectPath.set(obj, '15\u00f8C.3\u0111', 2) 184 | expect(objectPath.get(obj, '15\u00f8C.3\u0111')).to.be.equal(2) 185 | objectPath.set(obj, '15\u00f8C.3\u0111', 3) 186 | expect(objectPath.get(obj, ['15\u00f8C', '3\u0111'])).to.be.equal(3) 187 | }) 188 | 189 | it('should set the value using dot in key', function () { 190 | var obj = { 191 | 'a.b': { 192 | 'looks.like': 1 193 | } 194 | } 195 | objectPath.set(obj, ['a.b', 'looks.like'], 2) 196 | expect(objectPath.get(obj, ['a.b', 'looks.like'])).to.be.equal(2) 197 | }) 198 | 199 | it('should set value under shallow object', function () { 200 | var obj = getTestObj() 201 | objectPath.set(obj, 'c', {m: 'o'}) 202 | expect(obj).to.include.nested.property('c.m', 'o') 203 | obj = getTestObj() 204 | objectPath.set(obj, ['c'], {m: 'o'}) 205 | expect(obj).to.include.nested.property('c.m', 'o') 206 | }) 207 | 208 | it('should set value using number path', function () { 209 | var obj = getTestObj() 210 | objectPath.set(obj.b.d, 0, 'o') 211 | expect(obj).to.have.nested.property('b.d.0', 'o') 212 | }) 213 | 214 | it('should set value under deep object', function () { 215 | var obj = getTestObj() 216 | objectPath.set(obj, 'b.c', 'o') 217 | expect(obj).to.have.nested.property('b.c', 'o') 218 | obj = getTestObj() 219 | objectPath.set(obj, ['b', 'c'], 'o') 220 | expect(obj).to.have.nested.property('b.c', 'o') 221 | }) 222 | 223 | it('should set value under array', function () { 224 | var obj = getTestObj() 225 | objectPath.set(obj, 'b.e.1.g', 'f') 226 | expect(obj).to.have.nested.property('b.e.1.g', 'f') 227 | obj = getTestObj() 228 | objectPath.set(obj, ['b', 'e', 1, 'g'], 'f') 229 | expect(obj).to.have.nested.property('b.e.1.g', 'f') 230 | 231 | obj = {} 232 | objectPath.set(obj, 'b.0', 'a') 233 | objectPath.set(obj, 'b.1', 'b') 234 | expect(obj.b).to.be.deep.equal(['a', 'b']) 235 | }) 236 | 237 | it('should create intermediate objects', function () { 238 | var obj = getTestObj() 239 | objectPath.set(obj, 'c.d.e.f', 'l') 240 | expect(obj).to.have.nested.property('c.d.e.f', 'l') 241 | obj = getTestObj() 242 | objectPath.set(obj, ['c', 'd', 'e', 'f'], 'l') 243 | expect(obj).to.have.nested.property('c.d.e.f', 'l') 244 | }) 245 | 246 | it('should create intermediate arrays', function () { 247 | var obj = getTestObj() 248 | objectPath.set(obj, 'c.0.1.m', 'l') 249 | expect(obj.c).to.be.an('array') 250 | expect(obj.c[0]).to.be.an('array') 251 | expect(obj).to.have.nested.property('c.0.1.m', 'l') 252 | obj = getTestObj() 253 | objectPath.set(obj, ['c', '0', 1, 'm'], 'l') 254 | expect(obj.c).to.be.an('object') 255 | expect(obj.c[0]).to.be.an('array') 256 | expect(obj).to.have.nested.property('c.0.1.m', 'l') 257 | }) 258 | 259 | it('should set value under integer-like key', function () { 260 | var obj = getTestObj() 261 | objectPath.set(obj, '1a', 'foo') 262 | expect(obj).to.have.nested.property('1a', 'foo') 263 | obj = getTestObj() 264 | objectPath.set(obj, ['1a'], 'foo') 265 | expect(obj).to.have.nested.property('1a', 'foo') 266 | }) 267 | 268 | it('should set value under empty array', function () { 269 | var obj = [] 270 | objectPath.set(obj, [0], 'foo') 271 | expect(obj[0]).to.be.equal('foo') 272 | obj = [] 273 | objectPath.set(obj, '0', 'foo') 274 | expect(obj[0]).to.be.equal('foo') 275 | }) 276 | 277 | it('[security] should not set magic properties in default mode', function () { 278 | objectPath.set({}, '__proto__.injected', 'this is bad') 279 | expect(Object.prototype.injected).to.be.undefined 280 | 281 | objectPath.set({}, [['__proto__'], 'injected'], 'this is bad') 282 | expect(Object.prototype.injected).to.be.undefined 283 | 284 | objectPath.set({}, ['__proto__'], {}) 285 | expect(Object.prototype.toString).to.be.a('function') 286 | 287 | function Clazz() {} 288 | Clazz.prototype.test = 'original' 289 | 290 | objectPath.set({}, ['__proto__'], {test: 'this is bad'}) 291 | expect(Clazz.prototype.test).to.be.equal('original') 292 | 293 | objectPath.set(new Clazz(), '__proto__.test', 'this is bad') 294 | expect(Clazz.prototype.test).to.be.equal('original') 295 | 296 | objectPath.set(new Clazz(), [['__proto__'], 'test'], 'this is bad') 297 | expect(Clazz.prototype.test).to.be.equal('original') 298 | 299 | objectPath.set(new Clazz(), 'constructor.prototype.test', 'this is bad') 300 | expect(Clazz.prototype.test).to.be.equal('original') 301 | }) 302 | 303 | it('[security] should throw an exception if trying to set magic properties in inheritedProps mode', function () { 304 | expect(function() { 305 | objectPath.withInheritedProps.set({}, '__proto__.injected', 'this is bad') 306 | expect(Object.prototype.injected).to.be.undefined 307 | }).to.throw('For security reasons') 308 | 309 | expect(function() { 310 | objectPath.withInheritedProps.set({}, [['__proto__'], 'injected'], 'this is bad') 311 | expect(Object.prototype.injected).to.be.undefined 312 | }).to.throw('For security reasons') 313 | 314 | function Clazz() {} 315 | Clazz.prototype.test = 'original' 316 | 317 | expect(function() { 318 | objectPath.withInheritedProps.set(new Clazz(), '__proto__.test', 'this is bad') 319 | expect(Clazz.prototype.test).to.be.equal('original') 320 | }).to.throw('For security reasons') 321 | 322 | expect(function() { 323 | objectPath.withInheritedProps.set(new Clazz(), 'constructor.prototype.test', 'this is bad') 324 | expect(Clazz.prototype.test).to.be.equal('original') 325 | }).to.throw('For security reasons') 326 | 327 | expect(function() { 328 | objectPath.withInheritedProps.set({}, 'constructor.prototype.injected', 'this is OK') 329 | expect(Object.prototype.injected).to.be.undefined 330 | }).to.throw('For security reasons') 331 | 332 | expect(function() { 333 | objectPath.withInheritedProps.set({}, [['constructor'], 'prototype', 'injected'], 'this is bad') 334 | expect(Object.prototype.injected).to.be.undefined 335 | }).to.throw('For security reasons') 336 | }) 337 | }) 338 | 339 | 340 | describe('push', function () { 341 | it('should push value to existing array using unicode key', function () { 342 | var obj = getTestObj() 343 | objectPath.push(obj, 'b.\u1290c', 'l') 344 | expect(obj).to.have.nested.property('b.\u1290c.0', 'l') 345 | objectPath.push(obj, ['b', '\u1290c'], 'l') 346 | expect(obj).to.have.nested.property('b.\u1290c.1', 'l') 347 | }) 348 | 349 | it('should push value to existing array using dot key', function () { 350 | var obj = getTestObj() 351 | objectPath.push(obj, ['b', 'z.d'], 'l') 352 | expect(objectPath.get(obj, ['b', 'z.d', 0])).to.be.equal('l') 353 | }) 354 | 355 | it('should push value to existing array', function () { 356 | var obj = getTestObj() 357 | objectPath.push(obj, 'b.c', 'l') 358 | expect(obj).to.have.nested.property('b.c.0', 'l') 359 | obj = getTestObj() 360 | objectPath.push(obj, ['b', 'c'], 'l') 361 | expect(obj).to.have.nested.property('b.c.0', 'l') 362 | }) 363 | 364 | it('should push value to new array', function () { 365 | var obj = getTestObj() 366 | objectPath.push(obj, 'b.h', 'l') 367 | expect(obj).to.have.nested.property('b.h.0', 'l') 368 | obj = getTestObj() 369 | objectPath.push(obj, ['b', 'h'], 'l') 370 | expect(obj).to.have.nested.property('b.h.0', 'l') 371 | }) 372 | 373 | it('should push value to existing array using number path', function () { 374 | var obj = getTestObj() 375 | objectPath.push(obj.b.e, 0, 'l') 376 | expect(obj).to.have.nested.property('b.e.0.0', 'l') 377 | }) 378 | 379 | it('[security] should not push within prototype properties in default mode', function () { 380 | function Clazz() {} 381 | Clazz.prototype.test = [] 382 | 383 | objectPath.push(new Clazz(), '__proto__.test', 'pushed') 384 | expect(Clazz.prototype.test).to.be.deep.equal([]) 385 | 386 | objectPath.push(new Clazz(), [['__proto__'], 'test'], 'pushed') 387 | expect(Clazz.prototype.test).to.be.deep.equal([]) 388 | 389 | objectPath.push(new Clazz(), 'constructor.prototype.test', 'pushed') 390 | expect(Clazz.prototype.test).to.be.deep.equal([]) 391 | }) 392 | 393 | it('[security] should not push within prototype properties in inheritedProps mode', function () { 394 | function Clazz() {} 395 | Clazz.prototype.test = [] 396 | 397 | expect(function() { 398 | objectPath.withInheritedProps.push(new Clazz(), '__proto__.test', 'pushed') 399 | expect(Clazz.prototype.test).to.be.deep.equal([]) 400 | }).to.throw('For security reasons') 401 | 402 | expect(function() { 403 | objectPath.withInheritedProps.push(new Clazz(), [['__proto__'], 'test'], 'pushed') 404 | expect(Clazz.prototype.test).to.be.deep.equal([]) 405 | }).to.throw('For security reasons') 406 | 407 | expect(function() { 408 | objectPath.withInheritedProps.push(new Clazz(), 'constructor.prototype.test', 'pushed') 409 | expect(Clazz.prototype.test).to.be.deep.equal([]) 410 | }).to.throw('For security reasons') 411 | }) 412 | }) 413 | 414 | 415 | describe('ensureExists', function () { 416 | it('should create the path if it does not exists', function () { 417 | var obj = getTestObj() 418 | var oldVal = objectPath.ensureExists(obj, 'b.g.1.l', 'test') 419 | expect(oldVal).to.not.exist 420 | expect(obj).to.have.nested.property('b.g.1.l', 'test') 421 | oldVal = objectPath.ensureExists(obj, 'b.g.1.l', 'test1') 422 | expect(oldVal).to.be.equal('test') 423 | expect(obj).to.have.nested.property('b.g.1.l', 'test') 424 | oldVal = objectPath.ensureExists(obj, 'b.\u8210', 'ok') 425 | expect(oldVal).to.not.exist 426 | expect(obj).to.have.nested.property('b.\u8210', 'ok') 427 | oldVal = objectPath.ensureExists(obj, ['b', 'dot.dot'], 'ok') 428 | expect(oldVal).to.not.exist 429 | expect(objectPath.get(obj, ['b', 'dot.dot'])).to.be.equal('ok') 430 | }) 431 | 432 | 433 | it('should return the object if path is empty', function () { 434 | var obj = getTestObj() 435 | expect(objectPath.ensureExists(obj, [], 'test')).to.have.property('a', 'b') 436 | }) 437 | 438 | it('Issue #26', function () { 439 | var any = {} 440 | objectPath.ensureExists(any, ['1', '1'], {}) 441 | expect(any).to.be.an('object') 442 | expect(any[1]).to.be.an('object') 443 | expect(any[1][1]).to.be.an('object') 444 | }) 445 | 446 | it('[security] should not set magic properties in default mode', function () { 447 | objectPath.ensureExists({}, '__proto__.injected', 'this is bad') 448 | expect(Object.prototype.injected).to.be.undefined 449 | 450 | objectPath.ensureExists({}, [['__proto__'], 'injected'], 'this is bad') 451 | expect(Object.prototype.injected).to.be.undefined 452 | 453 | objectPath.ensureExists({}, ['__proto__'], {}) 454 | expect(Object.prototype.toString).to.be.a('function') 455 | 456 | function Clazz() {} 457 | Clazz.prototype.test = 'original' 458 | 459 | objectPath.ensureExists({}, ['__proto__'], {test: 'this is bad'}) 460 | expect(Clazz.prototype.test).to.be.equal('original') 461 | 462 | objectPath.ensureExists(new Clazz(), '__proto__.test', 'this is bad') 463 | expect(Clazz.prototype.test).to.be.equal('original') 464 | 465 | objectPath.ensureExists(new Clazz(), [['__proto__'], 'test'], 'this is bad') 466 | expect(Clazz.prototype.test).to.be.equal('original') 467 | 468 | objectPath.ensureExists(new Clazz(), 'constructor.prototype.test', 'this is bad') 469 | expect(Clazz.prototype.test).to.be.equal('original') 470 | }) 471 | 472 | it('[security] should throw an exception if trying to set magic properties in inheritedProps mode', function () { 473 | expect(function() {objectPath.withInheritedProps.ensureExists({}, '__proto__.injected', 'this is bad')}) 474 | .to.throw('For security reasons') 475 | expect(Object.prototype.injected).to.be.undefined 476 | 477 | expect(function() { 478 | objectPath.withInheritedProps.ensureExists({}, [['__proto__'], 'injected'], 'this is bad') 479 | expect(Object.prototype.injected).to.be.undefined 480 | }).to.throw('For security reasons') 481 | 482 | function Clazz() {} 483 | Clazz.prototype.test = 'original' 484 | 485 | expect(function() { 486 | objectPath.withInheritedProps.ensureExists(new Clazz(), '__proto__.test', 'this is bad') 487 | expect(Clazz.prototype.test).to.be.equal('original') 488 | }).to.throw('For security reasons') 489 | 490 | 491 | expect(function() { 492 | objectPath.withInheritedProps.ensureExists(new Clazz(), 'constructor.prototype.test', 'this is bad') 493 | expect(Clazz.prototype.test).to.be.equal('original') 494 | }).to.throw('For security reasons') 495 | 496 | expect(function() { 497 | objectPath.withInheritedProps.ensureExists({}, 'constructor.prototype.injected', 'this is OK') 498 | expect(Object.prototype.injected).to.be.undefined 499 | }).to.throw('For security reasons') 500 | 501 | expect(function() { 502 | objectPath.withInheritedProps.ensureExists({}, [['constructor'], 'prototype', 'injected'], 'this is bad') 503 | expect(Object.prototype.injected).to.be.undefined 504 | }).to.throw('For security reasons') 505 | }) 506 | }) 507 | 508 | describe('coalesce', function () { 509 | it('should return the first non-undefined value', function () { 510 | var obj = { 511 | should: {have: 'prop'} 512 | } 513 | 514 | expect(objectPath.coalesce(obj, [ 515 | 'doesnt.exist', 516 | ['might', 'not', 'exist'], 517 | 'should.have' 518 | ])).to.equal('prop') 519 | }) 520 | 521 | it('should work with falsy values (null, 0, \'\', false)', function () { 522 | var obj = { 523 | is: { 524 | false: false, 525 | null: null, 526 | empty: '', 527 | zero: 0 528 | } 529 | } 530 | 531 | expect(objectPath.coalesce(obj, [ 532 | 'doesnt.exist', 533 | 'is.zero' 534 | ])).to.equal(0) 535 | 536 | expect(objectPath.coalesce(obj, [ 537 | 'doesnt.exist', 538 | 'is.false' 539 | ])).to.equal(false) 540 | 541 | expect(objectPath.coalesce(obj, [ 542 | 'doesnt.exist', 543 | 'is.null' 544 | ])).to.equal(null) 545 | 546 | expect(objectPath.coalesce(obj, [ 547 | 'doesnt.exist', 548 | 'is.empty' 549 | ])).to.equal('') 550 | }) 551 | 552 | it('returns defaultValue if no paths found', function () { 553 | var obj = { 554 | doesnt: 'matter' 555 | } 556 | 557 | expect(objectPath.coalesce(obj, ['some.inexistant', 'path', ['on', 'object']], 'false')).to.equal('false') 558 | }) 559 | 560 | it('works with unicode and dot keys', function () { 561 | var obj = { 562 | '\u7591': true, 563 | 'dot.dot': false 564 | } 565 | 566 | expect(objectPath.coalesce(obj, ['1', '\u7591', 'a.b'])).to.equal(true) 567 | expect(objectPath.coalesce(obj, ['1', ['dot.dot'], '\u7591'])).to.equal(false) 568 | }) 569 | }) 570 | 571 | describe('empty', function () { 572 | it('should ignore invalid arguments safely', function () { 573 | var obj = {} 574 | expect(objectPath.empty()).to.equal(void 0) 575 | expect(objectPath.empty(obj, 'path')).to.equal(void 0) 576 | expect(objectPath.empty(obj, '')).to.equal(void 0) 577 | 578 | obj.path = true 579 | 580 | expect(objectPath.empty(obj, 'inexistant')).to.equal(void 0) 581 | 582 | expect(objectPath.empty(null, 'path')).to.equal(void 0) 583 | expect(objectPath.empty(void 0, 'path')).to.equal(void 0) 584 | }) 585 | 586 | it('should empty each path according to their types', function () { 587 | function Instance () { 588 | this.notOwn = true 589 | } 590 | 591 | /*istanbul ignore next: not part of code */ 592 | Instance.prototype.test = function () { 593 | } 594 | /*istanbul ignore next: not part of code */ 595 | Instance.prototype.arr = [] 596 | 597 | var 598 | obj = { 599 | string: 'some string', 600 | array: ['some', 'array', [1, 2, 3]], 601 | number: 21, 602 | boolean: true, 603 | object: { 604 | some: 'property', 605 | sub: { 606 | 'property': true 607 | }, 608 | nullProp: null, 609 | undefinedProp: void 0 610 | }, 611 | instance: new Instance() 612 | } 613 | 614 | /*istanbul ignore next: not part of code */ 615 | obj['function'] = function () { 616 | } 617 | 618 | objectPath.empty(obj, ['array', '2']) 619 | expect(obj.array[2]).to.deep.equal([]) 620 | 621 | objectPath.empty(obj, 'object.sub') 622 | expect(obj.object.sub).to.deep.equal({}) 623 | 624 | objectPath.empty(obj, 'object.nullProp') 625 | expect(obj.object.nullProp).to.equal(null) 626 | 627 | objectPath.empty(obj, 'object.undefinedProp') 628 | expect(obj.object.undefinedProp).to.equal(void 0) 629 | expect(obj.object).to.have.property('undefinedProp') 630 | 631 | objectPath.empty(obj, 'object.notAProp') 632 | expect(obj.object.notAProp).to.equal(void 0) 633 | expect(obj.object).to.not.have.property('notAProp') 634 | 635 | objectPath.empty(obj, 'instance.test') 636 | //instance.test is not own property, so it shouldn't be emptied 637 | expect(obj.instance.test).to.be.a('function') 638 | expect(Instance.prototype.test).to.be.a('function') 639 | 640 | objectPath.empty(obj, 'string') 641 | objectPath.empty(obj, 'number') 642 | objectPath.empty(obj, 'boolean') 643 | objectPath.empty(obj, 'function') 644 | objectPath.empty(obj, 'array') 645 | objectPath.empty(obj, 'object') 646 | objectPath.empty(obj, 'instance') 647 | 648 | expect(obj.string).to.equal('') 649 | expect(obj.array).to.deep.equal([]) 650 | expect(obj.number).to.equal(0) 651 | expect(obj.boolean).to.equal(false) 652 | expect(obj.object).to.deep.equal({}) 653 | expect(obj.instance.notOwn).to.be.an('undefined') 654 | expect(obj.instance.arr).to.be.an('array') 655 | expect(obj['function']).to.equal(null) 656 | }) 657 | 658 | it('[security] should not empty prototype properties in default mode', function () { 659 | function Clazz() {} 660 | Clazz.prototype.test = 'original' 661 | 662 | objectPath.empty(new Clazz(), '__proto__') 663 | expect(Clazz.prototype.test).to.be.equal('original') 664 | 665 | objectPath.empty(new Clazz(), [['__proto__']]) 666 | expect(Clazz.prototype.test).to.be.equal('original') 667 | 668 | objectPath.empty(new Clazz(), 'constructor.prototype') 669 | expect(Clazz.prototype.test).to.be.equal('original') 670 | }) 671 | 672 | it('[security] should throw an exception if trying to delete prototype properties in inheritedProps mode', function () { 673 | function Clazz() {} 674 | Clazz.prototype.test = 'original' 675 | 676 | expect(function() { 677 | objectPath.withInheritedProps.empty(new Clazz(), '__proto__') 678 | expect(Clazz.prototype.test).to.be.equal('original') 679 | }).to.throw('For security reasons') 680 | 681 | expect(function() { 682 | objectPath.withInheritedProps.empty(new Clazz(), 'constructor.prototype') 683 | expect(Clazz.prototype.test).to.be.equal('original') 684 | }).to.throw('For security reasons') 685 | 686 | expect(function() { 687 | objectPath.withInheritedProps.empty({}, [['constructor'], 'prototype']) 688 | expect(Clazz.prototype.test).to.be.equal('original') 689 | }).to.throw('For security reasons') 690 | }) 691 | }) 692 | 693 | describe('del', function () { 694 | it('should work with number path', function () { 695 | var obj = getTestObj() 696 | objectPath.del(obj.b.d, 1) 697 | expect(obj.b.d).to.deep.equal(['a']) 698 | }) 699 | 700 | it('should remove null and undefined props (but not explode on nested)', function () { 701 | var obj = {nullProp: null, undefinedProp: void 0} 702 | expect(obj).to.have.property('nullProp') 703 | expect(obj).to.have.property('undefinedProp') 704 | 705 | objectPath.del(obj, 'nullProp.foo') 706 | objectPath.del(obj, 'undefinedProp.bar') 707 | expect(obj).to.have.property('nullProp') 708 | expect(obj).to.have.property('undefinedProp') 709 | expect(obj).to.deep.equal({nullProp: null, undefinedProp: void 0}) 710 | 711 | objectPath.del(obj, 'nullProp') 712 | objectPath.del(obj, 'undefinedProp') 713 | expect(obj).to.not.have.property('nullProp') 714 | expect(obj).to.not.have.property('undefinedProp') 715 | expect(obj).to.deep.equal({}) 716 | }) 717 | 718 | it('should delete deep paths', function () { 719 | var obj = getTestObj() 720 | 721 | expect(objectPath.del(obj)).to.be.equal(obj) 722 | 723 | objectPath.set(obj, 'b.g.1.0', 'test') 724 | objectPath.set(obj, 'b.g.1.1', 'test') 725 | objectPath.set(obj, 'b.h.az', 'test') 726 | objectPath.set(obj, 'b.\ubeef', 'test') 727 | objectPath.set(obj, ['b', 'dot.dot'], 'test') 728 | 729 | expect(obj).to.have.nested.property('b.g.1.0', 'test') 730 | expect(obj).to.have.nested.property('b.g.1.1', 'test') 731 | expect(obj).to.have.nested.property('b.h.az', 'test') 732 | expect(obj).to.have.nested.property('b.\ubeef', 'test') 733 | 734 | objectPath.del(obj, 'b.h.az') 735 | expect(obj).to.not.have.nested.property('b.h.az') 736 | expect(obj).to.have.nested.property('b.h') 737 | 738 | objectPath.del(obj, 'b.g.1.1') 739 | expect(obj).to.not.have.nested.property('b.g.1.1') 740 | expect(obj).to.have.nested.property('b.g.1.0', 'test') 741 | 742 | objectPath.del(obj, 'b.\ubeef') 743 | expect(obj).to.not.have.nested.property('b.\ubeef') 744 | 745 | objectPath.del(obj, ['b', 'dot.dot']) 746 | expect(objectPath.get(obj, ['b', 'dot.dot'])).to.be.equal(void 0) 747 | 748 | objectPath.del(obj, ['b', 'g', '1', '0']) 749 | expect(obj).to.not.have.nested.property('b.g.1.0') 750 | expect(obj).to.have.nested.property('b.g.1') 751 | 752 | expect(objectPath.del(obj, ['b'])).to.not.have.nested.property('b.g') 753 | expect(obj).to.be.deep.equal({'a': 'b'}) 754 | }) 755 | 756 | it('should remove items from existing array', function () { 757 | var obj = getTestObj() 758 | 759 | objectPath.del(obj, 'b.d.0') 760 | expect(obj.b.d).to.have.length(1) 761 | expect(obj.b.d).to.be.deep.equal(['b']) 762 | 763 | objectPath.del(obj, 'b.d.0') 764 | expect(obj.b.d).to.have.length(0) 765 | expect(obj.b.d).to.be.deep.equal([]) 766 | }) 767 | 768 | it('[security] should not delete prototype properties in default mode', function () { 769 | objectPath.del({}, '__proto__.valueOf') 770 | expect(Object.prototype.valueOf).to.be.a('function') 771 | 772 | objectPath.del({}, [['__proto__'], 'valueOf']) 773 | expect(Object.prototype.valueOf).to.be.a('function') 774 | 775 | function Clazz() {} 776 | Clazz.prototype.test = 'original' 777 | 778 | objectPath.del(new Clazz(), '__proto__.test') 779 | expect(Clazz.prototype.test).to.be.equal('original') 780 | 781 | objectPath.del(new Clazz(), [['__proto__'], 'test']) 782 | expect(Clazz.prototype.test).to.be.equal('original') 783 | 784 | objectPath.del(new Clazz(), 'constructor.prototype.test') 785 | expect(Clazz.prototype.test).to.be.equal('original') 786 | }) 787 | 788 | it('[security] should throw an exception if trying to delete prototype properties in inheritedProps mode', function () { 789 | expect(function() { 790 | objectPath.withInheritedProps.del({}, '__proto__.valueOf') 791 | expect(Object.prototype.valueOf).to.be.a('function') 792 | }).to.throw('For security reasons') 793 | 794 | expect(function() { 795 | objectPath.withInheritedProps.del({}, [['__proto__'], 'valueOf']) 796 | expect(Object.prototype.valueOf).to.be.a('function') 797 | }).to.throw('For security reasons') 798 | 799 | function Clazz() {} 800 | Clazz.prototype.test = 'original' 801 | 802 | expect(function() { 803 | objectPath.withInheritedProps.del(new Clazz(), '__proto__.test') 804 | expect(Clazz.prototype.test).to.be.equal('original') 805 | }).to.throw('For security reasons') 806 | 807 | expect(function() { 808 | objectPath.withInheritedProps.del(new Clazz(), 'constructor.prototype.test', 'this is bad') 809 | expect(Clazz.prototype.test).to.be.equal('original') 810 | }).to.throw('For security reasons') 811 | 812 | expect(function() { 813 | objectPath.withInheritedProps.del({}, [['constructor'], 'prototype', 'test']) 814 | expect(Clazz.prototype.test).to.be.equal('original') 815 | }).to.throw('For security reasons') 816 | }) 817 | }) 818 | 819 | describe('insert', function () { 820 | it('should insert value into existing array', function () { 821 | var obj = getTestObj() 822 | 823 | objectPath.insert(obj, 'b.c', 'asdf') 824 | expect(obj).to.have.nested.property('b.c.0', 'asdf') 825 | expect(obj).to.not.have.nested.property('b.c.1') 826 | }) 827 | 828 | it('should create intermediary array', function () { 829 | var obj = getTestObj() 830 | 831 | objectPath.insert(obj, 'b.c.0', 'asdf') 832 | expect(obj).to.have.nested.property('b.c.0.0', 'asdf') 833 | }) 834 | 835 | it('should insert in another index', function () { 836 | var obj = getTestObj() 837 | 838 | objectPath.insert(obj, 'b.d', 'asdf', 1) 839 | expect(obj).to.have.nested.property('b.d.1', 'asdf') 840 | expect(obj).to.have.nested.property('b.d.0', 'a') 841 | expect(obj).to.have.nested.property('b.d.2', 'b') 842 | }) 843 | 844 | it('should handle sparse array', function () { 845 | var obj = getTestObj() 846 | obj.b.d = new Array(4) 847 | obj.b.d[0] = 'a' 848 | obj.b.d[1] = 'b' 849 | 850 | objectPath.insert(obj, 'b.d', 'asdf', 3) 851 | expect(obj.b.d).to.have.members([ 852 | 'a', 853 | 'b', 854 | , 855 | , 856 | 'asdf' 857 | ]) 858 | }) 859 | 860 | it('[security] should not insert within prototype properties in default mode', function () { 861 | function Clazz() {} 862 | Clazz.prototype.test = [] 863 | 864 | objectPath.insert(new Clazz(), '__proto__.test', 'inserted', 0) 865 | expect(Clazz.prototype.test).to.be.deep.equal([]) 866 | 867 | objectPath.insert(new Clazz(), [['__proto__'], 'test'], 'inserted', 0) 868 | expect(Clazz.prototype.test).to.be.deep.equal([]) 869 | 870 | objectPath.insert(new Clazz(), 'constructor.prototype.test', 'inserted', 0) 871 | expect(Clazz.prototype.test).to.be.deep.equal([]) 872 | }) 873 | 874 | it('[security] should not insert within prototype properties in inheritedProps mode', function () { 875 | function Clazz() {} 876 | Clazz.prototype.test = [] 877 | 878 | expect(function() { 879 | objectPath.withInheritedProps.insert(new Clazz(), '__proto__.test', 'inserted', 0) 880 | expect(Clazz.prototype.test).to.be.deep.equal([]) 881 | }).to.throw('For security reasons') 882 | 883 | expect(function() { 884 | objectPath.withInheritedProps.insert(new Clazz(), [['__proto__'], 'test'], 'inserted', 0) 885 | expect(Clazz.prototype.test).to.be.deep.equal([]) 886 | }).to.throw('For security reasons') 887 | 888 | expect(function() { 889 | objectPath.withInheritedProps.insert(new Clazz(), 'constructor.prototype.test', 'inserted', 0) 890 | expect(Clazz.prototype.test).to.be.deep.equal([]) 891 | }).to.throw('For security reasons') 892 | 893 | expect(function() { 894 | objectPath.withInheritedProps.insert(new Clazz().constructor, 'prototype.test', 'inserted', 0) 895 | expect(Clazz.prototype.test).to.be.deep.equal([]) 896 | }).to.throw('For security reasons') 897 | }) 898 | }) 899 | 900 | describe('has', function () { 901 | it('should return false for empty object', function () { 902 | expect(objectPath.has({}, 'a')).to.be.equal(false) 903 | }) 904 | 905 | it('should handle empty paths properly', function () { 906 | var obj = getTestObj() 907 | expect(objectPath.has(obj, '')).to.be.equal(false) 908 | expect(objectPath.has(obj, [''])).to.be.equal(false) 909 | obj[''] = 1 910 | expect(objectPath.has(obj, '')).to.be.equal(true) 911 | expect(objectPath.has(obj, [''])).to.be.equal(true) 912 | 913 | expect(objectPath.has(obj, [])).to.be.equal(true) 914 | expect(objectPath.has(null, [])).to.be.equal(false) 915 | }) 916 | 917 | it('should test under shallow object', function () { 918 | var obj = getTestObj() 919 | expect(objectPath.has(obj, 'a')).to.be.equal(true) 920 | expect(objectPath.has(obj, ['a'])).to.be.equal(true) 921 | expect(objectPath.has(obj, 'z')).to.be.equal(false) 922 | expect(objectPath.has(obj, ['z'])).to.be.equal(false) 923 | }) 924 | 925 | it('should work with number path', function () { 926 | var obj = getTestObj() 927 | expect(objectPath.has(obj.b.d, 0)).to.be.equal(true) 928 | expect(objectPath.has(obj.b, 0)).to.be.equal(false) 929 | expect(objectPath.has(obj.b.d, 10)).to.be.equal(false) 930 | expect(objectPath.has(obj.b, 10)).to.be.equal(false) 931 | }) 932 | 933 | it('should test under deep object', function () { 934 | var obj = getTestObj() 935 | expect(objectPath.has(obj, 'b.f')).to.be.equal(true) 936 | expect(objectPath.has(obj, ['b', 'f'])).to.be.equal(true) 937 | expect(objectPath.has(obj, 'b.g')).to.be.equal(false) 938 | expect(objectPath.has(obj, ['b', 'g'])).to.be.equal(false) 939 | }) 940 | 941 | it('should test value under array', function () { 942 | var obj = { 943 | b: ['a'] 944 | } 945 | obj.b[3] = {o: 'a'} 946 | expect(objectPath.has(obj, 'b.0')).to.be.equal(true) 947 | expect(objectPath.has(obj, 'b.1')).to.be.equal(true) 948 | expect(objectPath.has(obj, 'b.3.o')).to.be.equal(true) 949 | expect(objectPath.has(obj, 'b.3.qwe')).to.be.equal(false) 950 | expect(objectPath.has(obj, 'b.4')).to.be.equal(false) 951 | }) 952 | 953 | it('should test the value under array deep', function () { 954 | var obj = getTestObj() 955 | expect(objectPath.has(obj, 'b.e.1.f')).to.be.equal(true) 956 | expect(objectPath.has(obj, ['b', 'e', 1, 'f'])).to.be.equal(true) 957 | expect(objectPath.has(obj, 'b.e.1.f.g.h.i')).to.be.equal(false) 958 | expect(objectPath.has(obj, ['b', 'e', 1, 'f', 'g', 'h', 'i'])).to.be.equal(false) 959 | }) 960 | 961 | it('should test the value under integer-like key', function () { 962 | var obj = {'1a': 'foo'} 963 | expect(objectPath.has(obj, '1a')).to.be.equal(true) 964 | expect(objectPath.has(obj, ['1a'])).to.be.equal(true) 965 | }) 966 | 967 | it('should distinct nonexistent key and key = undefined', function () { 968 | var obj = {} 969 | expect(objectPath.has(obj, 'key')).to.be.equal(false) 970 | 971 | obj.key = undefined 972 | expect(objectPath.has(obj, 'key')).to.be.equal(true) 973 | }) 974 | 975 | it('should work with deep undefined/null values', function () { 976 | var obj = {} 977 | expect(objectPath.has(obj, 'missing.test')).to.be.equal(false) 978 | 979 | obj.missing = null 980 | expect(objectPath.has(obj, 'missing.test')).to.be.equal(false) 981 | 982 | obj.sparseArray = [1, undefined, 3] 983 | expect(objectPath.has(obj, 'sparseArray.1.test')).to.be.equal(false) 984 | }) 985 | }) 986 | 987 | 988 | describe('bind object', function () { 989 | // just get one scenario from each feature, so whole functionality is proxied well 990 | it('should return the value under shallow object', function () { 991 | var obj = getTestObj() 992 | var model = objectPath(obj) 993 | expect(model.get('a')).to.be.equal('b') 994 | expect(model.get(['a'])).to.be.equal('b') 995 | }) 996 | 997 | it('should set value under shallow object', function () { 998 | var obj = getTestObj() 999 | var model = objectPath(obj) 1000 | model.set('c', {m: 'o'}) 1001 | expect(obj).to.have.nested.property('c.m', 'o') 1002 | obj = getTestObj() 1003 | model = objectPath(obj) 1004 | model.set(['c'], {m: 'o'}) 1005 | expect(obj).to.have.nested.property('c.m', 'o') 1006 | }) 1007 | 1008 | it('should push value to existing array', function () { 1009 | var obj = getTestObj() 1010 | var model = objectPath(obj) 1011 | model.push('b.c', 'l') 1012 | expect(obj).to.have.nested.property('b.c.0', 'l') 1013 | obj = getTestObj() 1014 | model = objectPath(obj) 1015 | model.push(['b', 'c'], 'l') 1016 | expect(obj).to.have.nested.property('b.c.0', 'l') 1017 | }) 1018 | 1019 | it('should create the path if it does not exists', function () { 1020 | var obj = getTestObj() 1021 | var model = objectPath(obj) 1022 | var oldVal = model.ensureExists('b.g.1.l', 'test') 1023 | expect(oldVal).to.not.exist 1024 | expect(obj).to.have.nested.property('b.g.1.l', 'test') 1025 | oldVal = model.ensureExists('b.g.1.l', 'test1') 1026 | expect(oldVal).to.be.equal('test') 1027 | expect(obj).to.have.nested.property('b.g.1.l', 'test') 1028 | }) 1029 | 1030 | it('should return the first non-undefined value', function () { 1031 | var obj = { 1032 | should: {have: 'prop'} 1033 | } 1034 | var model = objectPath(obj) 1035 | 1036 | expect(model.coalesce([ 1037 | 'doesnt.exist', 1038 | ['might', 'not', 'exist'], 1039 | 'should.have' 1040 | ])).to.equal('prop') 1041 | }) 1042 | 1043 | it('should empty each path according to their types', function () { 1044 | function Instance () { 1045 | this.notOwn = true 1046 | } 1047 | 1048 | /*istanbul ignore next: not part of code */ 1049 | Instance.prototype.test = function () { 1050 | } 1051 | /*istanbul ignore next: not part of code */ 1052 | Instance.prototype.arr = [] 1053 | 1054 | var 1055 | obj = { 1056 | string: 'some string', 1057 | array: ['some', 'array', [1, 2, 3]], 1058 | number: 21, 1059 | boolean: true, 1060 | object: { 1061 | some: 'property', 1062 | sub: { 1063 | 'property': true 1064 | } 1065 | }, 1066 | instance: new Instance() 1067 | } 1068 | 1069 | /*istanbul ignore next: not part of code */ 1070 | obj['function'] = function () { 1071 | } 1072 | 1073 | var model = objectPath(obj) 1074 | 1075 | model.empty(['array', '2']) 1076 | expect(obj.array[2]).to.deep.equal([]) 1077 | 1078 | model.empty('object.sub') 1079 | expect(obj.object.sub).to.deep.equal({}) 1080 | 1081 | model.empty('instance.test') 1082 | //instance.test is not own property so it shouldn't be emptied 1083 | expect(obj.instance.test).to.be.a('function') 1084 | expect(Instance.prototype.test).to.be.a('function') 1085 | 1086 | model.empty('string') 1087 | model.empty('number') 1088 | model.empty('boolean') 1089 | model.empty('function') 1090 | model.empty('array') 1091 | model.empty('object') 1092 | model.empty('instance') 1093 | 1094 | expect(obj.string).to.equal('') 1095 | expect(obj.array).to.deep.equal([]) 1096 | expect(obj.number).to.equal(0) 1097 | expect(obj.boolean).to.equal(false) 1098 | expect(obj.object).to.deep.equal({}) 1099 | expect(obj.instance.notOwn).to.be.an('undefined') 1100 | expect(obj.instance.arr).to.be.an('array') 1101 | expect(obj['function']).to.equal(null) 1102 | }) 1103 | 1104 | it('should delete deep paths', function () { 1105 | var obj = getTestObj() 1106 | var model = objectPath(obj) 1107 | 1108 | expect(model.del()).to.be.equal(obj) 1109 | 1110 | model.set('b.g.1.0', 'test') 1111 | model.set('b.g.1.1', 'test') 1112 | model.set('b.h.az', 'test') 1113 | 1114 | expect(obj).to.have.nested.property('b.g.1.0', 'test') 1115 | expect(obj).to.have.nested.property('b.g.1.1', 'test') 1116 | expect(obj).to.have.nested.property('b.h.az', 'test') 1117 | 1118 | model.del('b.h.az') 1119 | expect(obj).to.not.have.nested.property('b.h.az') 1120 | expect(obj).to.have.nested.property('b.h') 1121 | 1122 | model.del('b.g.1.1') 1123 | expect(obj).to.not.have.nested.property('b.g.1.1') 1124 | expect(obj).to.have.nested.property('b.g.1.0', 'test') 1125 | 1126 | model.del(['b', 'g', '1', '0']) 1127 | expect(obj).to.not.have.nested.property('b.g.1.0') 1128 | expect(obj).to.have.nested.property('b.g.1') 1129 | 1130 | expect(model.del(['b'])).to.not.have.nested.property('b.g') 1131 | expect(obj).to.be.deep.equal({'a': 'b'}) 1132 | }) 1133 | 1134 | it('should insert value into existing array', function () { 1135 | var obj = getTestObj() 1136 | var model = objectPath(obj) 1137 | 1138 | model.insert('b.c', 'asdf') 1139 | expect(obj).to.have.nested.property('b.c.0', 'asdf') 1140 | expect(obj).to.not.have.nested.property('b.c.1') 1141 | }) 1142 | 1143 | it('should test under shallow object', function () { 1144 | var obj = getTestObj() 1145 | var model = objectPath(obj) 1146 | 1147 | expect(model.has('a')).to.be.equal(true) 1148 | expect(model.has(['a'])).to.be.equal(true) 1149 | expect(model.has('z')).to.be.equal(false) 1150 | expect(model.has(['z'])).to.be.equal(false) 1151 | }) 1152 | }) 1153 | 1154 | describe('Don\'t access not own properties [default]', function () { 1155 | it('should not get a not own property', function () { 1156 | var Obj = function () { 1157 | } 1158 | Obj.prototype.notOwn = {a: 'a'} 1159 | var obj = new Obj() 1160 | 1161 | expect(objectPath.get(obj, 'notOwn')).to.be.undefined 1162 | }) 1163 | 1164 | it('should set a not own property on the instance (not the prototype)', function () { 1165 | var proto = { 1166 | notOwn: {} 1167 | } 1168 | var obj = Object.create(proto) 1169 | 1170 | objectPath.set(obj, 'notOwn.test', 'a') 1171 | expect(obj.notOwn.test).to.be.equal('a') 1172 | expect(proto.notOwn).to.be.deep.equal({}) 1173 | }) 1174 | 1175 | it('has should return false on a not own property', function () { 1176 | var proto = { 1177 | notOwn: {a: 'a'} 1178 | } 1179 | var obj = Object.create(proto) 1180 | 1181 | 1182 | expect(objectPath.has(obj, 'notOwn')).to.be.false 1183 | expect(objectPath.has(obj, 'notOwn.a')).to.be.false 1184 | }) 1185 | 1186 | it('empty should not empty on a not own property', function () { 1187 | var proto = { 1188 | notOwn: {a: 'a'} 1189 | } 1190 | var obj = Object.create(proto) 1191 | 1192 | objectPath.empty(obj, 'notOwn') 1193 | expect(proto.notOwn).to.be.deep.equal({a: 'a'}) 1194 | expect(obj.notOwn).to.be.deep.equal({a: 'a'}) 1195 | }) 1196 | 1197 | it('del should not delete not own property', function () { 1198 | var proto = { 1199 | notOwn: {a: 'a'} 1200 | } 1201 | var obj = Object.create(proto) 1202 | 1203 | objectPath.del(obj, 'notOwn.a') 1204 | expect(proto.notOwn).to.be.deep.equal({a: 'a'}) 1205 | //expect(obj.notOwn).to.be.deep.equal({a: 'a'}); 1206 | //objectPath.del(obj, 'notOwn'); 1207 | //expect(proto).to.be.deep.equal({notOwn: {a: 'a'}}); 1208 | //expect(obj).to.be.deep.equal({notOwn: {a: 'a'}}); 1209 | }) 1210 | }) 1211 | 1212 | describe('Access own properties [optional]', function () { 1213 | it('should get a not own property', function () { 1214 | var Obj = function () { 1215 | } 1216 | Obj.prototype.notOwn = {a: 'a'} 1217 | var obj = new Obj() 1218 | 1219 | expect(objectPath.withInheritedProps.get(obj, 'notOwn.a')).to.be.equal('a') 1220 | }) 1221 | 1222 | it('should set a deep not own property on the prototype (if exists)', function () { 1223 | var proto = { 1224 | notOwn: {} 1225 | } 1226 | var obj = Object.create(proto) 1227 | 1228 | objectPath.withInheritedProps.set(obj, 'notOwn.test', 'a') 1229 | expect(obj.notOwn.test).to.be.equal('a') 1230 | expect(proto.notOwn).to.be.deep.equal({test: 'a'}) 1231 | }) 1232 | 1233 | 1234 | it('has should return true on a not own property', function () { 1235 | var proto = { 1236 | notOwn: {a: 'a'} 1237 | } 1238 | var obj = Object.create(proto) 1239 | 1240 | expect(objectPath.withInheritedProps.has(obj, 'notOwn')).to.be.true 1241 | expect(objectPath.withInheritedProps.has(obj, 'notOwn.a')).to.be.true 1242 | }) 1243 | 1244 | it('empty should empty a not own property', function () { 1245 | var proto = { 1246 | notOwn: {a: 'a'} 1247 | } 1248 | var obj = Object.create(proto) 1249 | 1250 | objectPath.withInheritedProps.empty(obj, 'notOwn') 1251 | expect(proto.notOwn).to.be.deep.equal({}) 1252 | expect(obj.notOwn).to.be.deep.equal({}) 1253 | }) 1254 | 1255 | it('del should delete a not own property', function () { 1256 | var proto = { 1257 | notOwn: {a: 'a'} 1258 | } 1259 | var obj = Object.create(proto) 1260 | 1261 | objectPath.withInheritedProps.del(obj, 'notOwn.a') 1262 | expect(proto.notOwn).to.be.deep.equal({}) 1263 | //expect(obj.notOwn).to.be.deep.equal({}); 1264 | objectPath.withInheritedProps.del(obj, 'notOwn') 1265 | //expect(proto).to.be.deep.equal({notOwn: {}}); 1266 | //expect(obj).to.be.deep.equal({notOwn: {}}); 1267 | }) 1268 | }) 1269 | --------------------------------------------------------------------------------