├── .gitignore ├── .travis.yml ├── test ├── find.test.js ├── paths.test.js ├── flatten.test.js ├── set.test.js ├── get.test.js └── transform.test.js ├── package.json ├── LICENSE ├── README.md └── dottie.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | - "4.0" 5 | - "iojs" 6 | -------------------------------------------------------------------------------- /test/find.test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js") 2 | , dottie = require('../dottie'); 3 | 4 | describe("dottie.find", function () { 5 | var data = { 6 | 'foo': { 7 | 'bar': 'baz' 8 | }, 9 | 'zoo': 'lander' 10 | }; 11 | 12 | it("should get first-level values", function () { 13 | expect(dottie.find('zoo', data)).to.equal('lander'); 14 | }); 15 | 16 | it("should get nested-level values", function () { 17 | expect(dottie.find('foo.bar', data)).to.equal('baz'); 18 | }); 19 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dottie", 3 | "version": "1.1.0", 4 | "devDependencies": { 5 | "expect.js": "~0.2.0", 6 | "mocha": "~1.14.0" 7 | }, 8 | "description": "Fast and safe nested object access and manipulation in JavaScript", 9 | "author": "Mick Hansen ", 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/mickhansen/dottie.js.git" 13 | }, 14 | "main": "dottie.js", 15 | "scripts": { 16 | "test": "./node_modules/mocha/bin/mocha -t 5000 -s 100 --reporter spec test/" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/paths.test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js") 2 | , dottie = require("../dottie"); 3 | 4 | describe("dottie.paths", function() { 5 | it("throws for non-objects", function() { 6 | expect(function() { 7 | dottie.paths("no object") 8 | }).to.throwError(); 9 | }); 10 | 11 | it("returns the keys of a flat object", function() { 12 | expect(dottie.paths({ a: 1, b: 2 })).to.eql(["a", "b"]); 13 | }); 14 | 15 | it("returns the paths of a deeply nested object", function() { 16 | var object = { 17 | a: 1, 18 | b: { 19 | c: 2, 20 | d: { e: 3 } 21 | } 22 | }; 23 | 24 | expect(dottie.paths(object)).to.eql(["a", "b.c", "b.d.e"]); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/flatten.test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js") 2 | , dottie = require('../dottie'); 3 | 4 | describe("dottie.flatten", function () { 5 | it('should handle a basic nested structure without arrays', function () { 6 | var data = { 7 | 'foo': { 8 | 'bar': 'baa', 9 | 'baz': { 10 | 'foo': 'bar' 11 | } 12 | }, 13 | 'bar': 'baz' 14 | }; 15 | 16 | expect(dottie.flatten(data)).to.eql({ 17 | 'foo.bar': 'baa', 18 | 'foo.baz.foo': 'bar', 19 | 'bar': 'baz' 20 | }); 21 | }); 22 | 23 | it('should be possible to define your own seperator', function () { 24 | var data = { 25 | 'foo': { 26 | 'bar': 'baa', 27 | 'baz': { 28 | 'foo': 'bar' 29 | } 30 | }, 31 | 'bar': 'baz' 32 | }; 33 | 34 | expect(dottie.flatten(data, '_')).to.eql({ 35 | 'foo_bar': 'baa', 36 | 'foo_baz_foo': 'bar', 37 | 'bar': 'baz' 38 | }); 39 | }); 40 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013-2014 Mick Hansen. http://mhansen.io 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. 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/set.test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js") 2 | , dottie = require('../dottie'); 3 | 4 | describe("dottie.set", function () { 5 | var data = { 6 | 'foo': { 7 | 'bar': 'baa' 8 | } 9 | }; 10 | 11 | it("should set nested values on existing structure", function () { 12 | dottie.set(data, 'foo.bar', 'baz'); 13 | expect(data.foo.bar).to.equal('baz'); 14 | }); 15 | 16 | it("should create nested structure if not existing", function () { 17 | dottie.set(data, 'level1.level2', 'foo'); 18 | expect(data.level1.level2).to.equal('foo'); 19 | expect(typeof data.level1).to.equal('object'); 20 | }); 21 | 22 | it("should handle setting a nested value on an undefined value (should convert undefined to object)", function () { 23 | var data = { 24 | 'values': undefined 25 | }; 26 | 27 | dottie.set(data, 'values.level1', 'foo'); 28 | expect(data.values.level1).to.equal('foo'); 29 | }); 30 | 31 | it('should be able to set with an array path', function () { 32 | dottie.set(data, ['some.dot.containing', 'value'], 'razzamataz'); 33 | expect(data['some.dot.containing'].value).to.equal('razzamataz'); 34 | }); 35 | 36 | it("should throw error when setting a nested value on an existing key with a non-object value", function() { 37 | expect(function () { 38 | dottie.set(data, 'foo.bar.baz', 'someValue'); 39 | }).to.throwError(); 40 | }); 41 | 42 | it('should overwrite a nested non-object value on force: true', function () { 43 | dottie.set(data, 'foo.bar.baz', 'someValue', { 44 | force: true 45 | }); 46 | expect(data.foo.bar.baz).to.equal('someValue'); 47 | }); 48 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/mickhansen/dottie.js.png)](https://travis-ci.org/mickhansen/dottie.js.png) 2 | 3 | Dottie helps you easily (and with sacrificing too much performance) look up and play with nested keys in objects, without them throwing up in your face. 4 | 5 | ## Install 6 | npm install dottie 7 | 8 | ## Usage 9 | For detailed usage, check source or tests. 10 | 11 | ### Get value 12 | Gets nested value, or undefined if unreachable, or a default value if passed. 13 | 14 | ```js 15 | var values = { 16 | some: { 17 | nested: { 18 | key: 'foobar'; 19 | } 20 | }, 21 | 'some.dot.included': { 22 | key: 'barfoo' 23 | } 24 | } 25 | 26 | dottie.get(values, 'some.nested.key'); // returns 'foobar' 27 | dottie.get(values, 'some.undefined.key'); // returns undefined 28 | dottie.get(values, 'some.undefined.key', 'defaultval'); // returns 'defaultval' 29 | dottie.get(values, ['some.dot.included', 'key']); // returns 'barfoo' 30 | ``` 31 | 32 | ### Set value 33 | Sets nested value, creates nested structure if needed 34 | 35 | ```js 36 | dottie.set(values, 'some.nested.value', someValue); 37 | dottie.set(values, ['some.dot.included', 'value'], someValue); 38 | dottie.set(values, 'some.nested.object', someValue, { 39 | force: true // force overwrite defined non-object keys into objects if needed 40 | }); 41 | ``` 42 | 43 | ### Transform object 44 | Transform object from keys with dottie notation to nested objects 45 | 46 | ```js 47 | var values = { 48 | 'user.name': 'Gummy Bear', 49 | 'user.email': 'gummybear@candymountain.com', 50 | 'user.professional.title': 'King', 51 | 'user.professional.employer': 'Candy Mountain' 52 | }; 53 | var transformed = dottie.transform(values); 54 | 55 | /* 56 | { 57 | user: { 58 | name: 'Gummy Bear', 59 | email: 'gummybear@candymountain.com', 60 | professional: { 61 | title: 'King', 62 | employer: 'Candy Mountain' 63 | } 64 | } 65 | } 66 | */ 67 | ``` 68 | 69 | #### With a custom delimiter 70 | 71 | ```js 72 | var values = { 73 | 'user_name': 'Mick Hansen', 74 | 'user_email': 'maker@mhansen.io' 75 | }; 76 | var transformed = dottie.transform(values, { delimiter: '_' }); 77 | 78 | /* 79 | { 80 | user: { 81 | name: 'Mick Hansen', 82 | email: 'maker@mhansen.io' 83 | } 84 | } 85 | */ 86 | ``` 87 | 88 | ### Get paths in object 89 | ```js 90 | var object = { 91 | a: 1, 92 | b: { 93 | c: 2, 94 | d: { e: 3 } 95 | } 96 | }; 97 | 98 | dottie.paths(object); // ["a", "b.c", "b.d.e"]; 99 | ``` 100 | 101 | ## Performance 102 | 103 | `0.3.1` and up ships with `dottie.memoizePath: true` by default, if this causes any bugs, please try setting it to false 104 | 105 | ## License 106 | 107 | [MIT](https://github.com/mickhansen/dottie.js/blob/master/LICENSE) 108 | -------------------------------------------------------------------------------- /test/get.test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js") 2 | , dottie = require('../dottie'); 3 | 4 | /* If people modify the array prototype Dottie should not be affected */ 5 | Array.prototype.getByName = function(name) { 6 | for (var i = 0, len = this.length; i < len; i++) { 7 | if (typeof this[i] != "object") continue; 8 | if (this[i].name === name) return this[i]; 9 | } 10 | }; 11 | 12 | Array.prototype.getByType = function(type) { 13 | var newvalues = []; 14 | for (var i = 0, len = this.length; i < len; i++) { 15 | if (typeof this[i] != "object") continue; 16 | if (this[i].type === type) newvalues.push(this[i]); 17 | } 18 | if (newvalues.length <= 0) newvalues = undefined; 19 | return newvalues; 20 | }; 21 | 22 | describe("dottie.get", function () { 23 | var flags = { 24 | memoizePath: [false, true] 25 | }; 26 | 27 | var data = { 28 | 'foo': { 29 | 'bar': 'baz' 30 | }, 31 | 'zoo': 'lander', 32 | 'false': { 33 | 'value': false 34 | }, 35 | 'null': { 36 | 'value': null 37 | }, 38 | 'nullvalue': null, 39 | 'nested.dot': { 40 | 'key': 'value' 41 | } 42 | }; 43 | 44 | Object.keys(flags).forEach(function (flag) { 45 | flags[flag].forEach(function (value) { 46 | describe(flag+': '+value, function () { 47 | beforeEach(function () { 48 | dottie[flag] = value; 49 | }); 50 | 51 | it('should return undefined if value is undefined', function () { 52 | expect(dottie.get(undefined, 'foo')).to.equal(undefined); 53 | expect(dottie.get(undefined, 'foo')).to.equal(undefined); 54 | }); 55 | 56 | it("should get first-level values", function () { 57 | expect(dottie.get(data, 'zoo')).to.equal('lander'); 58 | expect(dottie.get(data, 'zoo')).to.equal('lander'); 59 | }); 60 | 61 | it("should get nested-level values", function () { 62 | expect(dottie.get(data, 'foo.bar')).to.equal('baz'); 63 | }); 64 | 65 | it("should get nested-level values multiple times", function () { 66 | expect(dottie.get(data, 'foo.bar')).to.equal('baz'); 67 | expect(dottie.get(data, 'foo.bar')).to.equal('baz'); 68 | expect(dottie.get(data, 'foo.bar')).to.equal('baz'); 69 | expect(dottie.get(data, 'foo.bar')).to.equal('baz'); 70 | }); 71 | 72 | it("should return undefined if not found", function () { 73 | expect(dottie.get(data, 'foo.zoo.lander')).to.equal(undefined); 74 | }); 75 | 76 | it("should return false values properly", function () { 77 | expect(dottie.get(data, 'false.value')).to.equal(false); 78 | }); 79 | 80 | it("should return the default value passed in if not found", function() { 81 | expect(dottie.get(data, 'foo.zoo.lander', 'novalue')).to.equal('novalue'); 82 | }); 83 | 84 | it("should return null of the value is null and not undefined", function() { 85 | expect(dottie.get(data, 'null.value')).to.equal(null); 86 | }); 87 | 88 | it("should return undefined if accessing a child property of a null value", function () { 89 | expect(dottie.get(data, 'nullvalue.childProp')).to.equal(undefined); 90 | expect(dottie.get(data, 'null.value.childProp')).to.equal(undefined); 91 | }); 92 | 93 | it("should return undefined if accessing a child property of a string value", function () { 94 | expect(dottie.get(data, 'foo.bar.baz.yapa')).to.equal(undefined); 95 | }); 96 | 97 | it('should get nested values with keys that have dots', function () { 98 | expect(dottie.get(data, ['nested.dot', 'key'])).to.equal('value'); 99 | }); 100 | }); 101 | }); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /test/transform.test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js") 2 | , dottie = require('../dottie'); 3 | 4 | /* If people modify the array prototype Dottie should not be affected */ 5 | Array.prototype.getByName = function(name) { 6 | for (var i = 0, len = this.length; i < len; i++) { 7 | if (typeof this[i] != "object") continue; 8 | if (this[i].name === name) return this[i]; 9 | 10 | } 11 | }; 12 | Array.prototype.getByType = function(type) { 13 | var newvalues = []; 14 | for (var i = 0, len = this.length; i < len; i++) { 15 | if (typeof this[i] != "object") continue; 16 | if (this[i].type === type) newvalues.push(this[i]); 17 | } 18 | if (newvalues.length <= 0) newvalues = undefined; 19 | return newvalues; 20 | }; 21 | 22 | describe("dottie.transform", function () { 23 | it("should create a properly nested object from a basic dottie notated set of keys", function () { 24 | var values = { 25 | 'user.name': 'John Doe', 26 | 'user.email': 'jd@foobar.com', 27 | 'user.location.country': 'Zanzibar', 28 | 'user.location.city': 'Zanzibar City' 29 | }; 30 | 31 | var transformed = dottie.transform(values); 32 | 33 | expect(transformed.user).not.to.be(undefined); 34 | expect(transformed.user.location).not.to.be(undefined); 35 | 36 | expect(transformed.user).to.be.an('object'); 37 | expect(transformed.user.location).to.be.an('object'); 38 | 39 | expect(transformed.user.email).to.equal('jd@foobar.com'); 40 | expect(transformed.user.location.city).to.equal('Zanzibar City'); 41 | }); 42 | 43 | it("should be able to handle a mixture of nested properties and dottie notated set of keys", function () { 44 | var values = { 45 | user: { 46 | name: 'John Doe' 47 | }, 48 | 'user.email': 'jd@foobar.com', 49 | 'user.location.country': 'Zanzibar', 50 | 'user.location.city': 'Zanzibar City', 51 | 'project.title': 'dottie' 52 | }; 53 | 54 | var transformed = dottie.transform(values); 55 | 56 | expect(transformed.user).not.to.be(undefined); 57 | expect(transformed.user.location).not.to.be(undefined); 58 | expect(transformed.project).not.to.be(undefined); 59 | 60 | expect(transformed.user).to.be.an('object'); 61 | expect(transformed.user.location).to.be.an('object'); 62 | expect(transformed.project).to.be.an('object'); 63 | 64 | expect(transformed.user.email).to.equal('jd@foobar.com'); 65 | expect(transformed.user.location.city).to.equal('Zanzibar City'); 66 | expect(transformed.project.title).to.equal('dottie'); 67 | }); 68 | 69 | it("should be able to handle base level properties together with nested", function () { 70 | var values = { 71 | 'customer.name': 'John Doe', 72 | 'customer.age': 15, 73 | 'client': 'Lolcat' 74 | }; 75 | 76 | var transformed = dottie.transform(values); 77 | 78 | expect(transformed.client).not.to.be(undefined); 79 | expect(transformed.hasOwnProperty('client')).to.be.ok(); // Ensure that the property is actually on the object itself, not on the prototype. 80 | expect(transformed.customer).not.to.be(undefined); 81 | 82 | expect(transformed.customer).to.be.an('object'); 83 | 84 | expect(transformed.client).to.equal('Lolcat'); 85 | expect(transformed.customer.name).to.equal('John Doe'); 86 | expect(transformed.customer.age).to.equal(15); 87 | }); 88 | 89 | it("should be able to handle null valued properties, not assigning nested level objects", function() { 90 | var values = { 91 | 'section.id': 20, 92 | 'section.layout': null, 93 | 'section.layout.id': null, 94 | 'section.layout.name': null 95 | }; 96 | 97 | var transformed = dottie.transform(values); 98 | 99 | expect(transformed.section.layout).to.be(null); 100 | expect(transformed['section.layout.id']).to.be(undefined); 101 | expect(transformed['section.layout.name']).to.be(undefined); 102 | }); 103 | 104 | it("supports arrays", function () { 105 | var values = [ 106 | { 107 | 'customer.name': 'John Doe', 108 | 'customer.age': 15, 109 | 'client': 'Lolcat' 110 | }, 111 | { 112 | 'client.name': 'John Doe', 113 | 'client.age': 15, 114 | 'customer': 'Lolcat' 115 | } 116 | ]; 117 | 118 | var transformed = dottie.transform(values); 119 | expect(transformed[0].customer.name).to.equal('John Doe'); 120 | expect(transformed[1].client.name).to.equal('John Doe'); 121 | }); 122 | 123 | it("supports custom delimiters", function () { 124 | var values = { 125 | user: { 126 | name: 'John Doe' 127 | }, 128 | 'user_email': 'jd@foobar.com', 129 | 'user_location_country': 'Zanzibar', 130 | 'user_location_city': 'Zanzibar City', 131 | 'project_title': 'dottie' 132 | }; 133 | 134 | var transformed = dottie.transform(values, { delimiter: '_' }); 135 | 136 | expect(transformed.user).not.to.be(undefined); 137 | expect(transformed.user.location).not.to.be(undefined); 138 | expect(transformed.project).not.to.be(undefined); 139 | 140 | expect(transformed.user).to.be.an('object'); 141 | expect(transformed.user.location).to.be.an('object'); 142 | expect(transformed.project).to.be.an('object'); 143 | 144 | expect(transformed.user.email).to.equal('jd@foobar.com'); 145 | expect(transformed.user.location.city).to.equal('Zanzibar City'); 146 | expect(transformed.project.title).to.equal('dottie'); 147 | }); 148 | }); 149 | -------------------------------------------------------------------------------- /dottie.js: -------------------------------------------------------------------------------- 1 | (function(undefined) { 2 | var root = this; 3 | 4 | // Weird IE shit, objects do not have hasOwn, but the prototype does... 5 | var hasOwnProp = Object.prototype.hasOwnProperty; 6 | 7 | // Object cloning function, uses jQuery/Underscore/Object.create depending on what's available 8 | 9 | var clone = function (object) { 10 | if (typeof Object.hasOwnProperty !== 'undefined') { 11 | var target = {}; 12 | for (var i in object) { 13 | if (hasOwnProp.call(object, i)) { 14 | target[i] = object[i]; 15 | } 16 | } 17 | return target; 18 | } 19 | if (typeof jQuery !== 'undefined') { 20 | return jQuery.extend({}, object); 21 | } 22 | if (typeof _ !== 'undefined') { 23 | return _.extend({}, object); 24 | } 25 | }; 26 | 27 | var Dottie = function() { 28 | var args = Array.prototype.slice.call(arguments); 29 | 30 | if (args.length == 2) { 31 | return Dottie.find.apply(this, args); 32 | } 33 | return Dottie.transform.apply(this, args); 34 | }; 35 | 36 | // Legacy syntax, changed syntax to have get/set be similar in arg order 37 | Dottie.find = function(path, object) { 38 | return Dottie.get(object, path); 39 | }; 40 | 41 | // Dottie memoization flag 42 | Dottie.memoizePath = true; 43 | var memoized = {}; 44 | 45 | // Traverse object according to path, return value if found - Return undefined if destination is unreachable 46 | Dottie.get = function(object, path, defaultVal) { 47 | if ((object === undefined) || (object === null)) return defaultVal; 48 | 49 | var names; 50 | 51 | if (typeof path === "string") { 52 | if (Dottie.memoizePath) { 53 | if (memoized[path]) { 54 | names = memoized[path].slice(0); 55 | } else { 56 | names = path.split('.').reverse(); 57 | memoized[path] = names.slice(0); 58 | } 59 | } else { 60 | names = path.split('.').reverse(); 61 | } 62 | } else if (Array.isArray(path)) { 63 | names = path.reverse(); 64 | } 65 | 66 | while (names.length && (object = object[names.pop()]) !== undefined && object !== null); 67 | 68 | // Handle cases where accessing a childprop of a null value 69 | if (object === null && names.length) object = undefined; 70 | 71 | return (object === undefined ? defaultVal : object); 72 | }; 73 | 74 | Dottie.exists = function(object, path) { 75 | return Dottie.get(object, path) !== undefined; 76 | }; 77 | 78 | // Set nested value 79 | Dottie.set = function(object, path, value, options) { 80 | var pieces = Array.isArray(path) ? path : path.split('.'), current = object, piece, length = pieces.length; 81 | 82 | if (typeof current !== 'object') { 83 | throw new Error('Parent is not an object.'); 84 | } 85 | 86 | for (var index = 0; index < length; index++) { 87 | piece = pieces[index]; 88 | 89 | // Create namespace (object) where none exists. 90 | // If `force === true`, bruteforce the path without throwing errors. 91 | if (!hasOwnProp.call(current, piece) || current[piece] === undefined || (typeof current[piece] !== 'object' && options && options.force === true)) { 92 | current[piece] = {}; 93 | } 94 | 95 | if (index == (length - 1)) { 96 | // Set final value 97 | current[piece] = value; 98 | } else { 99 | // We do not overwrite existing path pieces by default 100 | if (typeof current[piece] !== 'object') { 101 | throw new Error('Target key "' + piece + '" is not suitable for a nested value. (It is in use as non-object. Set `force` to `true` to override.)'); 102 | } 103 | 104 | // Traverse next in path 105 | current = current[piece]; 106 | } 107 | } 108 | 109 | // Is there any case when this is relevant? It's also the last line in the above for-loop 110 | current[piece] = value; 111 | }; 112 | 113 | // Set default nested value 114 | Dottie['default'] = function(object, path, value) { 115 | if (Dottie.get(object, path) === undefined) { 116 | Dottie.set(object, path, value); 117 | } 118 | }; 119 | 120 | // Transform unnested object with .-seperated keys into a nested object. 121 | Dottie.transform = function Dottie$transformfunction(object, options) { 122 | if (Array.isArray(object)) { 123 | return object.map(function(o) { 124 | return Dottie.transform(o, options); 125 | }); 126 | } 127 | 128 | options = options || {}; 129 | options.delimiter = options.delimiter || '.'; 130 | 131 | var pieces 132 | , piecesLength 133 | , piece 134 | , current 135 | , transformed = {} 136 | , key 137 | , keys = Object.keys(object) 138 | , length = keys.length 139 | , i; 140 | 141 | for (i = 0; i < length; i++) { 142 | key = keys[i]; 143 | 144 | if (key.indexOf(options.delimiter) !== -1) { 145 | pieces = key.split(options.delimiter); 146 | piecesLength = pieces.length; 147 | current = transformed; 148 | 149 | for (var index = 0; index < piecesLength; index++) { 150 | piece = pieces[index]; 151 | if (index != (piecesLength - 1) && !current.hasOwnProperty(piece)) { 152 | current[piece] = {}; 153 | } 154 | 155 | if (index == (piecesLength - 1)) { 156 | current[piece] = object[key]; 157 | } 158 | 159 | current = current[piece]; 160 | if (current === null) { 161 | break; 162 | } 163 | } 164 | } else { 165 | transformed[key] = object[key]; 166 | } 167 | } 168 | 169 | return transformed; 170 | }; 171 | 172 | Dottie.flatten = function(object, seperator) { 173 | if (typeof seperator === "undefined") seperator = '.'; 174 | var flattened = {} 175 | , current 176 | , nested; 177 | 178 | for (var key in object) { 179 | if (hasOwnProp.call(object, key)) { 180 | current = object[key]; 181 | if (Object.prototype.toString.call(current) === "[object Object]") { 182 | nested = Dottie.flatten(current, seperator); 183 | 184 | for (var _key in nested) { 185 | flattened[key+seperator+_key] = nested[_key]; 186 | } 187 | } else { 188 | flattened[key] = current; 189 | } 190 | } 191 | } 192 | 193 | return flattened; 194 | }; 195 | 196 | Dottie.paths = function(object, prefixes) { 197 | var paths = []; 198 | var value; 199 | var key; 200 | 201 | prefixes = prefixes || []; 202 | 203 | if (typeof object === 'object') { 204 | for (key in object) { 205 | value = object[key]; 206 | 207 | if (typeof value === 'object') { 208 | paths = paths.concat(Dottie.paths(value, prefixes.concat([key]))); 209 | } else { 210 | paths.push(prefixes.concat(key).join('.')); 211 | } 212 | } 213 | } else { 214 | throw new Error('Paths was called with non-object argument.'); 215 | } 216 | 217 | return paths; 218 | }; 219 | 220 | if (typeof module !== 'undefined' && module.exports) { 221 | exports = module.exports = Dottie; 222 | } else { 223 | root['Dottie'] = Dottie; 224 | root['Dot'] = Dottie; //BC 225 | 226 | if (typeof define === "function") { 227 | define([], function () { return Dottie; }); 228 | } 229 | } 230 | })(); 231 | --------------------------------------------------------------------------------