package/package.json000644 000765 000024 0000001633 13055004253013015 0ustar00000000 000000 { "name": "object-path", "description": "Access deep object properties using a path", "version": "0.11.4", "author": { "name": "Mario Casciaro" }, "homepage": "https://github.com/mariocasciaro/object-path", "repository": { "type": "git", "url": "git://github.com/mariocasciaro/object-path.git" }, "engines": { "node": ">=0.10.0" }, "devDependencies": { "@mariocasciaro/benchpress": "^0.1.3", "chai": "^3.5.0", "coveralls": "^2.11.2", "istanbul": "^0.4.4", "mocha": "^2.2.4", "mocha-lcov-reporter": "^1.2.0" }, "scripts": { "test": "istanbul cover ./node_modules/mocha/bin/_mocha test.js --report html -- -R spec" }, "keywords": [ "deep", "path", "access", "bean", "get", "property", "dot", "prop", "object", "obj", "notation", "segment", "value", "nested", "key" ], "license": "MIT" } package/.npmignore000755 000765 000024 0000000110 12734502315012523 0ustar00000000 000000 node_modules .settings .idea npm-debug.log generated coverage .DS_Store package/README.md000644 000765 000024 0000012662 13055004215012010 0ustar00000000 000000 object-path =========== Access deep properties using a path [![NPM version](https://badge.fury.io/js/object-path.png)](http://badge.fury.io/js/object-path) [![Build Status](https://travis-ci.org/mariocasciaro/object-path.png)](https://travis-ci.org/mariocasciaro/object-path) [![Coverage Status](https://coveralls.io/repos/mariocasciaro/object-path/badge.png)](https://coveralls.io/r/mariocasciaro/object-path) [![devDependency Status](https://david-dm.org/mariocasciaro/object-path/dev-status.svg)](https://david-dm.org/mariocasciaro/object-path#info=devDependencies) ![Downloads](http://img.shields.io/npm/dm/object-path.svg) ## Changelog ### 0.11.0 * Introduce ability to specify options and create new instances of `object-path` * Introduce option to control the way `object-path` deals with inherited properties (`includeInheritedProps`) * New default `object-path` instance already configured to handle not-own object properties (`withInheritedProps`) ### 0.10.0 * Improved performance of `get`, `set`, and `push` by 2x-3x * Introduced a benchmarking test suite * **BREAKING CHANGE**: `del`, `empty`, `set` will not affect not-own object's properties (made them consistent with the other methods) ## Install ### Node.js ``` npm install object-path --save ``` ### Bower ``` bower install object-path --save ``` ### Typescript typings ``` typings install --save dt~object-path ``` ## Usage ```javascript var obj = { a: { b: "d", c: ["e", "f"], '\u1200': 'unicode key', 'dot.dot': 'key' } }; var objectPath = require("object-path"); //get deep property objectPath.get(obj, "a.b"); //returns "d" objectPath.get(obj, ["a", "dot.dot"]); //returns "key" objectPath.get(obj, 'a.\u1200'); //returns "unicode key" //get the first non-undefined value objectPath.coalesce(obj, ['a.z', 'a.d', ['a','b']], 'default'); //empty a given path (but do not delete it) depending on their type,so it retains reference to objects and arrays. //functions that are not inherited from prototype are set to null. //object instances are considered objects and just own property names are deleted objectPath.empty(obj, 'a.b'); // obj.a.b is now '' objectPath.empty(obj, 'a.c'); // obj.a.c is now [] objectPath.empty(obj, 'a'); // obj.a is now {} //works also with arrays objectPath.get(obj, "a.c.1"); //returns "f" objectPath.get(obj, ["a","c","1"]); //returns "f" //can return a default value with get objectPath.get(obj, ["a.c.b"], "DEFAULT"); //returns "DEFAULT", since a.c.b path doesn't exists, if omitted, returns undefined //set objectPath.set(obj, "a.h", "m"); // or objectPath.set(obj, ["a","h"], "m"); objectPath.get(obj, "a.h"); //returns "m" //set will create intermediate object/arrays objectPath.set(obj, "a.j.0.f", "m"); //will insert values in array objectPath.insert(obj, "a.c", "m", 1); // obj.a.c = ["e", "m", "f"] //push into arrays (and create intermediate objects/arrays) objectPath.push(obj, "a.k", "o"); //ensure a path exists (if it doesn't, set the default value you provide) objectPath.ensureExists(obj, "a.k.1", "DEFAULT"); var oldVal = objectPath.ensureExists(obj, "a.b", "DEFAULT"); // oldval === "d" //deletes a path objectPath.del(obj, "a.b"); // obj.a.b is now undefined objectPath.del(obj, ["a","c",0]); // obj.a.c is now ['f'] //tests path existence objectPath.has(obj, "a.b"); // true objectPath.has(obj, ["a","d"]); // false //bind object var model = objectPath({ a: { b: "d", c: ["e", "f"] } }); //now any method from above is supported directly w/o passing an object model.get("a.b"); //returns "d" model.get(["a.c.b"], "DEFAULT"); //returns "DEFAULT" model.del("a.b"); // obj.a.b is now undefined model.has("a.b"); // false ``` ### How `object-path` deals with inherited properties By default `object-path` will only access an object's own properties. Look at the following example: ```javascript var proto = { notOwn: {prop: 'a'} } var obj = Object.create(proto); //This will return undefined (or the default value you specified), because notOwn is //an inherited property objectPath.get(obj, 'notOwn.prop'); //This will set the property on the obj instance and not the prototype. //In other words proto.notOwn.prop === 'a' and obj.notOwn.prop === 'b' objectPath.set(obj, 'notOwn.prop', 'b'); ``` To configure `object-path` to also deal with inherited properties, you need to create a new instance and specify the `includeInheritedProps = true` in the options object: ```javascript var objectPath = require("object-path"); var objectPathWithInheritedProps = objectPath.create({includeInheritedProps: true}) ``` Alternatively, `object-path` exposes an instance already configured to handle inherited properties (`objectPath.withInheritedProps`): ```javascript var objectPath = require("object-path"); var objectPathWithInheritedProps = objectPath.withInheritedProps ``` Once you have the new instance, you can access inherited properties as you access other properties: ```javascript var proto = { notOwn: {prop: 'a'} } var obj = Object.create(proto); //This will return 'a' objectPath.withInheritedProps.get(obj, 'notOwn.prop'); //This will set proto.notOwn.prop to 'b' objectPath.set(obj, 'notOwn.prop', 'b'); ``` ### Immutability 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) ### Credits * [Mario Casciaro](https://github.com/mariocasciaro) - Author * [Paulo Cesar](https://github.com/pocesar) - Major contributor package/LICENSE000644 000765 000024 0000002070 13055004215011526 0ustar00000000 000000 The MIT License (MIT) Copyright (c) 2015 Mario Casciaro Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.package/test.js000644 000765 000024 0000070713 13055004215012047 0ustar00000000 000000 'use strict'; var expect = require('chai').expect, objectPath = require('./index.js'); function getTestObj() { return { a: 'b', b: { c: [], d: ['a', 'b'], e: [{},{f: 'g'}], f: 'i' } }; } describe('get', function() { it('should return the value using unicode key', function() { var obj = { '15\u00f8C': { '3\u0111': 1 } }; expect(objectPath.get(obj, '15\u00f8C.3\u0111')).to.be.equal(1); expect(objectPath.get(obj, ['15\u00f8C','3\u0111'])).to.be.equal(1); }); it('should return the value using dot in key', function() { var obj = { 'a.b': { 'looks.like': 1 } }; expect(objectPath.get(obj, 'a.b.looks.like')).to.be.equal(void 0); expect(objectPath.get(obj, ['a.b','looks.like'])).to.be.equal(1); }); it('should return the value under shallow object', function() { var obj = getTestObj(); expect(objectPath.get(obj, 'a')).to.be.equal('b'); expect(objectPath.get(obj, ['a'])).to.be.equal('b'); }); it('should work with number path', function() { var obj = getTestObj(); expect(objectPath.get(obj.b.d, 0)).to.be.equal('a'); expect(objectPath.get(obj.b, 0)).to.be.equal(void 0); }); it('should return the value under deep object', function() { var obj = getTestObj(); expect(objectPath.get(obj, 'b.f')).to.be.equal('i'); expect(objectPath.get(obj, ['b','f'])).to.be.equal('i'); }); it('should return the value under array', function() { var obj = getTestObj(); expect(objectPath.get(obj, 'b.d.0')).to.be.equal('a'); expect(objectPath.get(obj, ['b','d',0])).to.be.equal('a'); }); it('should return the value under array deep', function() { var obj = getTestObj(); expect(objectPath.get(obj, 'b.e.1.f')).to.be.equal('g'); expect(objectPath.get(obj, ['b','e',1,'f'])).to.be.equal('g'); }); it('should return undefined for missing values under object', function() { var obj = getTestObj(); expect(objectPath.get(obj, 'a.b')).to.not.exist; expect(objectPath.get(obj, ['a','b'])).to.not.exist; }); it('should return undefined for missing values under array', function() { var obj = getTestObj(); expect(objectPath.get(obj, 'b.d.5')).to.not.exist; expect(objectPath.get(obj, ['b','d','5'])).to.not.exist; }); it('should return the value under integer-like key', function() { var obj = { '1a': 'foo' }; expect(objectPath.get(obj, '1a')).to.be.equal('foo'); expect(objectPath.get(obj, ['1a'])).to.be.equal('foo'); }); it('should return the default value when the key doesnt exist', function() { var obj = { '1a': 'foo' }; expect(objectPath.get(obj, '1b', null)).to.be.equal(null); expect(objectPath.get(obj, ['1b'], null)).to.be.equal(null); }); it('should return the default value when path is empty', function() { var obj = { '1a': 'foo' }; expect(objectPath.get(obj, '', null)).to.be.deep.equal({ '1a': 'foo' }); expect(objectPath.get(obj, [])).to.be.deep.equal({ '1a': 'foo' }); expect(objectPath.get({ }, ['1'])).to.be.equal(undefined); }); it('should return the default value when object is null or undefined', function() { expect(objectPath.get(null, 'test', 'a')).to.be.deep.equal('a'); expect(objectPath.get(undefined, 'test', 'a')).to.be.deep.equal('a'); }); it( 'should not fail on an object with a null prototype', function assertSuccessForObjWithNullProto(){ var foo = 'FOO'; var objWithNullProto = Object.create(null); objWithNullProto.foo = foo; expect(objectPath.get(objWithNullProto, 'foo')).to.equal(foo); } ); it('should skip non own properties', function() { var Base = function(enabled){ }; Base.prototype = { one: { two: true } }; var Extended = function(){ Base.call(this, true); }; Extended.prototype = Object.create(Base.prototype); var extended = new Extended(); expect(objectPath.get(extended, ['one','two'])).to.be.equal(undefined); extended.enabled = true; expect(objectPath.get(extended, 'enabled')).to.be.equal(true); expect(objectPath.get(extended, 'one')).to.be.equal(undefined); }); }); describe('set', function() { it('should set the value using unicode key', function() { var obj = { '15\u00f8C': { '3\u0111': 1 } }; objectPath.set(obj, '15\u00f8C.3\u0111', 2); expect(objectPath.get(obj, '15\u00f8C.3\u0111')).to.be.equal(2); objectPath.set(obj, '15\u00f8C.3\u0111', 3); expect(objectPath.get(obj, ['15\u00f8C','3\u0111'])).to.be.equal(3); }); it('should set the value using dot in key', function() { var obj = { 'a.b': { 'looks.like': 1 } }; objectPath.set(obj, ['a.b','looks.like'], 2); expect(objectPath.get(obj, ['a.b','looks.like'])).to.be.equal(2); }); it('should set value under shallow object', function() { var obj = getTestObj(); objectPath.set(obj, 'c', {m: 'o'}); expect(obj).to.have.deep.property('c.m', 'o'); obj = getTestObj(); objectPath.set(obj, ['c'], {m: 'o'}); expect(obj).to.have.deep.property('c.m', 'o'); }); it('should set value using number path', function() { var obj = getTestObj(); objectPath.set(obj.b.d, 0, 'o'); expect(obj).to.have.deep.property('b.d.0', 'o'); }); it('should set value under deep object', function() { var obj = getTestObj(); objectPath.set(obj, 'b.c', 'o'); expect(obj).to.have.deep.property('b.c', 'o'); obj = getTestObj(); objectPath.set(obj, ['b','c'], 'o'); expect(obj).to.have.deep.property('b.c', 'o'); }); it('should set value under array', function() { var obj = getTestObj(); objectPath.set(obj, 'b.e.1.g', 'f'); expect(obj).to.have.deep.property('b.e.1.g', 'f'); obj = getTestObj(); objectPath.set(obj, ['b','e',1,'g'], 'f'); expect(obj).to.have.deep.property('b.e.1.g', 'f'); obj = {} objectPath.set(obj, 'b.0', 'a'); objectPath.set(obj, 'b.1', 'b'); expect(obj.b).to.be.deep.equal(['a', 'b']); }); it('should create intermediate objects', function() { var obj = getTestObj(); objectPath.set(obj, 'c.d.e.f', 'l'); expect(obj).to.have.deep.property('c.d.e.f', 'l'); obj = getTestObj(); objectPath.set(obj, ['c','d','e','f'], 'l'); expect(obj).to.have.deep.property('c.d.e.f', 'l'); }); it('should create intermediate arrays', function() { var obj = getTestObj(); objectPath.set(obj, 'c.0.1.m', 'l'); expect(obj.c).to.be.an('array'); expect(obj.c[0]).to.be.an('array'); expect(obj).to.have.deep.property('c.0.1.m', 'l'); obj = getTestObj(); objectPath.set(obj, ['c','0', 1,'m'], 'l'); expect(obj.c).to.be.an('object'); expect(obj.c[0]).to.be.an('array'); expect(obj).to.have.deep.property('c.0.1.m', 'l'); }); it('should set value under integer-like key', function() { var obj = getTestObj(); objectPath.set(obj, '1a', 'foo'); expect(obj).to.have.deep.property('1a', 'foo'); obj = getTestObj(); objectPath.set(obj, ['1a'], 'foo'); expect(obj).to.have.deep.property('1a', 'foo'); }); it('should set value under empty array', function() { var obj = []; objectPath.set(obj, [0], 'foo'); expect(obj[0]).to.be.equal('foo'); obj = []; objectPath.set(obj, '0', 'foo'); expect(obj[0]).to.be.equal('foo'); }); }); describe('push', function() { it('should push value to existing array using unicode key', function() { var obj = getTestObj(); objectPath.push(obj, 'b.\u1290c', 'l'); expect(obj).to.have.deep.property('b.\u1290c.0', 'l'); objectPath.push(obj, ['b','\u1290c'], 'l'); expect(obj).to.have.deep.property('b.\u1290c.1', 'l'); }); it('should push value to existing array using dot key', function() { var obj = getTestObj(); objectPath.push(obj, ['b','z.d'], 'l'); expect(objectPath.get(obj, ['b','z.d', 0])).to.be.equal('l'); }); it('should push value to existing array', function() { var obj = getTestObj(); objectPath.push(obj, 'b.c', 'l'); expect(obj).to.have.deep.property('b.c.0', 'l'); obj = getTestObj(); objectPath.push(obj, ['b','c'], 'l'); expect(obj).to.have.deep.property('b.c.0', 'l'); }); it('should push value to new array', function() { var obj = getTestObj(); objectPath.push(obj, 'b.h', 'l'); expect(obj).to.have.deep.property('b.h.0', 'l'); obj = getTestObj(); objectPath.push(obj, ['b','h'], 'l'); expect(obj).to.have.deep.property('b.h.0', 'l'); }); it('should push value to existing array using number path', function() { var obj = getTestObj(); objectPath.push(obj.b.e, 0, 'l'); expect(obj).to.have.deep.property('b.e.0.0', 'l'); }); }); describe('ensureExists', function() { it('should create the path if it does not exists', function() { var obj = getTestObj(); var oldVal = objectPath.ensureExists(obj, 'b.g.1.l', 'test'); expect(oldVal).to.not.exist; expect(obj).to.have.deep.property('b.g.1.l', 'test'); oldVal = objectPath.ensureExists(obj, 'b.g.1.l', 'test1'); expect(oldVal).to.be.equal('test'); expect(obj).to.have.deep.property('b.g.1.l', 'test'); oldVal = objectPath.ensureExists(obj, 'b.\u8210', 'ok'); expect(oldVal).to.not.exist; expect(obj).to.have.deep.property('b.\u8210', 'ok'); oldVal = objectPath.ensureExists(obj, ['b','dot.dot'], 'ok'); expect(oldVal).to.not.exist; expect(objectPath.get(obj, ['b','dot.dot'])).to.be.equal('ok'); }); it('should return the object if path is empty', function() { var obj = getTestObj(); expect(objectPath.ensureExists(obj, [], 'test')).to.have.property('a', 'b'); }); it('Issue #26', function() { var any = {}; objectPath.ensureExists(any, ['1','1'], {}); expect(any).to.be.an('object'); expect(any[1]).to.be.an('object'); expect(any[1][1]).to.be.an('object'); }); }); describe('coalesce', function(){ it('should return the first non-undefined value', function(){ var obj = { should: {have: 'prop'} }; expect(objectPath.coalesce(obj, [ 'doesnt.exist', ['might','not','exist'], 'should.have' ])).to.equal('prop'); }); it('should work with falsy values (null, 0, \'\', false)', function(){ var obj = { is: { false: false, null: null, empty: '', zero: 0 } }; expect(objectPath.coalesce(obj, [ 'doesnt.exist', 'is.zero' ])).to.equal(0); expect(objectPath.coalesce(obj, [ 'doesnt.exist', 'is.false' ])).to.equal(false); expect(objectPath.coalesce(obj, [ 'doesnt.exist', 'is.null' ])).to.equal(null); expect(objectPath.coalesce(obj, [ 'doesnt.exist', 'is.empty' ])).to.equal(''); }); it('returns defaultValue if no paths found', function(){ var obj = { doesnt: 'matter' }; expect(objectPath.coalesce(obj, ['some.inexistant','path',['on','object']], 'false')).to.equal('false'); }); it('works with unicode and dot keys', function(){ var obj = { '\u7591': true, 'dot.dot': false }; expect(objectPath.coalesce(obj, ['1', '\u7591', 'a.b'])).to.equal(true); expect(objectPath.coalesce(obj, ['1', ['dot.dot'], '\u7591'])).to.equal(false); }); }); describe('empty', function(){ it('should ignore invalid arguments safely', function(){ var obj = {}; expect(objectPath.empty()).to.equal(void 0); expect(objectPath.empty(obj, 'path')).to.equal(void 0); expect(objectPath.empty(obj, '')).to.equal(void 0); obj.path = true; expect(objectPath.empty(obj, 'inexistant')).to.equal(void 0); expect(objectPath.empty(null, 'path')).to.equal(void 0); expect(objectPath.empty(void 0, 'path')).to.equal(void 0); }); it('should empty each path according to their types', function(){ function Instance(){ this.notOwn = true; } /*istanbul ignore next: not part of code */ Instance.prototype.test = function(){}; /*istanbul ignore next: not part of code */ Instance.prototype.arr = []; var obj = { string: 'some string', array: ['some','array',[1,2,3]], number: 21, boolean: true, object: { some:'property', sub: { 'property': true }, nullProp: null, undefinedProp: void 0 }, instance: new Instance() }; /*istanbul ignore next: not part of code */ obj['function'] = function(){}; objectPath.empty(obj, ['array','2']); expect(obj.array[2]).to.deep.equal([]); objectPath.empty(obj, 'object.sub'); expect(obj.object.sub).to.deep.equal({}); objectPath.empty(obj, 'object.nullProp'); expect(obj.object.nullProp).to.equal(null); objectPath.empty(obj, 'object.undefinedProp'); expect(obj.object.undefinedProp).to.equal(void 0); expect(obj.object).to.have.property('undefinedProp'); objectPath.empty(obj, 'object.notAProp'); expect(obj.object.notAProp).to.equal(void 0); expect(obj.object).to.not.have.property('notAProp'); objectPath.empty(obj, 'instance.test'); //instance.test is not own property, so it shouldn't be emptied expect(obj.instance.test).to.be.a('function'); expect(Instance.prototype.test).to.be.a('function'); objectPath.empty(obj, 'string'); objectPath.empty(obj, 'number'); objectPath.empty(obj, 'boolean'); objectPath.empty(obj, 'function'); objectPath.empty(obj, 'array'); objectPath.empty(obj, 'object'); objectPath.empty(obj, 'instance'); expect(obj.string).to.equal(''); expect(obj.array).to.deep.equal([]); expect(obj.number).to.equal(0); expect(obj.boolean).to.equal(false); expect(obj.object).to.deep.equal({}); expect(obj.instance.notOwn).to.be.an('undefined'); expect(obj.instance.arr).to.be.an('array'); expect(obj['function']).to.equal(null); }); }); describe('del', function(){ it('should work with number path', function(){ var obj = getTestObj(); objectPath.del(obj.b.d, 1); expect(obj.b.d).to.deep.equal(['a']); }); it('should remove null and undefined props (but not explode on nested)', function(){ var obj = { nullProp: null, undefinedProp: void 0 }; expect(obj).to.have.property('nullProp'); expect(obj).to.have.property('undefinedProp'); objectPath.del(obj, 'nullProp.foo'); objectPath.del(obj, 'undefinedProp.bar'); expect(obj).to.have.property('nullProp'); expect(obj).to.have.property('undefinedProp'); expect(obj).to.deep.equal({ nullProp: null, undefinedProp: void 0 }); objectPath.del(obj, 'nullProp'); objectPath.del(obj, 'undefinedProp'); expect(obj).to.not.have.property('nullProp'); expect(obj).to.not.have.property('undefinedProp'); expect(obj).to.deep.equal({}); }); it('should delete deep paths', function(){ var obj = getTestObj(); expect(objectPath.del(obj)).to.be.equal(obj); objectPath.set(obj, 'b.g.1.0', 'test'); objectPath.set(obj, 'b.g.1.1', 'test'); objectPath.set(obj, 'b.h.az', 'test'); objectPath.set(obj, 'b.\ubeef', 'test'); objectPath.set(obj, ['b','dot.dot'], 'test'); expect(obj).to.have.deep.property('b.g.1.0','test'); expect(obj).to.have.deep.property('b.g.1.1','test'); expect(obj).to.have.deep.property('b.h.az','test'); expect(obj).to.have.deep.property('b.\ubeef','test'); objectPath.del(obj, 'b.h.az'); expect(obj).to.not.have.deep.property('b.h.az'); expect(obj).to.have.deep.property('b.h'); objectPath.del(obj, 'b.g.1.1'); expect(obj).to.not.have.deep.property('b.g.1.1'); expect(obj).to.have.deep.property('b.g.1.0','test'); objectPath.del(obj, 'b.\ubeef'); expect(obj).to.not.have.deep.property('b.\ubeef'); objectPath.del(obj, ['b','dot.dot']); expect(objectPath.get(obj, ['b','dot.dot'])).to.be.equal(void 0); objectPath.del(obj, ['b','g','1','0']); expect(obj).to.not.have.deep.property('b.g.1.0'); expect(obj).to.have.deep.property('b.g.1'); expect(objectPath.del(obj, ['b'])).to.not.have.deep.property('b.g'); expect(obj).to.be.deep.equal({'a':'b'}); }); it('should remove items from existing array', function(){ var obj = getTestObj(); objectPath.del(obj, 'b.d.0'); expect(obj.b.d).to.have.length(1); expect(obj.b.d).to.be.deep.equal(['b']); objectPath.del(obj, 'b.d.0'); expect(obj.b.d).to.have.length(0); expect(obj.b.d).to.be.deep.equal([]); }); }); describe('insert', function(){ it('should insert value into existing array', function(){ var obj = getTestObj(); objectPath.insert(obj, 'b.c', 'asdf'); expect(obj).to.have.deep.property('b.c.0', 'asdf'); expect(obj).to.not.have.deep.property('b.c.1'); }); it('should create intermediary array', function(){ var obj = getTestObj(); objectPath.insert(obj, 'b.c.0', 'asdf'); expect(obj).to.have.deep.property('b.c.0.0', 'asdf'); }); it('should insert in another index', function(){ var obj = getTestObj(); objectPath.insert(obj, 'b.d', 'asdf', 1); expect(obj).to.have.deep.property('b.d.1', 'asdf'); expect(obj).to.have.deep.property('b.d.0', 'a'); expect(obj).to.have.deep.property('b.d.2', 'b'); }); it('should handle sparse array', function(){ var obj = getTestObj(); obj.b.d = new Array(4); obj.b.d[0] = 'a'; obj.b.d[1] = 'b'; objectPath.insert(obj, 'b.d', 'asdf', 3); expect(obj.b.d).to.have.members([ 'a', 'b', , , 'asdf' ]); }); }); describe('has', function () { it('should return false for empty object', function () { expect(objectPath.has({}, 'a')).to.be.equal(false); }); it('should handle empty paths properly', function () { var obj = getTestObj(); expect(objectPath.has(obj, '')).to.be.equal(false); expect(objectPath.has(obj, [''])).to.be.equal(false); obj[''] = 1 expect(objectPath.has(obj, '')).to.be.equal(true); expect(objectPath.has(obj, [''])).to.be.equal(true); expect(objectPath.has(obj, [])).to.be.equal(true); expect(objectPath.has(null, [])).to.be.equal(false); }); it('should test under shallow object', function() { var obj = getTestObj(); expect(objectPath.has(obj, 'a')).to.be.equal(true); expect(objectPath.has(obj, ['a'])).to.be.equal(true); expect(objectPath.has(obj, 'z')).to.be.equal(false); expect(objectPath.has(obj, ['z'])).to.be.equal(false); }); it('should work with number path', function() { var obj = getTestObj(); expect(objectPath.has(obj.b.d, 0)).to.be.equal(true); expect(objectPath.has(obj.b, 0)).to.be.equal(false); expect(objectPath.has(obj.b.d, 10)).to.be.equal(false); expect(objectPath.has(obj.b, 10)).to.be.equal(false); }); it('should test under deep object', function() { var obj = getTestObj(); expect(objectPath.has(obj, 'b.f')).to.be.equal(true); expect(objectPath.has(obj, ['b','f'])).to.be.equal(true); expect(objectPath.has(obj, 'b.g')).to.be.equal(false); expect(objectPath.has(obj, ['b','g'])).to.be.equal(false); }); it('should test value under array', function() { var obj = { b: ['a'] }; obj.b[3] = {o: 'a'} expect(objectPath.has(obj, 'b.0')).to.be.equal(true); expect(objectPath.has(obj, 'b.1')).to.be.equal(true); expect(objectPath.has(obj, 'b.3.o')).to.be.equal(true); expect(objectPath.has(obj, 'b.3.qwe')).to.be.equal(false); expect(objectPath.has(obj, 'b.4')).to.be.equal(false); }); it('should test the value under array deep', function() { var obj = getTestObj(); expect(objectPath.has(obj, 'b.e.1.f')).to.be.equal(true); expect(objectPath.has(obj, ['b','e',1,'f'])).to.be.equal(true); expect(objectPath.has(obj, 'b.e.1.f.g.h.i')).to.be.equal(false); expect(objectPath.has(obj, ['b','e',1,'f','g','h','i'])).to.be.equal(false); }); it('should test the value under integer-like key', function() { var obj = { '1a': 'foo' }; expect(objectPath.has(obj, '1a')).to.be.equal(true); expect(objectPath.has(obj, ['1a'])).to.be.equal(true); }); it('should distinct nonexistent key and key = undefined', function() { var obj = {}; expect(objectPath.has(obj, 'key')).to.be.equal(false); obj.key = undefined; expect(objectPath.has(obj, 'key')).to.be.equal(true); }); it('should work with deep undefined/null values', function() { var obj = {}; expect(objectPath.has(obj, 'missing.test')).to.be.equal(false); obj.missing = null; expect(objectPath.has(obj, 'missing.test')).to.be.equal(false); obj.sparseArray = [1, undefined, 3] expect(objectPath.has(obj, 'sparseArray.1.test')).to.be.equal(false); }); }); describe('bind object', function () { // just get one scenario from each feature, so whole functionality is proxied well it('should return the value under shallow object', function() { var obj = getTestObj(); var model = objectPath(obj); expect(model.get('a')).to.be.equal('b'); expect(model.get(['a'])).to.be.equal('b'); }); it('should set value under shallow object', function() { var obj = getTestObj(); var model = objectPath(obj); model.set('c', {m: 'o'}); expect(obj).to.have.deep.property('c.m', 'o'); obj = getTestObj(); model = objectPath(obj); model.set(['c'], {m: 'o'}); expect(obj).to.have.deep.property('c.m', 'o'); }); it('should push value to existing array', function() { var obj = getTestObj(); var model = objectPath(obj); model.push('b.c', 'l'); expect(obj).to.have.deep.property('b.c.0', 'l'); obj = getTestObj(); model = objectPath(obj); model.push(['b','c'], 'l'); expect(obj).to.have.deep.property('b.c.0', 'l'); }); it('should create the path if it does not exists', function() { var obj = getTestObj(); var model = objectPath(obj); var oldVal = model.ensureExists('b.g.1.l', 'test'); expect(oldVal).to.not.exist; expect(obj).to.have.deep.property('b.g.1.l', 'test'); oldVal = model.ensureExists('b.g.1.l', 'test1'); expect(oldVal).to.be.equal('test'); expect(obj).to.have.deep.property('b.g.1.l', 'test'); }); it('should return the first non-undefined value', function(){ var obj = { should: {have: 'prop'} }; var model = objectPath(obj); expect(model.coalesce([ 'doesnt.exist', ['might','not','exist'], 'should.have' ])).to.equal('prop'); }); it('should empty each path according to their types', function(){ function Instance(){ this.notOwn = true; } /*istanbul ignore next: not part of code */ Instance.prototype.test = function(){}; /*istanbul ignore next: not part of code */ Instance.prototype.arr = []; var obj = { string: 'some string', array: ['some','array',[1,2,3]], number: 21, boolean: true, object: { some:'property', sub: { 'property': true } }, instance: new Instance() }; /*istanbul ignore next: not part of code */ obj['function'] = function(){}; var model = objectPath(obj); model.empty(['array','2']); expect(obj.array[2]).to.deep.equal([]); model.empty('object.sub'); expect(obj.object.sub).to.deep.equal({}); model.empty('instance.test'); //instance.test is not own property so it shouldn't be emptied expect(obj.instance.test).to.be.a('function'); expect(Instance.prototype.test).to.be.a('function'); model.empty('string'); model.empty('number'); model.empty('boolean'); model.empty('function'); model.empty('array'); model.empty('object'); model.empty('instance'); expect(obj.string).to.equal(''); expect(obj.array).to.deep.equal([]); expect(obj.number).to.equal(0); expect(obj.boolean).to.equal(false); expect(obj.object).to.deep.equal({}); expect(obj.instance.notOwn).to.be.an('undefined'); expect(obj.instance.arr).to.be.an('array'); expect(obj['function']).to.equal(null); }); it('should delete deep paths', function(){ var obj = getTestObj(); var model = objectPath(obj); expect(model.del()).to.be.equal(obj); model.set('b.g.1.0', 'test'); model.set('b.g.1.1', 'test'); model.set('b.h.az', 'test'); expect(obj).to.have.deep.property('b.g.1.0','test'); expect(obj).to.have.deep.property('b.g.1.1','test'); expect(obj).to.have.deep.property('b.h.az','test'); model.del('b.h.az'); expect(obj).to.not.have.deep.property('b.h.az'); expect(obj).to.have.deep.property('b.h'); model.del('b.g.1.1'); expect(obj).to.not.have.deep.property('b.g.1.1'); expect(obj).to.have.deep.property('b.g.1.0','test'); model.del(['b','g','1','0']); expect(obj).to.not.have.deep.property('b.g.1.0'); expect(obj).to.have.deep.property('b.g.1'); expect(model.del(['b'])).to.not.have.deep.property('b.g'); expect(obj).to.be.deep.equal({'a':'b'}); }); it('should insert value into existing array', function(){ var obj = getTestObj(); var model = objectPath(obj); model.insert('b.c', 'asdf'); expect(obj).to.have.deep.property('b.c.0', 'asdf'); expect(obj).to.not.have.deep.property('b.c.1'); }); it('should test under shallow object', function() { var obj = getTestObj(); var model = objectPath(obj); expect(model.has('a')).to.be.equal(true); expect(model.has(['a'])).to.be.equal(true); expect(model.has('z')).to.be.equal(false); expect(model.has(['z'])).to.be.equal(false); }); }); describe('Don\'t access not own properties [default]', function () { it('should not get a not own property', function() { var Obj = function() {}; Obj.prototype.notOwn = {a: 'a'}; var obj = new Obj(); expect(objectPath.get(obj, 'notOwn')).to.be.undefined }); it('should set a not own property on the instance (not the prototype)', function() { var proto = { notOwn: {} } var obj = Object.create(proto) objectPath.set(obj, 'notOwn.test', 'a'); expect(obj.notOwn.test).to.be.equal('a'); expect(proto.notOwn).to.be.deep.equal({}); }); it('has should return false on a not own property', function() { var proto = { notOwn: {a: 'a'} } var obj = Object.create(proto) expect(objectPath.has(obj, 'notOwn')).to.be.false; expect(objectPath.has(obj, 'notOwn.a')).to.be.false; }); it('empty should not empty on a not own property', function() { var proto = { notOwn: {a: 'a'} } var obj = Object.create(proto); objectPath.empty(obj, 'notOwn'); expect(proto.notOwn).to.be.deep.equal({a: 'a'}); expect(obj.notOwn).to.be.deep.equal({a: 'a'}); }); it('del should not delete not own property', function() { var proto = { notOwn: {a: 'a'} } var obj = Object.create(proto); objectPath.del(obj, 'notOwn.a'); expect(proto.notOwn).to.be.deep.equal({a: 'a'}); //expect(obj.notOwn).to.be.deep.equal({a: 'a'}); //objectPath.del(obj, 'notOwn'); //expect(proto).to.be.deep.equal({notOwn: {a: 'a'}}); //expect(obj).to.be.deep.equal({notOwn: {a: 'a'}}); }); }); describe('Access own properties [optional]', function () { it('should get a not own property', function() { var Obj = function() {}; Obj.prototype.notOwn = {a: 'a'}; var obj = new Obj(); expect(objectPath.withInheritedProps.get(obj, 'notOwn.a')).to.be.equal('a') }); it('should set a deep not own property on the prototype (if exists)', function() { var proto = { notOwn: {} } var obj = Object.create(proto) objectPath.withInheritedProps.set(obj, 'notOwn.test', 'a'); expect(obj.notOwn.test).to.be.equal('a'); expect(proto.notOwn).to.be.deep.equal({test: 'a'}); }); it('has should return true on a not own property', function() { var proto = { notOwn: {a: 'a'} } var obj = Object.create(proto) expect(objectPath.withInheritedProps.has(obj, 'notOwn')).to.be.true; expect(objectPath.withInheritedProps.has(obj, 'notOwn.a')).to.be.true; }); it('empty should empty a not own property', function() { var proto = { notOwn: {a: 'a'} } var obj = Object.create(proto); objectPath.withInheritedProps.empty(obj, 'notOwn'); expect(proto.notOwn).to.be.deep.equal({}); expect(obj.notOwn).to.be.deep.equal({}); }); it('del should delete a not own property', function() { var proto = { notOwn: {a: 'a'} } var obj = Object.create(proto); objectPath.withInheritedProps.del(obj, 'notOwn.a'); expect(proto.notOwn).to.be.deep.equal({}); //expect(obj.notOwn).to.be.deep.equal({}); objectPath.withInheritedProps.del(obj, 'notOwn'); //expect(proto).to.be.deep.equal({notOwn: {}}); //expect(obj).to.be.deep.equal({notOwn: {}}); }); }); package/benchmark.js000644 000765 000024 0000002117 12734466012013025 0ustar00000000 000000 var Benchpress = require('@mariocasciaro/benchpress') var benchmark = new Benchpress() var op = require('./') var testObj = { level1_a: { level2_a: { level3_a: { level4_a: { } } } } } var testObj2 benchmark .add('get existing', { iterations: 100000, fn: function() { op.get(testObj, ['level1_a', 'level2_a', 'level3_a', 'level4_a']) } }) .add('get non-existing', { iterations: 100000, fn: function() { op.get(testObj, ['level5_a']) } }) .add('push', { iterations: 100000, fn: function() { op.push(testObj, ['level1_a', 'level2_a', 'level3_a', 'level4_a', 'level5_a'], 'val') } }) .add('set non existing', { iterations: 100000, fn: function() { op.set(testObj2, ['level1_a', 'level2_b', 'level3_b', 'level4_b', 'level5_b'], 'val') }, beforeEach: function() { testObj2 = {} } }) .add('set existing', { iterations: 100000, fn: function() { op.set(testObj, ['level1_a', 'level2_a', 'level3_a', 'level4_a', 'level5_b'], 'val') } }) .run() package/index.js000644 000765 000024 0000016126 13055004215012175 0ustar00000000 000000 (function (root, factory){ 'use strict'; /*istanbul ignore next:cant test*/ if (typeof module === 'object' && typeof module.exports === 'object') { module.exports = factory(); } else if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else { // Browser globals root.objectPath = factory(); } })(this, function(){ 'use strict'; 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; }, {}); }; function hasShallowProperty(obj, prop) { return (options.includeInheritedProps || (typeof prop === 'number' && Array.isArray(obj)) || hasOwnProperty(obj, prop)) } function getShallowProperty(obj, prop) { if (hasShallowProperty(obj, prop)) { return obj[prop]; } } 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 = getShallowProperty(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 = getShallowProperty(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]); 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; }); package/component.json000644 000765 000024 0000000711 12513674167013437 0ustar00000000 000000 { "name": "object-path", "description": "Access deep properties using a path", "version": "0.9.2", "author": { "name": "Mario Casciaro" }, "homepage": "https://github.com/mariocasciaro/object-path", "repository": { "type": "git", "url": "git://github.com/mariocasciaro/object-path.git" }, "main": "index.js", "scripts": ["index.js"], "keywords": [ "deep", "path", "access", "bean" ], "license": "MIT" } package/.travis.yml000755 000765 000024 0000000422 12734456363012656 0ustar00000000 000000 sudo: false language: node_js node_js: - "0.12" - "0.10" - "4" - "6" 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 package/bower.json000644 000765 000024 0000000305 12732525605012545 0ustar00000000 000000 { "name": "object-path", "main": "index.js", "keywords": [ "deep", "path", "access", "bean", "object" ], "ignore" : [ "test.js", "coverage", "*.yml" ] }