├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── .yarnrc.yml ├── CHANGELOG.md ├── MIT-LICENSE ├── README.md ├── bin └── dot-object ├── bower.json ├── dist ├── dot-object.js └── dot-object.min.js ├── gulpfile.js ├── index.js ├── package.json ├── src ├── dot-object.js ├── footer.tpl └── header.tpl └── test ├── _process.js ├── array_notation.js ├── copy.js ├── data ├── array_deep_bug.json ├── array_deep_bug2.json ├── empty_array.json ├── empty_object.json ├── index.js ├── object_deep_numeric_keys.json └── object_deep_numeric_keys2.json ├── dot-json.js ├── dot.js ├── fixtures └── package.json ├── merge.js ├── move.js ├── override.js ├── pick.js ├── remove.js ├── str.js ├── test_data.js ├── test_transforms.js ├── transfer.js ├── transforms ├── contact.json └── twitter.json └── useArray.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "env": { 4 | "node": true, 5 | "mocha": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ATTIC 2 | .c9/ 3 | .idea 4 | yarn.lock 5 | node_modules 6 | *.log 7 | *.swp 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.ts 3 | ATTIC 4 | lib 5 | Gruntfile.js 6 | node_modules 7 | test 8 | src 9 | bower.json 10 | gulpfile.js 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "stable" 5 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | yarnPath: .yarn/releases/yarn-4.1.1.cjs 2 | nodeLinker: node-modules 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | ## 2024-04-19 Version 2.1.5 4 | * [[`a23221d367408`](https://github.com/rhalff/dot-object/commit/a23221d367408)] - Add LICENSE property to package.json (Fixed by denniskim #54) 5 | * 6 | ## 2020-09-10 Version 2.1.4 7 | * [[`94b9eb8a2d`](https://github.com/rhalff/dot-object/commit/94b9eb8a2d)] - Fix parsing of array paths for non standard separators (Fixed by boidolr #58) 8 | 9 | ## 2020-02-16 Version 2.1.3 10 | * fix possible pollution of prototype for paths containing __proto__ 11 | 12 | ## 2019-11-02 Version 2.1.1 13 | * fix undefined key with root level array. 14 | 15 | ## 2019-11-02 Version 2.1.1 16 | * Wrong array conversion when brackets are used (Reported by vladshcherbin #27) 17 | 18 | ## 2019-11-02 Version 2.1.0 19 | * fix delete function not being wrapped. (Reported by murphyke #40) 20 | 21 | ## 2019-11-02 Version 2.0.0 22 | * [[`2cb41bbd1b`](https://github.com/rhalff/dot-object/commit/2cb41bbd1b)] - Add useBrackets option for the .dot() method (by z1m1n #42) 23 | * dot() now writes brackets by default (possibly breaking change). 24 | Use Dot.useBrackets = false to keep the old behavior 25 | 26 | ## 2019-07-29 Version 1.9.0 27 | * allows allow to process root level property using dot.object 28 | 29 | ## 2019-07-18 Version 1.8.1 30 | * always allow to set root level property using dot.str 31 | 32 | ## 2019-07-18 Version 1.8.0 33 | * [[`cdc691424b`](https://github.com/rhalff/dot-object/commit/cdc691424b)] - Options to remove array elements and reindex the array. (Fixed by MechJosh0 #36) 34 | 35 | ## 2018-10-26 Version 1.7.1 36 | * [[`e1bb99c83e`](https://github.com/rhalff/dot-object/commit/e1bb99c83e)] - Fix isIndex numeric key matching. (Fixed by mrdivyansh #31) 37 | 38 | ## 2017-09-20 Version 1.7.0 39 | * let .dot and .object understand empty objects / arrays 40 | 41 | ## 2017-07-27 Version 1.6.0 42 | * implemented keepArray 43 | 44 | ## 2016-09-29 Version 1.5.4 45 | * update dist 46 | 47 | ## 2016-09-29, Version 1.5.3 48 | * Fix override bug in str() 49 | 50 | ## 2016-08-04, Version 1.5.0 51 | * [[`a7e948f2fa`](https://github.com/rhalff/dot-object/commit/a7e948f2fa)] - Ensure we only process true Arrays and Objects. (Fixed by MechJosh0 #15) 52 | 53 | ## 2016-02-14, Version 1.4.0 54 | * add transform() method 55 | * use [standard](https://github.com/feross/standard/) style 56 | 57 | ## 2015-11-17, Version 1.3.1 58 | * [[`e46da6ffc0`](https://github.com/rhalff/dot-object/commit/e46da6ffc0)] - Fix deep array bug. (Reported by ami44 #10) 59 | 60 | ## 2015-10-01, Version 1.3.0 61 | * [[`baa42022bf`](https://github.com/rhalff/dot-object/commit/baa42022bf)] - Adds a parameter (useArray) to allow converting object without using arrays. (Thomas Moiron) 62 | 63 | ## 2015-09-07, Version 1.2.0 64 | * allow override even when target is non-empty object 65 | * also return the object when using object() or str() 66 | 67 | ## 2015-08-08, Version 1.1.0 68 | * Also let .object() understand array notation. 69 | 70 | ## 2015-08-03, Version 1.0.0 71 | * Convert object to dotted-key/value pairs 72 | 73 | ## 2015-04-15, Version 0.11.0 74 | * Add support for bracket notation 75 | 76 | ## 2015-03-22, Version 0.10.0 77 | * Make return value optional for Object/Array modifier(s) 78 | * Add modifier option to move(), transfer() & copy() 79 | 80 | ## 2015-03-21, Version 0.9.0 81 | * support multiple replacements/deletions (cli) 82 | * add del/remove method 83 | * improve bower build 84 | 85 | ## 2015-03-09, Version 0.8.1 86 | 87 | * [[`679f87590f`](https://github.com/rhalff/dot-object/commit/679f87590f)] - add to bower 88 | * [[`9a026982d8`](https://github.com/rhalff/dot-object/commit/9a026982d8)] - consider amd or attaching to window 89 | 90 | ## 2015-03-06, Version 0.8.0 91 | 92 | * [[`5ce0fe8836`](https://github.com/rhalff/dot-object/commit/5ce0fe8836)] - Simplify API 93 | 94 | ## 2015-03-06, Version 0.7.0 95 | 96 | * [[`c4658c386f`](https://github.com/rhalff/dot-object/commit/c4658c386f)] - Look for properties down in the prototype chain. (Fer Uría) 97 | * [[`a45c4a7982`](https://github.com/rhalff/dot-object/commit/a45c4a7982)] - Fix picking a null property with a dotted value. (Fer Uría) 98 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Rob Halff 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/rhalff/dot-object.png)](https://travis-ci.org/rhalff/dot-object) 2 | 3 | Dot-Object 4 | ======== 5 | 6 | Dot-Object makes it possible to transform javascript objects using dot notation. 7 | 8 | ### Installation 9 | 10 | Install from npm: 11 | ``` 12 | npm install dot-object --save 13 | ``` 14 | 15 | Install from bower: 16 | 17 | ``` 18 | bower install dot-object --save 19 | ``` 20 | 21 | ### Download 22 | 23 | * Development version: https://unpkg.com/dot-object/dist/dot-object.js *Uncompressed with Comments* 24 | * Production version: https://unpkg.com/dot-object/dist/dot-object.min.js *Minified* 25 | 26 | ## Usage 27 | 28 | #### Move a property within one object to another location 29 | ```javascript 30 | var dot = require('dot-object'); 31 | 32 | var obj = { 33 | 'first_name': 'John', 34 | 'last_name': 'Doe' 35 | }; 36 | 37 | dot.move('first_name', 'contact.firstname', obj); 38 | dot.move('last_name', 'contact.lastname', obj); 39 | 40 | console.log(obj); 41 | 42 | { 43 | contact: { 44 | firstname: 'John', 45 | lastname: 'Doe' 46 | } 47 | } 48 | 49 | ``` 50 | 51 | #### Copy property from one object to another 52 | ```javascript 53 | var dot = require('dot-object'); 54 | 55 | var src = { 56 | name: 'John', 57 | stuff: { 58 | phone: { 59 | brand: 'iphone', 60 | version: 6 61 | } 62 | } 63 | }; 64 | 65 | var tgt = {name: 'Brandon'}; 66 | 67 | dot.copy('stuff.phone', 'wanna.haves.phone', src, tgt); 68 | 69 | console.log(tgt); 70 | 71 | { 72 | name: 'Brandon', 73 | wanna: { 74 | haves: { 75 | phone: { 76 | brand: 'iphone', 77 | version: 6 78 | } 79 | } 80 | } 81 | } 82 | 83 | ``` 84 | 85 | #### Transfer property from one object to another 86 | 87 | Does the same as copy but removes the value from the source object: 88 | 89 | ```javascript 90 | dot.transfer('stuff.phone', 'wanna.haves.phone', src, tgt); 91 | 92 | // src: {"name":"John","stuff":{}} 93 | // tgt: {"name":"Brandon","wanna":{"haves":{"phone":{"brand":"iphone","version":6}}} 94 | ``` 95 | 96 | 97 | #### Expand to an object 98 | 99 | ```javascript 100 | var dot = require('dot-object'); 101 | 102 | var row = { 103 | 'id': 2, 104 | 'contact.name.first': 'John', 105 | 'contact.name.last': 'Doe', 106 | 'contact.email': 'example@gmail.com', 107 | 'contact.info.about.me': 'classified', 108 | 'devices[0]': 'mobile', 109 | 'devices[1]': 'laptop', 110 | 'some.other.things.0': 'this', 111 | 'some.other.things.1': 'that' 112 | }; 113 | 114 | dot.object(row); 115 | 116 | console.log(row); 117 | 118 | { 119 | "id": 2, 120 | "contact": { 121 | "name": { 122 | "first": "John", 123 | "last": "Doe" 124 | }, 125 | "email": "example@gmail.com", 126 | "info": { 127 | "about": { 128 | "me": "classified" 129 | } 130 | } 131 | }, 132 | "devices": [ 133 | "mobile", 134 | "laptop" 135 | ], 136 | "some": { 137 | "other": { 138 | "things": [ 139 | "this", 140 | "that" 141 | ] 142 | } 143 | } 144 | } 145 | ``` 146 | 147 | To convert manually per string use: 148 | ```javascript 149 | var dot = require('dot-object'); 150 | 151 | var tgt = { val: 'test' }; 152 | dot.str('this.is.my.string', 'value', tgt); 153 | 154 | console.log(tgt); 155 | 156 | { 157 | "val": "test", 158 | "this": { 159 | "is": { 160 | "my": { 161 | "string": "value" 162 | } 163 | } 164 | } 165 | } 166 | ``` 167 | 168 | #### Pick a value using dot notation: 169 | 170 | Picks a value from the object without removing it. 171 | 172 | ```js 173 | var dot = require('dot-object'); 174 | 175 | var obj = { 176 | some: { 177 | nested: { 178 | value: 'Hi there!' 179 | } 180 | } 181 | }; 182 | 183 | var val = dot.pick('some.nested.value', obj); 184 | console.log(val); 185 | 186 | Hi there! 187 | ``` 188 | 189 | #### Delete/Remove a value using dot notation: 190 | 191 | Remove and delete mostly behave the same, but in case of a path addressing array items: 192 | 193 | - `delete` will re-index the array. 194 | - `remove` will retain array indexes 195 | 196 | ```js 197 | var dot = require('dot-object'); 198 | 199 | var obj = { 200 | a: 'Hi There!', 201 | nested: { 202 | array: [ 203 | 'Veni', 204 | 'Vidi', 205 | 'Vici', 206 | ] 207 | } 208 | }; 209 | 210 | var val = dot.delete('a', obj); 211 | console.log(val); 212 | 213 | Hi There! 214 | 215 | // To remove an item and directly update any array indexes use: 216 | var val = dot.delete('nested.array[1]', obj); 217 | console.log(val); 218 | 219 | Vidi 220 | 221 | // Remove a value but retain array indexes. 222 | var val = dot.remove('nested.array[1]', obj); 223 | 224 | // To remove multiple paths at once: 225 | var val = dot.remove(['nested.array[0]', 'nested.array[2]'], obj); 226 | ``` 227 | 228 | ### Using modifiers 229 | 230 | You can use modifiers to translate values on the fly. 231 | 232 | This example uses the [underscore.string](https://github.com/epeli/underscore.string) library. 233 | 234 | 235 | 236 | ```javascript 237 | var dot = require('dot-object'); 238 | 239 | var _s = require('underscore.string'); 240 | 241 | var row = { 242 | 'nr': 200, 243 | 'doc.name': ' My Document ' 244 | }; 245 | 246 | var mods = { 247 | "doc.name": [_s.trim, _s.underscored], 248 | }; 249 | 250 | dot.object(row, mods); 251 | 252 | console.log(row); 253 | ``` 254 | 255 | ``` 256 | { 257 | "nr": 200, 258 | "doc": { 259 | "name": "my_document" 260 | } 261 | } 262 | ``` 263 | 264 | Or using .str() directy: 265 | 266 | ```javascript 267 | 268 | var dot = require('dot-object'); 269 | var _s = require('underscore.string'); 270 | var obj = { id: 100 }; 271 | 272 | // use one modifier 273 | dot.str('my.title', 'this is my title', obj, _s.slugify); 274 | 275 | // multiple modifiers 276 | dot.str('my.title', ' this is my title ', obj, [_s.trim, _s.slugify]); 277 | 278 | console.log(obj); 279 | ``` 280 | Result: 281 | ```json 282 | { 283 | "id": 100, 284 | "my": { 285 | "title": "this-is-my-title" 286 | } 287 | } 288 | ``` 289 | 290 | #### Transform object 291 | 292 | ```javascript 293 | var dot = require('dot-object'); 294 | 295 | var source = { 296 | "id": 1, 297 | "contact": { 298 | "firstName": "John", 299 | "lastName": "Doe", 300 | "email": "example@gmail.com", 301 | } 302 | } 303 | 304 | var recipe = { 305 | 'id': 'nr', 306 | 'contact.firstName': 'name.first', 307 | 'contact.lastName': 'name.last', 308 | 'contact.email': 'email' 309 | }; 310 | 311 | var tgt = {} 312 | dot.transform(recipe, source, tgt); 313 | 314 | // OR 315 | 316 | var tgt = dot.transform(recipe, source); 317 | 318 | console.log(tgt); 319 | { 320 | "nr": 1, 321 | "name": { 322 | "first": "John", 323 | "last": "Doe" 324 | }, 325 | "email": "example@gmail.com" 326 | } 327 | ``` 328 | 329 | 330 | ### Convert object to dotted-key/value pair 331 | 332 | ```javascript 333 | var dot = require('dot-object'); 334 | 335 | var obj = { 336 | id: 'my-id', 337 | nes: { ted: { value: true } }, 338 | other: { nested: { stuff: 5 } }, 339 | some: { array: ['A', 'B'] } 340 | }; 341 | 342 | var tgt = dot.dot(obj); 343 | 344 | // or 345 | 346 | var tgt = {}; 347 | dot.dot(obj, tgt); 348 | 349 | console.log(tgt); 350 | ``` 351 | Result: 352 | ```json 353 | { 354 | "id": "my-id", 355 | "nes.ted.value": true, 356 | "other.nested.stuff": 5, 357 | "some.array[0]": "A", 358 | "some.array[1]": "B" 359 | } 360 | ``` 361 | 362 | ### Keep array 363 | 364 | Set keepArray to true. 365 | 366 | ```javascript 367 | var dot = require('dot-object'); 368 | 369 | var obj = { 370 | id: 'my-id', 371 | other: [1, 2, 3], 372 | some: { array: ['A', 'B'] } 373 | }; 374 | 375 | dot.keepArray = true; 376 | var tgt = dot.dot(obj); 377 | 378 | console.log(tgt); 379 | ``` 380 | Result: 381 | ```json 382 | { 383 | "id": "my-id", 384 | "other": [1, 2, 3], 385 | "some.array": ["A", "B"] 386 | } 387 | ``` 388 | 389 | ## Using a different separator 390 | 391 | If you do not like dot notation, you are free to specify a different separator. 392 | 393 | ```javascript 394 | var Dot = require('dot-object'); 395 | 396 | var dot = new Dot('->'); 397 | 398 | var _s = require('underscore.string'); 399 | 400 | var row = { 401 | 'nr': 200, 402 | 'doc->name': ' My Document ' 403 | }; 404 | 405 | var mods = { 406 | "doc->name": [_s.trim, _s.underscored], 407 | }; 408 | 409 | dot.object(row, mods); 410 | 411 | console.log(row); 412 | ``` 413 | 414 | ``` 415 | { 416 | "nr": 200, 417 | "doc": { 418 | "name": "my_document" 419 | } 420 | } 421 | ``` 422 | 423 | ## Transforming SQL results to JSON 424 | 425 | SQL translation on the fly: 426 | 427 | ```javascript 428 | // TODO 429 | 430 | ``` 431 | 432 | 433 | > Copyright © 2013 Rob Halff, released under the MIT license 434 | -------------------------------------------------------------------------------- /bin/dot-object: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // vim: set filetype=javascript: 3 | 'use strict'; 4 | 5 | var glob = require('glob'); 6 | var fs = require('fs'); 7 | 8 | /** 9 | * 10 | * dotob -f require -t dependencies.npm 11 | * 12 | */ 13 | var dotob = require('../index.js'); 14 | var program = require('commander'); 15 | var pkg = require('../package.json'); 16 | 17 | program 18 | .version(pkg.version) 19 | .usage('[options]') 20 | .option('-p, --pattern [pattern]', 'Files pattern to match or just the file') 21 | .option('-f, --from [path,..]', 'From path') 22 | .option('-t, --to [path,..]', 'To path (number of replacements must match --to values)') 23 | .option('-m, --merge', 'Merge into target') 24 | .option('-r, --remove [path,..]', 'Remove property') 25 | .option('-c, --dot', 'Convert object to dotted-key/value pair') 26 | .option('-s, --show', 'show all dotted paths') 27 | .option('-v, --verbose', 'Be verbose') 28 | .option('-d, --dry', 'Dry run do not modify files') 29 | .parse(process.argv); 30 | 31 | function must(program, option) { 32 | if(!program.hasOwnProperty(option)) { 33 | console.log([ 34 | 'The', option, 'is required' 35 | ].join(' ')); 36 | process.exit(1); 37 | } 38 | } 39 | 40 | must(program, 'pattern'); 41 | 42 | if (!program.remove && !program.dot && !program.show) { 43 | must(program, 'from'); 44 | must(program, 'to'); 45 | } 46 | 47 | var g = glob(program.pattern); 48 | 49 | function finish(program, file, orig, json) { 50 | 51 | return function(err) { 52 | if (err) { 53 | throw err; 54 | } else { 55 | if (program.verbose) { 56 | if (orig !== JSON.stringify(json)) { 57 | console.log(file + ': updated.'); 58 | } else { 59 | console.log(file + ': no matches.'); 60 | } 61 | } 62 | } 63 | }; 64 | } 65 | 66 | function splim(path) { 67 | return path.split(',') 68 | .map(function(val) { return val.trim(); }); 69 | } 70 | 71 | function processFile(file) { 72 | 73 | fs.readFile(file, 'utf8', function(err, contents) { 74 | var json; 75 | 76 | if (err) { 77 | console.log(err); 78 | return; 79 | } 80 | 81 | try { 82 | json = JSON.parse(contents); 83 | 84 | if(program.show) { 85 | json = console.log(Object.keys(dotob.dot(json)).join('\n')); 86 | process.exit() 87 | } else if(program.dot) { 88 | console.log(dotob.dot(json)); 89 | process.exit() 90 | } 91 | 92 | json = Array.isArray(json) ? json : [json]; 93 | 94 | if(program.remove) { 95 | // support comma seperate list of removals 96 | splim(program.remove) 97 | .forEach(function(path) { 98 | for (var j = 0; j < json.length; j++) { 99 | dotob.remove(path, json[j]); 100 | } 101 | }); 102 | } else { 103 | var from = splim(program.from); 104 | var to = splim(program.to); 105 | if (from.length === to.length) { 106 | for (var i = 0; i < from.length; i++) { 107 | for (var j = 0; j < json.length; j++) { 108 | dotob.move( 109 | from[i], to[i], json[j], program.merge 110 | ); 111 | } 112 | } 113 | } else { 114 | console.error('--from and --to parameters are not of equal length'); 115 | } 116 | } 117 | 118 | if(program.dry) { 119 | console.log(json); 120 | finish(program, file, contents, json)(); 121 | } else { 122 | fs.writeFile(file, JSON.stringify(json, null, 2), finish( 123 | program, file, contents, json 124 | )); 125 | } 126 | } catch (e) { 127 | console.log(file + ': '); 128 | throw(e); 129 | } 130 | }); 131 | } 132 | 133 | g.on('match', processFile); 134 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dot-object", 3 | "version": "2.1.4", 4 | "description": "dot-object makes it possible to transform and read (JSON) objects using dot notation.", 5 | "main": "dist/dot-object.js", 6 | "authors": [ 7 | { "name": "Rob Halff", "email": "rob.halff@gmail.com" } 8 | ], 9 | "ignore": [ 10 | "bin", 11 | "node_modules", 12 | "src", 13 | "gulpfile.js", 14 | "index.js" 15 | ], 16 | "keywords": [ 17 | "json", 18 | "filter", 19 | "transform", 20 | "dot", 21 | "notation", 22 | "dot" 23 | ], 24 | "license": "MIT", 25 | "repository": { 26 | "type": "git", 27 | "url": "git://github.com/rhalff/dot-object.git" 28 | }, 29 | "homepage": "https://github.com/rhalff/dot-object", 30 | "moduleType": [ 31 | "amd", 32 | "globals", 33 | "node" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /dist/dot-object.js: -------------------------------------------------------------------------------- 1 | (function(global, exportName) { 2 | 'use strict' 3 | 4 | function _process(v, mod) { 5 | var i 6 | var r 7 | 8 | if (typeof mod === 'function') { 9 | r = mod(v) 10 | if (r !== undefined) { 11 | v = r 12 | } 13 | } else if (Array.isArray(mod)) { 14 | for (i = 0; i < mod.length; i++) { 15 | r = mod[i](v) 16 | if (r !== undefined) { 17 | v = r 18 | } 19 | } 20 | } 21 | 22 | return v 23 | } 24 | 25 | function parseKey(key, val) { 26 | // detect negative index notation 27 | if (key[0] === '-' && Array.isArray(val) && /^-\d+$/.test(key)) { 28 | return val.length + parseInt(key, 10) 29 | } 30 | return key 31 | } 32 | 33 | function isIndex(k) { 34 | return /^\d+$/.test(k) 35 | } 36 | 37 | function isObject(val) { 38 | return Object.prototype.toString.call(val) === '[object Object]' 39 | } 40 | 41 | function isArrayOrObject(val) { 42 | return Object(val) === val 43 | } 44 | 45 | function isEmptyObject(val) { 46 | return Object.keys(val).length === 0 47 | } 48 | 49 | var blacklist = ['__proto__', 'prototype', 'constructor'] 50 | var blacklistFilter = function(part) { 51 | return blacklist.indexOf(part) === -1 52 | } 53 | 54 | function parsePath(path, sep) { 55 | if (path.indexOf('[') >= 0) { 56 | path = path.replace(/\[/g, sep).replace(/]/g, '') 57 | } 58 | 59 | var parts = path.split(sep) 60 | 61 | var check = parts.filter(blacklistFilter) 62 | 63 | if (check.length !== parts.length) { 64 | throw Error('Refusing to update blacklisted property ' + path) 65 | } 66 | 67 | return parts 68 | } 69 | 70 | var hasOwnProperty = Object.prototype.hasOwnProperty 71 | 72 | function DotObject(separator, override, useArray, useBrackets) { 73 | if (!(this instanceof DotObject)) { 74 | return new DotObject(separator, override, useArray, useBrackets) 75 | } 76 | 77 | if (typeof override === 'undefined') override = false 78 | if (typeof useArray === 'undefined') useArray = true 79 | if (typeof useBrackets === 'undefined') useBrackets = true 80 | this.separator = separator || '.' 81 | this.override = override 82 | this.useArray = useArray 83 | this.useBrackets = useBrackets 84 | this.keepArray = false 85 | 86 | // contains touched arrays 87 | this.cleanup = [] 88 | } 89 | 90 | var dotDefault = new DotObject('.', false, true, true) 91 | 92 | function wrap(method) { 93 | return function() { 94 | return dotDefault[method].apply(dotDefault, arguments) 95 | } 96 | } 97 | 98 | DotObject.prototype._fill = function(a, obj, v, mod) { 99 | var k = a.shift() 100 | 101 | if (a.length > 0) { 102 | obj[k] = obj[k] || (this.useArray && isIndex(a[0]) ? [] : {}) 103 | 104 | if (!isArrayOrObject(obj[k])) { 105 | if (this.override) { 106 | obj[k] = {} 107 | } else { 108 | if (!(isArrayOrObject(v) && isEmptyObject(v))) { 109 | throw new Error( 110 | 'Trying to redefine `' + k + '` which is a ' + typeof obj[k] 111 | ) 112 | } 113 | 114 | return 115 | } 116 | } 117 | 118 | this._fill(a, obj[k], v, mod) 119 | } else { 120 | if (!this.override && isArrayOrObject(obj[k]) && !isEmptyObject(obj[k])) { 121 | if (!(isArrayOrObject(v) && isEmptyObject(v))) { 122 | throw new Error("Trying to redefine non-empty obj['" + k + "']") 123 | } 124 | 125 | return 126 | } 127 | 128 | obj[k] = _process(v, mod) 129 | } 130 | } 131 | 132 | /** 133 | * 134 | * Converts an object with dotted-key/value pairs to it's expanded version 135 | * 136 | * Optionally transformed by a set of modifiers. 137 | * 138 | * Usage: 139 | * 140 | * var row = { 141 | * 'nr': 200, 142 | * 'doc.name': ' My Document ' 143 | * } 144 | * 145 | * var mods = { 146 | * 'doc.name': [_s.trim, _s.underscored] 147 | * } 148 | * 149 | * dot.object(row, mods) 150 | * 151 | * @param {Object} obj 152 | * @param {Object} mods 153 | */ 154 | DotObject.prototype.object = function(obj, mods) { 155 | var self = this 156 | 157 | Object.keys(obj).forEach(function(k) { 158 | var mod = mods === undefined ? null : mods[k] 159 | // normalize array notation. 160 | var ok = parsePath(k, self.separator).join(self.separator) 161 | 162 | if (ok.indexOf(self.separator) !== -1) { 163 | self._fill(ok.split(self.separator), obj, obj[k], mod) 164 | delete obj[k] 165 | } else { 166 | obj[k] = _process(obj[k], mod) 167 | } 168 | }) 169 | 170 | return obj 171 | } 172 | 173 | /** 174 | * @param {String} path dotted path 175 | * @param {String} v value to be set 176 | * @param {Object} obj object to be modified 177 | * @param {Function|Array} mod optional modifier 178 | */ 179 | DotObject.prototype.str = function(path, v, obj, mod) { 180 | var ok = parsePath(path, this.separator).join(this.separator) 181 | 182 | if (path.indexOf(this.separator) !== -1) { 183 | this._fill(ok.split(this.separator), obj, v, mod) 184 | } else { 185 | obj[path] = _process(v, mod) 186 | } 187 | 188 | return obj 189 | } 190 | 191 | /** 192 | * 193 | * Pick a value from an object using dot notation. 194 | * 195 | * Optionally remove the value 196 | * 197 | * @param {String} path 198 | * @param {Object} obj 199 | * @param {Boolean} remove 200 | */ 201 | DotObject.prototype.pick = function(path, obj, remove, reindexArray) { 202 | var i 203 | var keys 204 | var val 205 | var key 206 | var cp 207 | 208 | keys = parsePath(path, this.separator) 209 | for (i = 0; i < keys.length; i++) { 210 | key = parseKey(keys[i], obj) 211 | if (obj && typeof obj === 'object' && key in obj) { 212 | if (i === keys.length - 1) { 213 | if (remove) { 214 | val = obj[key] 215 | if (reindexArray && Array.isArray(obj)) { 216 | obj.splice(key, 1) 217 | } else { 218 | delete obj[key] 219 | } 220 | if (Array.isArray(obj)) { 221 | cp = keys.slice(0, -1).join('.') 222 | if (this.cleanup.indexOf(cp) === -1) { 223 | this.cleanup.push(cp) 224 | } 225 | } 226 | return val 227 | } else { 228 | return obj[key] 229 | } 230 | } else { 231 | obj = obj[key] 232 | } 233 | } else { 234 | return undefined 235 | } 236 | } 237 | if (remove && Array.isArray(obj)) { 238 | obj = obj.filter(function(n) { 239 | return n !== undefined 240 | }) 241 | } 242 | return obj 243 | } 244 | /** 245 | * 246 | * Delete value from an object using dot notation. 247 | * 248 | * @param {String} path 249 | * @param {Object} obj 250 | * @return {any} The removed value 251 | */ 252 | DotObject.prototype.delete = function(path, obj) { 253 | return this.remove(path, obj, true) 254 | } 255 | 256 | /** 257 | * 258 | * Remove value from an object using dot notation. 259 | * 260 | * Will remove multiple items if path is an array. 261 | * In this case array indexes will be retained until all 262 | * removals have been processed. 263 | * 264 | * Use dot.delete() to automatically re-index arrays. 265 | * 266 | * @param {String|Array} path 267 | * @param {Object} obj 268 | * @param {Boolean} reindexArray 269 | * @return {any} The removed value 270 | */ 271 | DotObject.prototype.remove = function(path, obj, reindexArray) { 272 | var i 273 | 274 | this.cleanup = [] 275 | if (Array.isArray(path)) { 276 | for (i = 0; i < path.length; i++) { 277 | this.pick(path[i], obj, true, reindexArray) 278 | } 279 | if (!reindexArray) { 280 | this._cleanup(obj) 281 | } 282 | return obj 283 | } else { 284 | return this.pick(path, obj, true, reindexArray) 285 | } 286 | } 287 | 288 | DotObject.prototype._cleanup = function(obj) { 289 | var ret 290 | var i 291 | var keys 292 | var root 293 | if (this.cleanup.length) { 294 | for (i = 0; i < this.cleanup.length; i++) { 295 | keys = this.cleanup[i].split('.') 296 | root = keys.splice(0, -1).join('.') 297 | ret = root ? this.pick(root, obj) : obj 298 | ret = ret[keys[0]].filter(function(v) { 299 | return v !== undefined 300 | }) 301 | this.set(this.cleanup[i], ret, obj) 302 | } 303 | this.cleanup = [] 304 | } 305 | } 306 | 307 | /** 308 | * Alias method for `dot.remove` 309 | * 310 | * Note: this is not an alias for dot.delete() 311 | * 312 | * @param {String|Array} path 313 | * @param {Object} obj 314 | * @param {Boolean} reindexArray 315 | * @return {any} The removed value 316 | */ 317 | DotObject.prototype.del = DotObject.prototype.remove 318 | 319 | /** 320 | * 321 | * Move a property from one place to the other. 322 | * 323 | * If the source path does not exist (undefined) 324 | * the target property will not be set. 325 | * 326 | * @param {String} source 327 | * @param {String} target 328 | * @param {Object} obj 329 | * @param {Function|Array} mods 330 | * @param {Boolean} merge 331 | */ 332 | DotObject.prototype.move = function(source, target, obj, mods, merge) { 333 | if (typeof mods === 'function' || Array.isArray(mods)) { 334 | this.set(target, _process(this.pick(source, obj, true), mods), obj, merge) 335 | } else { 336 | merge = mods 337 | this.set(target, this.pick(source, obj, true), obj, merge) 338 | } 339 | 340 | return obj 341 | } 342 | 343 | /** 344 | * 345 | * Transfer a property from one object to another object. 346 | * 347 | * If the source path does not exist (undefined) 348 | * the property on the other object will not be set. 349 | * 350 | * @param {String} source 351 | * @param {String} target 352 | * @param {Object} obj1 353 | * @param {Object} obj2 354 | * @param {Function|Array} mods 355 | * @param {Boolean} merge 356 | */ 357 | DotObject.prototype.transfer = function( 358 | source, 359 | target, 360 | obj1, 361 | obj2, 362 | mods, 363 | merge 364 | ) { 365 | if (typeof mods === 'function' || Array.isArray(mods)) { 366 | this.set( 367 | target, 368 | _process(this.pick(source, obj1, true), mods), 369 | obj2, 370 | merge 371 | ) 372 | } else { 373 | merge = mods 374 | this.set(target, this.pick(source, obj1, true), obj2, merge) 375 | } 376 | 377 | return obj2 378 | } 379 | 380 | /** 381 | * 382 | * Copy a property from one object to another object. 383 | * 384 | * If the source path does not exist (undefined) 385 | * the property on the other object will not be set. 386 | * 387 | * @param {String} source 388 | * @param {String} target 389 | * @param {Object} obj1 390 | * @param {Object} obj2 391 | * @param {Function|Array} mods 392 | * @param {Boolean} merge 393 | */ 394 | DotObject.prototype.copy = function(source, target, obj1, obj2, mods, merge) { 395 | if (typeof mods === 'function' || Array.isArray(mods)) { 396 | this.set( 397 | target, 398 | _process( 399 | // clone what is picked 400 | JSON.parse(JSON.stringify(this.pick(source, obj1, false))), 401 | mods 402 | ), 403 | obj2, 404 | merge 405 | ) 406 | } else { 407 | merge = mods 408 | this.set(target, this.pick(source, obj1, false), obj2, merge) 409 | } 410 | 411 | return obj2 412 | } 413 | 414 | /** 415 | * 416 | * Set a property on an object using dot notation. 417 | * 418 | * @param {String} path 419 | * @param {any} val 420 | * @param {Object} obj 421 | * @param {Boolean} merge 422 | */ 423 | DotObject.prototype.set = function(path, val, obj, merge) { 424 | var i 425 | var k 426 | var keys 427 | var key 428 | 429 | // Do not operate if the value is undefined. 430 | if (typeof val === 'undefined') { 431 | return obj 432 | } 433 | keys = parsePath(path, this.separator) 434 | 435 | for (i = 0; i < keys.length; i++) { 436 | key = keys[i] 437 | if (i === keys.length - 1) { 438 | if (merge && isObject(val) && isObject(obj[key])) { 439 | for (k in val) { 440 | if (hasOwnProperty.call(val, k)) { 441 | obj[key][k] = val[k] 442 | } 443 | } 444 | } else if (merge && Array.isArray(obj[key]) && Array.isArray(val)) { 445 | for (var j = 0; j < val.length; j++) { 446 | obj[keys[i]].push(val[j]) 447 | } 448 | } else { 449 | obj[key] = val 450 | } 451 | } else if ( 452 | // force the value to be an object 453 | !hasOwnProperty.call(obj, key) || 454 | (!isObject(obj[key]) && !Array.isArray(obj[key])) 455 | ) { 456 | // initialize as array if next key is numeric 457 | if (/^\d+$/.test(keys[i + 1])) { 458 | obj[key] = [] 459 | } else { 460 | obj[key] = {} 461 | } 462 | } 463 | obj = obj[key] 464 | } 465 | return obj 466 | } 467 | 468 | /** 469 | * 470 | * Transform an object 471 | * 472 | * Usage: 473 | * 474 | * var obj = { 475 | * "id": 1, 476 | * "some": { 477 | * "thing": "else" 478 | * } 479 | * } 480 | * 481 | * var transform = { 482 | * "id": "nr", 483 | * "some.thing": "name" 484 | * } 485 | * 486 | * var tgt = dot.transform(transform, obj) 487 | * 488 | * @param {Object} recipe Transform recipe 489 | * @param {Object} obj Object to be transformed 490 | * @param {Array} mods modifiers for the target 491 | */ 492 | DotObject.prototype.transform = function(recipe, obj, tgt) { 493 | obj = obj || {} 494 | tgt = tgt || {} 495 | Object.keys(recipe).forEach( 496 | function(key) { 497 | this.set(recipe[key], this.pick(key, obj), tgt) 498 | }.bind(this) 499 | ) 500 | return tgt 501 | } 502 | 503 | /** 504 | * 505 | * Convert object to dotted-key/value pair 506 | * 507 | * Usage: 508 | * 509 | * var tgt = dot.dot(obj) 510 | * 511 | * or 512 | * 513 | * var tgt = {} 514 | * dot.dot(obj, tgt) 515 | * 516 | * @param {Object} obj source object 517 | * @param {Object} tgt target object 518 | * @param {Array} path path array (internal) 519 | */ 520 | DotObject.prototype.dot = function(obj, tgt, path) { 521 | tgt = tgt || {} 522 | path = path || [] 523 | var isArray = Array.isArray(obj) 524 | 525 | Object.keys(obj).forEach( 526 | function(key) { 527 | var index = isArray && this.useBrackets ? '[' + key + ']' : key 528 | if ( 529 | isArrayOrObject(obj[key]) && 530 | ((isObject(obj[key]) && !isEmptyObject(obj[key])) || 531 | (Array.isArray(obj[key]) && !this.keepArray && obj[key].length !== 0)) 532 | ) { 533 | if (isArray && this.useBrackets) { 534 | var previousKey = path[path.length - 1] || '' 535 | return this.dot( 536 | obj[key], 537 | tgt, 538 | path.slice(0, -1).concat(previousKey + index) 539 | ) 540 | } else { 541 | return this.dot(obj[key], tgt, path.concat(index)) 542 | } 543 | } else { 544 | if (isArray && this.useBrackets) { 545 | tgt[path.join(this.separator).concat('[' + key + ']')] = obj[key] 546 | } else { 547 | tgt[path.concat(index).join(this.separator)] = obj[key] 548 | } 549 | } 550 | }.bind(this) 551 | ) 552 | return tgt 553 | } 554 | 555 | DotObject.pick = wrap('pick') 556 | DotObject.move = wrap('move') 557 | DotObject.transfer = wrap('transfer') 558 | DotObject.transform = wrap('transform') 559 | DotObject.copy = wrap('copy') 560 | DotObject.object = wrap('object') 561 | DotObject.str = wrap('str') 562 | DotObject.set = wrap('set') 563 | DotObject.delete = wrap('delete') 564 | DotObject.del = DotObject.remove = wrap('remove') 565 | DotObject.dot = wrap('dot'); 566 | ['override', 'overwrite'].forEach(function(prop) { 567 | Object.defineProperty(DotObject, prop, { 568 | get: function() { 569 | return dotDefault.override 570 | }, 571 | set: function(val) { 572 | dotDefault.override = !!val 573 | } 574 | }) 575 | }); 576 | ['useArray', 'keepArray', 'useBrackets'].forEach(function(prop) { 577 | Object.defineProperty(DotObject, prop, { 578 | get: function() { 579 | return dotDefault[prop] 580 | }, 581 | set: function(val) { 582 | dotDefault[prop] = val 583 | } 584 | }) 585 | }) 586 | 587 | DotObject._process = _process 588 | 589 | 590 | if (typeof define === 'function' && define.amd) { 591 | define(function() { 592 | return DotObject 593 | }) 594 | } else if (typeof module != 'undefined' && module.exports) { 595 | module.exports = DotObject 596 | } else { 597 | global[exportName] = DotObject 598 | } 599 | 600 | })(this, 'DotObject') -------------------------------------------------------------------------------- /dist/dot-object.min.js: -------------------------------------------------------------------------------- 1 | !function(t){"use strict";function s(t,r){var e,i;if("function"==typeof r)void 0!==(i=r(t))&&(t=i);else if(Array.isArray(r))for(e=0;e= 0) { 53 | path = path.replace(/\[/g, sep).replace(/]/g, '') 54 | } 55 | 56 | var parts = path.split(sep) 57 | 58 | var check = parts.filter(blacklistFilter) 59 | 60 | if (check.length !== parts.length) { 61 | throw Error('Refusing to update blacklisted property ' + path) 62 | } 63 | 64 | return parts 65 | } 66 | 67 | var hasOwnProperty = Object.prototype.hasOwnProperty 68 | 69 | function DotObject (separator, override, useArray, useBrackets) { 70 | if (!(this instanceof DotObject)) { 71 | return new DotObject(separator, override, useArray, useBrackets) 72 | } 73 | 74 | if (typeof override === 'undefined') override = false 75 | if (typeof useArray === 'undefined') useArray = true 76 | if (typeof useBrackets === 'undefined') useBrackets = true 77 | this.separator = separator || '.' 78 | this.override = override 79 | this.useArray = useArray 80 | this.useBrackets = useBrackets 81 | this.keepArray = false 82 | 83 | // contains touched arrays 84 | this.cleanup = [] 85 | } 86 | 87 | var dotDefault = new DotObject('.', false, true, true) 88 | function wrap (method) { 89 | return function () { 90 | return dotDefault[method].apply(dotDefault, arguments) 91 | } 92 | } 93 | 94 | DotObject.prototype._fill = function (a, obj, v, mod) { 95 | var k = a.shift() 96 | 97 | if (a.length > 0) { 98 | obj[k] = obj[k] || (this.useArray && isIndex(a[0]) ? [] : {}) 99 | 100 | if (!isArrayOrObject(obj[k])) { 101 | if (this.override) { 102 | obj[k] = {} 103 | } else { 104 | if (!(isArrayOrObject(v) && isEmptyObject(v))) { 105 | throw new Error( 106 | 'Trying to redefine `' + k + '` which is a ' + typeof obj[k] 107 | ) 108 | } 109 | 110 | return 111 | } 112 | } 113 | 114 | this._fill(a, obj[k], v, mod) 115 | } else { 116 | if (!this.override && isArrayOrObject(obj[k]) && !isEmptyObject(obj[k])) { 117 | if (!(isArrayOrObject(v) && isEmptyObject(v))) { 118 | throw new Error("Trying to redefine non-empty obj['" + k + "']") 119 | } 120 | 121 | return 122 | } 123 | 124 | obj[k] = _process(v, mod) 125 | } 126 | } 127 | 128 | /** 129 | * 130 | * Converts an object with dotted-key/value pairs to it's expanded version 131 | * 132 | * Optionally transformed by a set of modifiers. 133 | * 134 | * Usage: 135 | * 136 | * var row = { 137 | * 'nr': 200, 138 | * 'doc.name': ' My Document ' 139 | * } 140 | * 141 | * var mods = { 142 | * 'doc.name': [_s.trim, _s.underscored] 143 | * } 144 | * 145 | * dot.object(row, mods) 146 | * 147 | * @param {Object} obj 148 | * @param {Object} mods 149 | */ 150 | DotObject.prototype.object = function (obj, mods) { 151 | var self = this 152 | 153 | Object.keys(obj).forEach(function (k) { 154 | var mod = mods === undefined ? null : mods[k] 155 | // normalize array notation. 156 | var ok = parsePath(k, self.separator).join(self.separator) 157 | 158 | if (ok.indexOf(self.separator) !== -1) { 159 | self._fill(ok.split(self.separator), obj, obj[k], mod) 160 | delete obj[k] 161 | } else { 162 | obj[k] = _process(obj[k], mod) 163 | } 164 | }) 165 | 166 | return obj 167 | } 168 | 169 | /** 170 | * @param {String} path dotted path 171 | * @param {String} v value to be set 172 | * @param {Object} obj object to be modified 173 | * @param {Function|Array} mod optional modifier 174 | */ 175 | DotObject.prototype.str = function (path, v, obj, mod) { 176 | var ok = parsePath(path, this.separator).join(this.separator) 177 | 178 | if (path.indexOf(this.separator) !== -1) { 179 | this._fill(ok.split(this.separator), obj, v, mod) 180 | } else { 181 | obj[path] = _process(v, mod) 182 | } 183 | 184 | return obj 185 | } 186 | 187 | /** 188 | * 189 | * Pick a value from an object using dot notation. 190 | * 191 | * Optionally remove the value 192 | * 193 | * @param {String} path 194 | * @param {Object} obj 195 | * @param {Boolean} remove 196 | */ 197 | DotObject.prototype.pick = function (path, obj, remove, reindexArray) { 198 | var i 199 | var keys 200 | var val 201 | var key 202 | var cp 203 | 204 | keys = parsePath(path, this.separator) 205 | for (i = 0; i < keys.length; i++) { 206 | key = parseKey(keys[i], obj) 207 | if (obj && typeof obj === 'object' && key in obj) { 208 | if (i === keys.length - 1) { 209 | if (remove) { 210 | val = obj[key] 211 | if (reindexArray && Array.isArray(obj)) { 212 | obj.splice(key, 1) 213 | } else { 214 | delete obj[key] 215 | } 216 | if (Array.isArray(obj)) { 217 | cp = keys.slice(0, -1).join('.') 218 | if (this.cleanup.indexOf(cp) === -1) { 219 | this.cleanup.push(cp) 220 | } 221 | } 222 | return val 223 | } else { 224 | return obj[key] 225 | } 226 | } else { 227 | obj = obj[key] 228 | } 229 | } else { 230 | return undefined 231 | } 232 | } 233 | if (remove && Array.isArray(obj)) { 234 | obj = obj.filter(function (n) { 235 | return n !== undefined 236 | }) 237 | } 238 | return obj 239 | } 240 | /** 241 | * 242 | * Delete value from an object using dot notation. 243 | * 244 | * @param {String} path 245 | * @param {Object} obj 246 | * @return {any} The removed value 247 | */ 248 | DotObject.prototype.delete = function (path, obj) { 249 | return this.remove(path, obj, true) 250 | } 251 | 252 | /** 253 | * 254 | * Remove value from an object using dot notation. 255 | * 256 | * Will remove multiple items if path is an array. 257 | * In this case array indexes will be retained until all 258 | * removals have been processed. 259 | * 260 | * Use dot.delete() to automatically re-index arrays. 261 | * 262 | * @param {String|Array} path 263 | * @param {Object} obj 264 | * @param {Boolean} reindexArray 265 | * @return {any} The removed value 266 | */ 267 | DotObject.prototype.remove = function (path, obj, reindexArray) { 268 | var i 269 | 270 | this.cleanup = [] 271 | if (Array.isArray(path)) { 272 | for (i = 0; i < path.length; i++) { 273 | this.pick(path[i], obj, true, reindexArray) 274 | } 275 | if (!reindexArray) { 276 | this._cleanup(obj) 277 | } 278 | return obj 279 | } else { 280 | return this.pick(path, obj, true, reindexArray) 281 | } 282 | } 283 | 284 | DotObject.prototype._cleanup = function (obj) { 285 | var ret 286 | var i 287 | var keys 288 | var root 289 | if (this.cleanup.length) { 290 | for (i = 0; i < this.cleanup.length; i++) { 291 | keys = this.cleanup[i].split('.') 292 | root = keys.splice(0, -1).join('.') 293 | ret = root ? this.pick(root, obj) : obj 294 | ret = ret[keys[0]].filter(function (v) { 295 | return v !== undefined 296 | }) 297 | this.set(this.cleanup[i], ret, obj) 298 | } 299 | this.cleanup = [] 300 | } 301 | } 302 | 303 | /** 304 | * Alias method for `dot.remove` 305 | * 306 | * Note: this is not an alias for dot.delete() 307 | * 308 | * @param {String|Array} path 309 | * @param {Object} obj 310 | * @param {Boolean} reindexArray 311 | * @return {any} The removed value 312 | */ 313 | DotObject.prototype.del = DotObject.prototype.remove 314 | 315 | /** 316 | * 317 | * Move a property from one place to the other. 318 | * 319 | * If the source path does not exist (undefined) 320 | * the target property will not be set. 321 | * 322 | * @param {String} source 323 | * @param {String} target 324 | * @param {Object} obj 325 | * @param {Function|Array} mods 326 | * @param {Boolean} merge 327 | */ 328 | DotObject.prototype.move = function (source, target, obj, mods, merge) { 329 | if (typeof mods === 'function' || Array.isArray(mods)) { 330 | this.set(target, _process(this.pick(source, obj, true), mods), obj, merge) 331 | } else { 332 | merge = mods 333 | this.set(target, this.pick(source, obj, true), obj, merge) 334 | } 335 | 336 | return obj 337 | } 338 | 339 | /** 340 | * 341 | * Transfer a property from one object to another object. 342 | * 343 | * If the source path does not exist (undefined) 344 | * the property on the other object will not be set. 345 | * 346 | * @param {String} source 347 | * @param {String} target 348 | * @param {Object} obj1 349 | * @param {Object} obj2 350 | * @param {Function|Array} mods 351 | * @param {Boolean} merge 352 | */ 353 | DotObject.prototype.transfer = function ( 354 | source, 355 | target, 356 | obj1, 357 | obj2, 358 | mods, 359 | merge 360 | ) { 361 | if (typeof mods === 'function' || Array.isArray(mods)) { 362 | this.set( 363 | target, 364 | _process(this.pick(source, obj1, true), mods), 365 | obj2, 366 | merge 367 | ) 368 | } else { 369 | merge = mods 370 | this.set(target, this.pick(source, obj1, true), obj2, merge) 371 | } 372 | 373 | return obj2 374 | } 375 | 376 | /** 377 | * 378 | * Copy a property from one object to another object. 379 | * 380 | * If the source path does not exist (undefined) 381 | * the property on the other object will not be set. 382 | * 383 | * @param {String} source 384 | * @param {String} target 385 | * @param {Object} obj1 386 | * @param {Object} obj2 387 | * @param {Function|Array} mods 388 | * @param {Boolean} merge 389 | */ 390 | DotObject.prototype.copy = function (source, target, obj1, obj2, mods, merge) { 391 | if (typeof mods === 'function' || Array.isArray(mods)) { 392 | this.set( 393 | target, 394 | _process( 395 | // clone what is picked 396 | JSON.parse(JSON.stringify(this.pick(source, obj1, false))), 397 | mods 398 | ), 399 | obj2, 400 | merge 401 | ) 402 | } else { 403 | merge = mods 404 | this.set(target, this.pick(source, obj1, false), obj2, merge) 405 | } 406 | 407 | return obj2 408 | } 409 | 410 | /** 411 | * 412 | * Set a property on an object using dot notation. 413 | * 414 | * @param {String} path 415 | * @param {any} val 416 | * @param {Object} obj 417 | * @param {Boolean} merge 418 | */ 419 | DotObject.prototype.set = function (path, val, obj, merge) { 420 | var i 421 | var k 422 | var keys 423 | var key 424 | 425 | // Do not operate if the value is undefined. 426 | if (typeof val === 'undefined') { 427 | return obj 428 | } 429 | keys = parsePath(path, this.separator) 430 | 431 | for (i = 0; i < keys.length; i++) { 432 | key = keys[i] 433 | if (i === keys.length - 1) { 434 | if (merge && isObject(val) && isObject(obj[key])) { 435 | for (k in val) { 436 | if (hasOwnProperty.call(val, k)) { 437 | obj[key][k] = val[k] 438 | } 439 | } 440 | } else if (merge && Array.isArray(obj[key]) && Array.isArray(val)) { 441 | for (var j = 0; j < val.length; j++) { 442 | obj[keys[i]].push(val[j]) 443 | } 444 | } else { 445 | obj[key] = val 446 | } 447 | } else if ( 448 | // force the value to be an object 449 | !hasOwnProperty.call(obj, key) || 450 | (!isObject(obj[key]) && !Array.isArray(obj[key])) 451 | ) { 452 | // initialize as array if next key is numeric 453 | if (/^\d+$/.test(keys[i + 1])) { 454 | obj[key] = [] 455 | } else { 456 | obj[key] = {} 457 | } 458 | } 459 | obj = obj[key] 460 | } 461 | return obj 462 | } 463 | 464 | /** 465 | * 466 | * Transform an object 467 | * 468 | * Usage: 469 | * 470 | * var obj = { 471 | * "id": 1, 472 | * "some": { 473 | * "thing": "else" 474 | * } 475 | * } 476 | * 477 | * var transform = { 478 | * "id": "nr", 479 | * "some.thing": "name" 480 | * } 481 | * 482 | * var tgt = dot.transform(transform, obj) 483 | * 484 | * @param {Object} recipe Transform recipe 485 | * @param {Object} obj Object to be transformed 486 | * @param {Array} mods modifiers for the target 487 | */ 488 | DotObject.prototype.transform = function (recipe, obj, tgt) { 489 | obj = obj || {} 490 | tgt = tgt || {} 491 | Object.keys(recipe).forEach( 492 | function (key) { 493 | this.set(recipe[key], this.pick(key, obj), tgt) 494 | }.bind(this) 495 | ) 496 | return tgt 497 | } 498 | 499 | /** 500 | * 501 | * Convert object to dotted-key/value pair 502 | * 503 | * Usage: 504 | * 505 | * var tgt = dot.dot(obj) 506 | * 507 | * or 508 | * 509 | * var tgt = {} 510 | * dot.dot(obj, tgt) 511 | * 512 | * @param {Object} obj source object 513 | * @param {Object} tgt target object 514 | * @param {Array} path path array (internal) 515 | */ 516 | DotObject.prototype.dot = function (obj, tgt, path) { 517 | tgt = tgt || {} 518 | path = path || [] 519 | var isArray = Array.isArray(obj) 520 | 521 | Object.keys(obj).forEach( 522 | function (key) { 523 | var index = isArray && this.useBrackets ? '[' + key + ']' : key 524 | if ( 525 | isArrayOrObject(obj[key]) && 526 | ((isObject(obj[key]) && !isEmptyObject(obj[key])) || 527 | (Array.isArray(obj[key]) && !this.keepArray && obj[key].length !== 0)) 528 | ) { 529 | if (isArray && this.useBrackets) { 530 | var previousKey = path[path.length - 1] || '' 531 | return this.dot( 532 | obj[key], 533 | tgt, 534 | path.slice(0, -1).concat(previousKey + index) 535 | ) 536 | } else { 537 | return this.dot(obj[key], tgt, path.concat(index)) 538 | } 539 | } else { 540 | if (isArray && this.useBrackets) { 541 | tgt[path.join(this.separator).concat('[' + key + ']')] = obj[key] 542 | } else { 543 | tgt[path.concat(index).join(this.separator)] = obj[key] 544 | } 545 | } 546 | }.bind(this) 547 | ) 548 | return tgt 549 | } 550 | 551 | DotObject.pick = wrap('pick') 552 | DotObject.move = wrap('move') 553 | DotObject.transfer = wrap('transfer') 554 | DotObject.transform = wrap('transform') 555 | DotObject.copy = wrap('copy') 556 | DotObject.object = wrap('object') 557 | DotObject.str = wrap('str') 558 | DotObject.set = wrap('set') 559 | DotObject.delete = wrap('delete') 560 | DotObject.del = DotObject.remove = wrap('remove') 561 | DotObject.dot = wrap('dot'); 562 | ['override', 'overwrite'].forEach(function (prop) { 563 | Object.defineProperty(DotObject, prop, { 564 | get: function () { 565 | return dotDefault.override 566 | }, 567 | set: function (val) { 568 | dotDefault.override = !!val 569 | } 570 | }) 571 | }); 572 | ['useArray', 'keepArray', 'useBrackets'].forEach(function (prop) { 573 | Object.defineProperty(DotObject, prop, { 574 | get: function () { 575 | return dotDefault[prop] 576 | }, 577 | set: function (val) { 578 | dotDefault[prop] = val 579 | } 580 | }) 581 | }) 582 | 583 | DotObject._process = _process 584 | 585 | module.exports = DotObject 586 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dot-object", 3 | "description": "dot-object makes it possible to transform and read (JSON) objects using dot notation.", 4 | "version": "2.1.5", 5 | "author": { 6 | "name": "Rob Halff", 7 | "email": "rob.halff@gmail.com" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/rhalff/dot-object.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/rhalff/dot-object/issues" 15 | }, 16 | "license": "MIT", 17 | "main": "index", 18 | "bin": "./bin/dot-object", 19 | "scripts": { 20 | "test": "gulp test", 21 | "watch": "gulp watch", 22 | "lint": "gulp lint", 23 | "dist": "gulp dist" 24 | }, 25 | "standard": { 26 | "globals": [ 27 | "it", 28 | "describe", 29 | "beforeEach" 30 | ] 31 | }, 32 | "devDependencies": { 33 | "eslint": "^7.9.0", 34 | "eslint-config-standard": "^14.1.1", 35 | "eslint-plugin-import": "^2.22.0", 36 | "eslint-plugin-node": "^11.1.0", 37 | "eslint-plugin-promise": "^4.2.1", 38 | "eslint-plugin-standard": "^4.0.1", 39 | "gulp": "^4.0.2", 40 | "gulp-beautify": "^3.0.0", 41 | "gulp-eslint": "^6.0.0", 42 | "gulp-headerfooter": "^1.0.3", 43 | "gulp-mocha": "^7.0.2", 44 | "gulp-rename": "^2.0.0", 45 | "gulp-uglify": "^3.0.2", 46 | "gulp-util": "^3.0.8", 47 | "mocha": "8.x.x", 48 | "should": "13.x.x", 49 | "underscore.string": "latest" 50 | }, 51 | "keywords": [ 52 | "json", 53 | "object", 54 | "filter", 55 | "transform", 56 | "dot notation", 57 | "dot" 58 | ], 59 | "dependencies": { 60 | "commander": "^6.1.0", 61 | "glob": "^7.1.6" 62 | }, 63 | "packageManager": "yarn@4.1.1" 64 | } 65 | -------------------------------------------------------------------------------- /src/dot-object.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function _process (v, mod) { 4 | var i 5 | var r 6 | 7 | if (typeof mod === 'function') { 8 | r = mod(v) 9 | if (r !== undefined) { 10 | v = r 11 | } 12 | } else if (Array.isArray(mod)) { 13 | for (i = 0; i < mod.length; i++) { 14 | r = mod[i](v) 15 | if (r !== undefined) { 16 | v = r 17 | } 18 | } 19 | } 20 | 21 | return v 22 | } 23 | 24 | function parseKey (key, val) { 25 | // detect negative index notation 26 | if (key[0] === '-' && Array.isArray(val) && /^-\d+$/.test(key)) { 27 | return val.length + parseInt(key, 10) 28 | } 29 | return key 30 | } 31 | 32 | function isIndex (k) { 33 | return /^\d+$/.test(k) 34 | } 35 | 36 | function isObject (val) { 37 | return Object.prototype.toString.call(val) === '[object Object]' 38 | } 39 | 40 | function isArrayOrObject (val) { 41 | return Object(val) === val 42 | } 43 | 44 | function isEmptyObject (val) { 45 | return Object.keys(val).length === 0 46 | } 47 | 48 | var blacklist = ['__proto__', 'prototype', 'constructor'] 49 | var blacklistFilter = function (part) { return blacklist.indexOf(part) === -1 } 50 | 51 | function parsePath (path, sep) { 52 | if (path.indexOf('[') >= 0) { 53 | path = path.replace(/\[/g, sep).replace(/]/g, '') 54 | } 55 | 56 | var parts = path.split(sep) 57 | 58 | var check = parts.filter(blacklistFilter) 59 | 60 | if (check.length !== parts.length) { 61 | throw Error('Refusing to update blacklisted property ' + path) 62 | } 63 | 64 | return parts 65 | } 66 | 67 | var hasOwnProperty = Object.prototype.hasOwnProperty 68 | 69 | function DotObject (separator, override, useArray, useBrackets) { 70 | if (!(this instanceof DotObject)) { 71 | return new DotObject(separator, override, useArray, useBrackets) 72 | } 73 | 74 | if (typeof override === 'undefined') override = false 75 | if (typeof useArray === 'undefined') useArray = true 76 | if (typeof useBrackets === 'undefined') useBrackets = true 77 | this.separator = separator || '.' 78 | this.override = override 79 | this.useArray = useArray 80 | this.useBrackets = useBrackets 81 | this.keepArray = false 82 | 83 | // contains touched arrays 84 | this.cleanup = [] 85 | } 86 | 87 | var dotDefault = new DotObject('.', false, true, true) 88 | function wrap (method) { 89 | return function () { 90 | return dotDefault[method].apply(dotDefault, arguments) 91 | } 92 | } 93 | 94 | DotObject.prototype._fill = function (a, obj, v, mod) { 95 | var k = a.shift() 96 | 97 | if (a.length > 0) { 98 | obj[k] = obj[k] || (this.useArray && isIndex(a[0]) ? [] : {}) 99 | 100 | if (!isArrayOrObject(obj[k])) { 101 | if (this.override) { 102 | obj[k] = {} 103 | } else { 104 | if (!(isArrayOrObject(v) && isEmptyObject(v))) { 105 | throw new Error( 106 | 'Trying to redefine `' + k + '` which is a ' + typeof obj[k] 107 | ) 108 | } 109 | 110 | return 111 | } 112 | } 113 | 114 | this._fill(a, obj[k], v, mod) 115 | } else { 116 | if (!this.override && isArrayOrObject(obj[k]) && !isEmptyObject(obj[k])) { 117 | if (!(isArrayOrObject(v) && isEmptyObject(v))) { 118 | throw new Error("Trying to redefine non-empty obj['" + k + "']") 119 | } 120 | 121 | return 122 | } 123 | 124 | obj[k] = _process(v, mod) 125 | } 126 | } 127 | 128 | /** 129 | * 130 | * Converts an object with dotted-key/value pairs to it's expanded version 131 | * 132 | * Optionally transformed by a set of modifiers. 133 | * 134 | * Usage: 135 | * 136 | * var row = { 137 | * 'nr': 200, 138 | * 'doc.name': ' My Document ' 139 | * } 140 | * 141 | * var mods = { 142 | * 'doc.name': [_s.trim, _s.underscored] 143 | * } 144 | * 145 | * dot.object(row, mods) 146 | * 147 | * @param {Object} obj 148 | * @param {Object} mods 149 | */ 150 | DotObject.prototype.object = function (obj, mods) { 151 | var self = this 152 | 153 | Object.keys(obj).forEach(function (k) { 154 | var mod = mods === undefined ? null : mods[k] 155 | // normalize array notation. 156 | var ok = parsePath(k, self.separator).join(self.separator) 157 | 158 | if (ok.indexOf(self.separator) !== -1) { 159 | self._fill(ok.split(self.separator), obj, obj[k], mod) 160 | delete obj[k] 161 | } else { 162 | obj[k] = _process(obj[k], mod) 163 | } 164 | }) 165 | 166 | return obj 167 | } 168 | 169 | /** 170 | * @param {String} path dotted path 171 | * @param {String} v value to be set 172 | * @param {Object} obj object to be modified 173 | * @param {Function|Array} mod optional modifier 174 | */ 175 | DotObject.prototype.str = function (path, v, obj, mod) { 176 | var ok = parsePath(path, this.separator).join(this.separator) 177 | 178 | if (path.indexOf(this.separator) !== -1) { 179 | this._fill(ok.split(this.separator), obj, v, mod) 180 | } else { 181 | obj[path] = _process(v, mod) 182 | } 183 | 184 | return obj 185 | } 186 | 187 | /** 188 | * 189 | * Pick a value from an object using dot notation. 190 | * 191 | * Optionally remove the value 192 | * 193 | * @param {String} path 194 | * @param {Object} obj 195 | * @param {Boolean} remove 196 | */ 197 | DotObject.prototype.pick = function (path, obj, remove, reindexArray) { 198 | var i 199 | var keys 200 | var val 201 | var key 202 | var cp 203 | 204 | keys = parsePath(path, this.separator) 205 | for (i = 0; i < keys.length; i++) { 206 | key = parseKey(keys[i], obj) 207 | if (obj && typeof obj === 'object' && key in obj) { 208 | if (i === keys.length - 1) { 209 | if (remove) { 210 | val = obj[key] 211 | if (reindexArray && Array.isArray(obj)) { 212 | obj.splice(key, 1) 213 | } else { 214 | delete obj[key] 215 | } 216 | if (Array.isArray(obj)) { 217 | cp = keys.slice(0, -1).join('.') 218 | if (this.cleanup.indexOf(cp) === -1) { 219 | this.cleanup.push(cp) 220 | } 221 | } 222 | return val 223 | } else { 224 | return obj[key] 225 | } 226 | } else { 227 | obj = obj[key] 228 | } 229 | } else { 230 | return undefined 231 | } 232 | } 233 | if (remove && Array.isArray(obj)) { 234 | obj = obj.filter(function (n) { 235 | return n !== undefined 236 | }) 237 | } 238 | return obj 239 | } 240 | /** 241 | * 242 | * Delete value from an object using dot notation. 243 | * 244 | * @param {String} path 245 | * @param {Object} obj 246 | * @return {any} The removed value 247 | */ 248 | DotObject.prototype.delete = function (path, obj) { 249 | return this.remove(path, obj, true) 250 | } 251 | 252 | /** 253 | * 254 | * Remove value from an object using dot notation. 255 | * 256 | * Will remove multiple items if path is an array. 257 | * In this case array indexes will be retained until all 258 | * removals have been processed. 259 | * 260 | * Use dot.delete() to automatically re-index arrays. 261 | * 262 | * @param {String|Array} path 263 | * @param {Object} obj 264 | * @param {Boolean} reindexArray 265 | * @return {any} The removed value 266 | */ 267 | DotObject.prototype.remove = function (path, obj, reindexArray) { 268 | var i 269 | 270 | this.cleanup = [] 271 | if (Array.isArray(path)) { 272 | for (i = 0; i < path.length; i++) { 273 | this.pick(path[i], obj, true, reindexArray) 274 | } 275 | if (!reindexArray) { 276 | this._cleanup(obj) 277 | } 278 | return obj 279 | } else { 280 | return this.pick(path, obj, true, reindexArray) 281 | } 282 | } 283 | 284 | DotObject.prototype._cleanup = function (obj) { 285 | var ret 286 | var i 287 | var keys 288 | var root 289 | if (this.cleanup.length) { 290 | for (i = 0; i < this.cleanup.length; i++) { 291 | keys = this.cleanup[i].split('.') 292 | root = keys.splice(0, -1).join('.') 293 | ret = root ? this.pick(root, obj) : obj 294 | ret = ret[keys[0]].filter(function (v) { 295 | return v !== undefined 296 | }) 297 | this.set(this.cleanup[i], ret, obj) 298 | } 299 | this.cleanup = [] 300 | } 301 | } 302 | 303 | /** 304 | * Alias method for `dot.remove` 305 | * 306 | * Note: this is not an alias for dot.delete() 307 | * 308 | * @param {String|Array} path 309 | * @param {Object} obj 310 | * @param {Boolean} reindexArray 311 | * @return {any} The removed value 312 | */ 313 | DotObject.prototype.del = DotObject.prototype.remove 314 | 315 | /** 316 | * 317 | * Move a property from one place to the other. 318 | * 319 | * If the source path does not exist (undefined) 320 | * the target property will not be set. 321 | * 322 | * @param {String} source 323 | * @param {String} target 324 | * @param {Object} obj 325 | * @param {Function|Array} mods 326 | * @param {Boolean} merge 327 | */ 328 | DotObject.prototype.move = function (source, target, obj, mods, merge) { 329 | if (typeof mods === 'function' || Array.isArray(mods)) { 330 | this.set(target, _process(this.pick(source, obj, true), mods), obj, merge) 331 | } else { 332 | merge = mods 333 | this.set(target, this.pick(source, obj, true), obj, merge) 334 | } 335 | 336 | return obj 337 | } 338 | 339 | /** 340 | * 341 | * Transfer a property from one object to another object. 342 | * 343 | * If the source path does not exist (undefined) 344 | * the property on the other object will not be set. 345 | * 346 | * @param {String} source 347 | * @param {String} target 348 | * @param {Object} obj1 349 | * @param {Object} obj2 350 | * @param {Function|Array} mods 351 | * @param {Boolean} merge 352 | */ 353 | DotObject.prototype.transfer = function ( 354 | source, 355 | target, 356 | obj1, 357 | obj2, 358 | mods, 359 | merge 360 | ) { 361 | if (typeof mods === 'function' || Array.isArray(mods)) { 362 | this.set( 363 | target, 364 | _process(this.pick(source, obj1, true), mods), 365 | obj2, 366 | merge 367 | ) 368 | } else { 369 | merge = mods 370 | this.set(target, this.pick(source, obj1, true), obj2, merge) 371 | } 372 | 373 | return obj2 374 | } 375 | 376 | /** 377 | * 378 | * Copy a property from one object to another object. 379 | * 380 | * If the source path does not exist (undefined) 381 | * the property on the other object will not be set. 382 | * 383 | * @param {String} source 384 | * @param {String} target 385 | * @param {Object} obj1 386 | * @param {Object} obj2 387 | * @param {Function|Array} mods 388 | * @param {Boolean} merge 389 | */ 390 | DotObject.prototype.copy = function (source, target, obj1, obj2, mods, merge) { 391 | if (typeof mods === 'function' || Array.isArray(mods)) { 392 | this.set( 393 | target, 394 | _process( 395 | // clone what is picked 396 | JSON.parse(JSON.stringify(this.pick(source, obj1, false))), 397 | mods 398 | ), 399 | obj2, 400 | merge 401 | ) 402 | } else { 403 | merge = mods 404 | this.set(target, this.pick(source, obj1, false), obj2, merge) 405 | } 406 | 407 | return obj2 408 | } 409 | 410 | /** 411 | * 412 | * Set a property on an object using dot notation. 413 | * 414 | * @param {String} path 415 | * @param {any} val 416 | * @param {Object} obj 417 | * @param {Boolean} merge 418 | */ 419 | DotObject.prototype.set = function (path, val, obj, merge) { 420 | var i 421 | var k 422 | var keys 423 | var key 424 | 425 | // Do not operate if the value is undefined. 426 | if (typeof val === 'undefined') { 427 | return obj 428 | } 429 | keys = parsePath(path, this.separator) 430 | 431 | for (i = 0; i < keys.length; i++) { 432 | key = keys[i] 433 | if (i === keys.length - 1) { 434 | if (merge && isObject(val) && isObject(obj[key])) { 435 | for (k in val) { 436 | if (hasOwnProperty.call(val, k)) { 437 | obj[key][k] = val[k] 438 | } 439 | } 440 | } else if (merge && Array.isArray(obj[key]) && Array.isArray(val)) { 441 | for (var j = 0; j < val.length; j++) { 442 | obj[keys[i]].push(val[j]) 443 | } 444 | } else { 445 | obj[key] = val 446 | } 447 | } else if ( 448 | // force the value to be an object 449 | !hasOwnProperty.call(obj, key) || 450 | (!isObject(obj[key]) && !Array.isArray(obj[key])) 451 | ) { 452 | // initialize as array if next key is numeric 453 | if (/^\d+$/.test(keys[i + 1])) { 454 | obj[key] = [] 455 | } else { 456 | obj[key] = {} 457 | } 458 | } 459 | obj = obj[key] 460 | } 461 | return obj 462 | } 463 | 464 | /** 465 | * 466 | * Transform an object 467 | * 468 | * Usage: 469 | * 470 | * var obj = { 471 | * "id": 1, 472 | * "some": { 473 | * "thing": "else" 474 | * } 475 | * } 476 | * 477 | * var transform = { 478 | * "id": "nr", 479 | * "some.thing": "name" 480 | * } 481 | * 482 | * var tgt = dot.transform(transform, obj) 483 | * 484 | * @param {Object} recipe Transform recipe 485 | * @param {Object} obj Object to be transformed 486 | * @param {Array} mods modifiers for the target 487 | */ 488 | DotObject.prototype.transform = function (recipe, obj, tgt) { 489 | obj = obj || {} 490 | tgt = tgt || {} 491 | Object.keys(recipe).forEach( 492 | function (key) { 493 | this.set(recipe[key], this.pick(key, obj), tgt) 494 | }.bind(this) 495 | ) 496 | return tgt 497 | } 498 | 499 | /** 500 | * 501 | * Convert object to dotted-key/value pair 502 | * 503 | * Usage: 504 | * 505 | * var tgt = dot.dot(obj) 506 | * 507 | * or 508 | * 509 | * var tgt = {} 510 | * dot.dot(obj, tgt) 511 | * 512 | * @param {Object} obj source object 513 | * @param {Object} tgt target object 514 | * @param {Array} path path array (internal) 515 | */ 516 | DotObject.prototype.dot = function (obj, tgt, path) { 517 | tgt = tgt || {} 518 | path = path || [] 519 | var isArray = Array.isArray(obj) 520 | 521 | Object.keys(obj).forEach( 522 | function (key) { 523 | var index = isArray && this.useBrackets ? '[' + key + ']' : key 524 | if ( 525 | isArrayOrObject(obj[key]) && 526 | ((isObject(obj[key]) && !isEmptyObject(obj[key])) || 527 | (Array.isArray(obj[key]) && !this.keepArray && obj[key].length !== 0)) 528 | ) { 529 | if (isArray && this.useBrackets) { 530 | var previousKey = path[path.length - 1] || '' 531 | return this.dot( 532 | obj[key], 533 | tgt, 534 | path.slice(0, -1).concat(previousKey + index) 535 | ) 536 | } else { 537 | return this.dot(obj[key], tgt, path.concat(index)) 538 | } 539 | } else { 540 | if (isArray && this.useBrackets) { 541 | tgt[path.join(this.separator).concat('[' + key + ']')] = obj[key] 542 | } else { 543 | tgt[path.concat(index).join(this.separator)] = obj[key] 544 | } 545 | } 546 | }.bind(this) 547 | ) 548 | return tgt 549 | } 550 | 551 | DotObject.pick = wrap('pick') 552 | DotObject.move = wrap('move') 553 | DotObject.transfer = wrap('transfer') 554 | DotObject.transform = wrap('transform') 555 | DotObject.copy = wrap('copy') 556 | DotObject.object = wrap('object') 557 | DotObject.str = wrap('str') 558 | DotObject.set = wrap('set') 559 | DotObject.delete = wrap('delete') 560 | DotObject.del = DotObject.remove = wrap('remove') 561 | DotObject.dot = wrap('dot'); 562 | ['override', 'overwrite'].forEach(function (prop) { 563 | Object.defineProperty(DotObject, prop, { 564 | get: function () { 565 | return dotDefault.override 566 | }, 567 | set: function (val) { 568 | dotDefault.override = !!val 569 | } 570 | }) 571 | }); 572 | ['useArray', 'keepArray', 'useBrackets'].forEach(function (prop) { 573 | Object.defineProperty(DotObject, prop, { 574 | get: function () { 575 | return dotDefault[prop] 576 | }, 577 | set: function (val) { 578 | dotDefault[prop] = val 579 | } 580 | }) 581 | }) 582 | 583 | DotObject._process = _process 584 | -------------------------------------------------------------------------------- /src/footer.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | if (typeof define === 'function' && define.amd) { 4 | define(function() { 5 | return DotObject 6 | }) 7 | } else if (typeof module != 'undefined' && module.exports) { 8 | module.exports = DotObject 9 | } else { 10 | global[exportName] = DotObject 11 | } 12 | 13 | })(this, 'DotObject') 14 | -------------------------------------------------------------------------------- /src/header.tpl: -------------------------------------------------------------------------------- 1 | (function(global, exportName) { 2 | -------------------------------------------------------------------------------- /test/_process.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var Dot = require('../index') 5 | 6 | describe('_process:', function () { 7 | describe('Should process modifier', function () { 8 | describe('if value is a string', function () { 9 | function up (val) { 10 | return val.toUpperCase() 11 | } 12 | 13 | it('using a single modifier', function () { 14 | Dot._process('k', up).should.eql('K') 15 | }) 16 | 17 | it('using an array of modifiers', function () { 18 | var v = 'k' 19 | Dot._process(v, [up]).should.eql('K') 20 | }) 21 | }) 22 | 23 | describe('if value is an object', function () { 24 | function withReturn (val) { 25 | val.withReturn = 'return' 26 | return val 27 | } 28 | 29 | function noReturn (val) { 30 | val.noReturn = 'no return' 31 | } 32 | 33 | it('using a single modifier *with* return', function () { 34 | var a = { 35 | test: 1 36 | } 37 | 38 | var expected = { 39 | test: 1, 40 | withReturn: 'return' 41 | } 42 | 43 | var ret = Dot._process(a, withReturn) 44 | a.should.eql(expected) 45 | ret.should.eql(expected) 46 | }) 47 | 48 | it('using a single modifier *without* return', function () { 49 | var a = { 50 | test: 1 51 | } 52 | var expected = { 53 | test: 1, 54 | noReturn: 'no return' 55 | } 56 | var ret = Dot._process(a, noReturn) 57 | a.should.eql(expected) 58 | ret.should.eql(expected) 59 | }) 60 | 61 | it('using an array of modifiers *with* return and *without* return', 62 | function () { 63 | var a = { 64 | test: 1 65 | } 66 | 67 | var expected = { 68 | test: 1, 69 | withReturn: 'return', 70 | noReturn: 'no return' 71 | } 72 | 73 | var ret = Dot._process(a, [withReturn, noReturn]) 74 | 75 | a.should.eql(expected) 76 | ret.should.eql(expected) 77 | } 78 | ) 79 | }) 80 | 81 | describe('if value is an array', function () { 82 | function withReturn (val) { 83 | val.push('return') 84 | return val 85 | } 86 | 87 | function noReturn (val) { 88 | val.push('no return') 89 | } 90 | 91 | it('using a single modifier *with* return', function () { 92 | var a = [1] 93 | 94 | var expected = [1, 'return'] 95 | 96 | var ret = Dot._process(a, withReturn) 97 | a.should.eql(expected) 98 | ret.should.eql(expected) 99 | }) 100 | 101 | it('using a single modifier *without* return', function () { 102 | var a = [1] 103 | 104 | var expected = [1, 'no return'] 105 | 106 | var ret = Dot._process(a, noReturn) 107 | a.should.eql(expected) 108 | ret.should.eql(expected) 109 | }) 110 | 111 | it('using an array of modifiers *with* return and *without* return', 112 | function () { 113 | var a = [1] 114 | 115 | var expected = [1, 'return', 'no return'] 116 | 117 | var ret = Dot._process(a, [withReturn, noReturn]) 118 | 119 | a.should.eql(expected) 120 | ret.should.eql(expected) 121 | } 122 | ) 123 | }) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /test/array_notation.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* jshint -W030 */ 4 | 5 | require('should') 6 | var Dot = require('../index') 7 | 8 | describe('Dotted Array notation', function () { 9 | var src 10 | 11 | beforeEach(function () { 12 | src = { 13 | path: [{ 14 | longitude: 5.512482166290283, 15 | latitude: 52.5006217956543 16 | }, { 17 | longitude: 5.512370586395264, 18 | latitude: 52.50059509277344 19 | }, { 20 | longitude: 5.512370586395264, 21 | latitude: 52.50059509277344 22 | }] 23 | } 24 | }) 25 | 26 | function runVariant (type) { 27 | var v = function (v) { 28 | if (type === 'bracket') { 29 | // rewrite some.prop.1 to some.prop[1] 30 | return v.replace(/\.(-?\d+)/g, '[$1]') 31 | } else { 32 | return v 33 | } 34 | } 35 | 36 | describe('can pick', function () { 37 | it('index', function () { 38 | Dot.pick(v('path.0'), src).should.eql(src.path[0]) 39 | Dot.pick(v('path.2'), src).should.eql(src.path[2]) 40 | ;(typeof Dot.pick(v('path.9'), src)).should.eql('undefined') 41 | }) 42 | 43 | it('negative index', function () { 44 | Dot.pick(v('path.-1'), src).should.eql(src.path[2]) 45 | Dot.pick(v('path.-2'), src).should.eql(src.path[1]) 46 | Dot.pick(v('path.-3'), src).should.eql(src.path[0]) 47 | ;(typeof Dot.pick(v('path.-9'), src)).should.eql('undefined') 48 | }) 49 | 50 | it('non-array `-` prefixed properties', function () { 51 | var src = { 52 | path: { 53 | '-1': 'test1', 54 | '-2': 'test2', 55 | '-3': 'test3', 56 | '----key': 'test4' 57 | } 58 | } 59 | Dot.pick(v('path.-1'), src).should.eql('test1') 60 | Dot.pick(v('path.-2'), src).should.eql('test2') 61 | Dot.pick(v('path.-3'), src).should.eql('test3') 62 | Dot.pick(v('path.----key'), src).should.eql('test4') 63 | ;(typeof Dot.pick(v('path.-9'), src)).should.eql('undefined') 64 | }) 65 | 66 | it('multiple indexes', function () { 67 | var src = { 68 | I: [ 69 | { am: [{ nes: ['ted'] }] }, 70 | { me: 'too' } 71 | ] 72 | } 73 | 74 | Dot.pick(v('I.0'), src).should.eql(src.I[0]) 75 | Dot.pick(v('I.0.am'), src).should.eql(src.I[0].am) 76 | Dot.pick(v('I.0.am.0'), src).should.eql(src.I[0].am[0]) 77 | Dot.pick(v('I.0.am.0.nes'), src).should.eql(src.I[0].am[0].nes) 78 | Dot.pick(v('I.0.am.0.nes.0'), src).should.eql('ted') 79 | Dot.pick(v('I.1.me'), src).should.eql('too') 80 | }) 81 | }) 82 | 83 | describe('can set', function () { 84 | it('index at target', function () { 85 | var obj = { path: [] } 86 | 87 | Dot.set(v('path.0'), 'test', obj) 88 | Dot.set(v('path.1'), 'test2', obj) 89 | 90 | obj.path.should.be.instanceOf(Array) 91 | obj.should.eql({ path: ['test', 'test2'] }) 92 | }) 93 | 94 | it('index and set undefined for empty indices', function () { 95 | var obj = { path: [] } 96 | 97 | Dot.set(v('path.0'), 'test', obj) 98 | Dot.set(v('path.2'), 'test2', obj) 99 | 100 | obj.path.should.be.instanceOf(Array) 101 | 102 | // array will have an undefined index. 103 | JSON.stringify(obj) 104 | .should.eql( 105 | JSON.stringify({ path: ['test', undefined, 'test2'] }) 106 | ) 107 | 108 | // to json will converted it to null 109 | JSON.stringify(obj).should.eql('{"path":["test",null,"test2"]}') 110 | }) 111 | 112 | it('index and overwrite existing values', function () { 113 | var obj = { path: ['still', 'shall', 'be', 'gone', 'here'] } 114 | 115 | Dot.set(v('path.1'), 'x', obj) 116 | Dot.set(v('path.2'), 'xx', obj) 117 | Dot.set(v('path.3'), 'xxx', obj) 118 | 119 | obj.should.eql({ path: ['still', 'x', 'xx', 'xxx', 'here'] }) 120 | }) 121 | }) 122 | 123 | describe('can remove', function () { 124 | it('indexes one by one leaving traces', function () { 125 | var obj = { path: ['still', 'shall', 'really', 'be', 'gone', 'here'] } 126 | 127 | Dot.remove(v('path.1'), obj) 128 | Dot.remove(v('path.2'), obj) 129 | Dot.del(v('path.3'), obj) // use alias 130 | Dot.del(v('path.4'), obj) 131 | 132 | // array will have an undefined index. 133 | JSON.stringify(obj) 134 | .should.eql( 135 | JSON.stringify({ 136 | path: [ 137 | 'still', undefined, undefined, undefined, undefined, 'here' 138 | ] 139 | }) 140 | ) 141 | 142 | // to json will converted it to null 143 | JSON.stringify(obj).should.eql( 144 | '{"path":["still",null,null,null,null,"here"]}' 145 | ) 146 | }) 147 | 148 | it('array of indexes leaving no traces', function () { 149 | var obj = { path: ['still', 'shall', 'really', 'be', 'gone', 'here'] } 150 | 151 | Dot.remove([ 152 | v('path.1'), 153 | v('path.2'), 154 | v('path.3'), 155 | v('path.4')], obj) 156 | 157 | JSON.stringify(obj).should.eql('{"path":["still","here"]}') 158 | }) 159 | }) 160 | } 161 | 162 | describe('with dot notation', function () { 163 | runVariant() 164 | }) 165 | 166 | // extra logic no real benefit. 167 | describe('with bracket notation', function () { 168 | runVariant('bracket') 169 | }) 170 | 171 | describe('Refuse to update __proto__', function () { 172 | var obj = { path: [] } 173 | 174 | ;(() => Dot.set('path[0].__proto__.toString', 'test', obj)).should.throw(/Refusing to update/) 175 | }) 176 | }) 177 | -------------------------------------------------------------------------------- /test/copy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var Dot = require('../index') 5 | 6 | describe('Copy:', function () { 7 | it('Should be able to copy properties', function () { 8 | var src = { 9 | name: 'John', 10 | stuff: { 11 | phone: { 12 | brand: 'iphone', 13 | version: 6 14 | } 15 | } 16 | } 17 | 18 | var tgt = { 19 | name: 'Brandon' 20 | } 21 | 22 | var srcExpected = JSON.parse(JSON.stringify(src)) 23 | 24 | var tgtExpected = { 25 | name: 'Brandon', 26 | copied: 'John', 27 | wanna: { 28 | haves: { 29 | phone: { 30 | brand: 'iphone', 31 | version: 6 32 | } 33 | } 34 | } 35 | } 36 | 37 | // copy object 38 | Dot.copy('stuff.phone', 'wanna.haves.phone', src, tgt) 39 | 40 | // copy string 41 | Dot.copy('name', 'copied', src, tgt) 42 | 43 | src.should.eql(srcExpected) 44 | tgt.should.eql(tgtExpected) 45 | }) 46 | 47 | it('Should process modifiers', function () { 48 | function up (val) { 49 | val.brand = val.brand.toUpperCase() 50 | return val 51 | } 52 | 53 | var src = { 54 | name: 'John', 55 | stuff: { 56 | phone: { 57 | brand: 'iphone', 58 | version: 6 59 | } 60 | } 61 | } 62 | 63 | var tgt = { 64 | name: 'Brandon' 65 | } 66 | 67 | var srcExpected = JSON.parse(JSON.stringify(src)) 68 | 69 | var tgtExpected = { 70 | name: 'Brandon', 71 | wanna: { 72 | haves: { 73 | phone: { 74 | brand: 'IPHONE', 75 | version: 6 76 | } 77 | } 78 | } 79 | } 80 | 81 | Dot.copy('stuff.phone', 'wanna.haves.phone', src, tgt, up) 82 | 83 | src.should.eql(srcExpected) 84 | tgt.should.eql(tgtExpected) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /test/data/array_deep_bug.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "array deep bug #10", 3 | "options": { 4 | "useArray": true 5 | }, 6 | "input": { 7 | "a[0]": "A0", 8 | "a[1]": "A1", 9 | "a[2]": "A2", 10 | "a[3].b[0].c[0]": "A3B0C0", 11 | "a[3].b[0].c[1]": "A3B0C1" 12 | }, 13 | "expected": { 14 | "a": [ 15 | "A0", 16 | "A1", 17 | "A2", 18 | { 19 | "b": [ 20 | { 21 | "c": [ 22 | "A3B0C0", 23 | "A3B0C1" 24 | ] 25 | } 26 | ] 27 | } 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/data/array_deep_bug2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "array deep bug2 #10", 3 | "options": { 4 | "useArray": true 5 | }, 6 | "input": { 7 | "b.0.c.1": "b0c1", 8 | "b.0.c.2": "b0c2" 9 | }, 10 | "expected": { 11 | "b" : [ { 12 | "c" : [ 13 | null, 14 | "b0c1", 15 | "b0c2" 16 | ] 17 | }] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/data/empty_array.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "empty array", 3 | "input": [ 4 | { 5 | "a": [] 6 | }, 7 | { 8 | "a.0": 1, 9 | "a": [] 10 | }, 11 | { 12 | "a": [], 13 | "a.0": 2 14 | }, 15 | { 16 | "a.0.b": [] 17 | }, 18 | { 19 | "b.a.0": "b", 20 | "b.a": [], 21 | "b.a.1": "c" 22 | } 23 | ], 24 | "expected": [ 25 | { 26 | "a": [] 27 | }, 28 | { 29 | "a": [1] 30 | }, 31 | { 32 | "a": [2] 33 | }, 34 | { 35 | "a" : [ {"b" : [] } ] 36 | }, 37 | { 38 | "b" : { 39 | "a": [ "b", "c" ] 40 | } 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /test/data/empty_object.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "empty object", 3 | "input": [ 4 | { 5 | "a.b": {} 6 | }, 7 | { 8 | "a.b.c": 1, 9 | "a.b": {} , 10 | "a.b.d": 2 11 | } 12 | ], 13 | "expected": [ 14 | { 15 | "a" : {"b" : {} } 16 | }, 17 | { 18 | "a" : { 19 | "b": { "c": 1, "d": 2 } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /test/data/index.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | require('./array_deep_bug'), 3 | require('./array_deep_bug2'), 4 | require('./empty_array'), 5 | require('./empty_object'), 6 | require('./object_deep_numeric_keys'), 7 | require('./object_deep_numeric_keys2') 8 | ] 9 | -------------------------------------------------------------------------------- /test/data/object_deep_numeric_keys.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "object deep numeric keys", 3 | "options": { 4 | "useArray": false 5 | }, 6 | "input": { 7 | "a[0]": "A0", 8 | "a[1]": "A1", 9 | "a[2]": "A2", 10 | "a[3].b[0].c[0]": "A3B0C0", 11 | "a[3].b[0].c[1]": "A3B0C1" 12 | }, 13 | "expected": { 14 | "a": { 15 | "0": "A0", 16 | "1": "A1", 17 | "2": "A2", 18 | "3": { 19 | "b": { 20 | "0": { 21 | "c": { 22 | "0": "A3B0C0", 23 | "1": "A3B0C1" 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/data/object_deep_numeric_keys2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "object deep numeric keys 2 #10", 3 | "options": { 4 | "useArray": false 5 | }, 6 | "input": { 7 | "b.0.c.1": "b0c1", 8 | "b.0.c.2": "b0c2" 9 | }, 10 | "expected": { 11 | "b": { 12 | "0": { 13 | "c": { 14 | "1": "b0c1", 15 | "2": "b0c2" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/dot-json.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var _s = require('underscore.string') 5 | var Dot = require('../index') 6 | 7 | describe('Object test:', function () { 8 | it('Should expand dotted keys', function () { 9 | var row = { 10 | id: 2, 11 | 'contact.name.first': 'John', 12 | 'contact.name.last': 'Doe', 13 | 'contact.email': 'example@gmail.com', 14 | 'contact.info.about.me': 'classified' 15 | } 16 | 17 | Dot.object(row) 18 | 19 | row.should.eql({ 20 | id: 2, 21 | contact: { 22 | name: { 23 | first: 'John', 24 | last: 'Doe' 25 | }, 26 | email: 'example@gmail.com', 27 | info: { 28 | about: { 29 | me: 'classified' 30 | } 31 | } 32 | } 33 | }) 34 | }) 35 | 36 | it('Should expand dotted keys with array notation', function () { 37 | var row = { 38 | id: 2, 39 | 'my.arr.0': 'one', 40 | 'my.arr.1': 'two', 41 | 'my.arr.2': 'three', 42 | 'my.arr2[0]': 'one', 43 | 'my.arr2[1]': 'two', 44 | 'my.arr2[2]': 'three' 45 | } 46 | 47 | Dot.object(row) 48 | 49 | row.should.eql({ 50 | id: 2, 51 | my: { 52 | arr: ['one', 'two', 'three'], 53 | arr2: ['one', 'two', 'three'] 54 | } 55 | }) 56 | }) 57 | 58 | it('Should expand dotted keys with array notation with different separator', function () { 59 | var row = { 60 | id: 2, 61 | my_arr_0: 'one', 62 | my_arr_1: 'two', 63 | my_arr_2: 'three', 64 | 'my_arr2[0]': 'one', 65 | 'my_arr2[1]': 'two', 66 | 'my_arr2[2]': 'three' 67 | } 68 | 69 | new Dot('_').object(row) 70 | 71 | row.should.eql({ 72 | id: 2, 73 | my: { 74 | arr: ['one', 'two', 'three'], 75 | arr2: ['one', 'two', 'three'] 76 | } 77 | }) 78 | }) 79 | 80 | it('Should allow keys with numbers', function () { 81 | var row = { 82 | id: 2, 83 | '0A': 'a', 84 | '0A9': 'b', 85 | '0B.1AB.A34C9': 'c' 86 | } 87 | 88 | Dot.object(row) 89 | 90 | row.should.eql({ 91 | id: 2, 92 | '0A': 'a', 93 | '0A9': 'b', 94 | '0B': { 95 | '1AB': { 96 | A34C9: 'c' 97 | } 98 | } 99 | }) 100 | }) 101 | 102 | it('Should expand dotted string', function () { 103 | var tgt = {} 104 | 105 | Dot.str('this.is.my.string', 'value', tgt) 106 | 107 | tgt.should.eql({ 108 | this: { 109 | is: { 110 | my: { 111 | string: 'value' 112 | } 113 | } 114 | } 115 | }) 116 | }) 117 | 118 | it('Dot.str Redefinition should fail', function () { 119 | var tgt = { 120 | already: 'set' 121 | } 122 | 123 | ;(function () { 124 | Dot.str('already.new', 'value', tgt) 125 | }).should.throw('Trying to redefine `already` which is a string') 126 | }) 127 | 128 | it('Dot.str should process a modifier', function () { 129 | var tgt = {} 130 | 131 | Dot.str('this.is.my.string', 'value', tgt, _s.capitalize) 132 | 133 | tgt.should.eql({ 134 | this: { 135 | is: { 136 | my: { 137 | string: 'Value' 138 | } 139 | } 140 | } 141 | }) 142 | }) 143 | 144 | it('Dot.str should process multiple modifiers', function () { 145 | var tgt = {} 146 | 147 | Dot.str( 148 | 'this.is.my.string', 149 | ' this is a test ', 150 | tgt, [_s.trim, _s.underscored] 151 | ) 152 | 153 | tgt.should.eql({ 154 | this: { 155 | is: { 156 | my: { 157 | string: 'this_is_a_test' 158 | } 159 | } 160 | } 161 | }) 162 | }) 163 | 164 | it('Dot.object should process a modifier', function () { 165 | var row = { 166 | 'page.title': 'my page', 167 | 'page.slug': 'My Page' 168 | } 169 | 170 | var mods = { 171 | 'page.title': _s.titleize, 172 | 'page.slug': _s.slugify 173 | } 174 | 175 | Dot.object(row, mods) 176 | 177 | row.should.eql({ page: { title: 'My Page', slug: 'my-page' } }) 178 | }) 179 | 180 | it('should process root properties', 181 | function () { 182 | var row = { 183 | nr: 200, 184 | 'nested.nr': 200 185 | } 186 | 187 | var mods = { 188 | nr: [val => val * 2], 189 | 'nested.nr': [val => val * 2] 190 | } 191 | 192 | Dot.object(row, mods) 193 | 194 | row.should.eql({ nr: 400, nested: { nr: 400 } }) 195 | } 196 | ) 197 | 198 | it('should process non dot value with modifier when override is false', 199 | function () { 200 | var row = { title: 'my page', slug: 'My Page' } 201 | 202 | var mods = { title: _s.titleize, slug: _s.slugify } 203 | 204 | Dot.object(row, mods) 205 | 206 | row.should.eql({ title: 'My Page', slug: 'my-page' }) 207 | } 208 | ) 209 | 210 | it('Dot.object should process multiple modifiers', function () { 211 | var row = { 'page.name': ' My Page ' } 212 | 213 | var mods = { 'page.name': [_s.trim, _s.underscored] } 214 | 215 | Dot.object(row, mods) 216 | 217 | row.should.eql({ page: { name: 'my_page' } }) 218 | }) 219 | 220 | it('Dot.object should work with a different separator', function () { 221 | var row = { 'page=>name': ' My Page ' } 222 | 223 | var mods = { 'page=>name': [_s.trim, _s.underscored] } 224 | 225 | var dot = new Dot('=>', false) 226 | dot.object(row, mods) 227 | 228 | row.should.eql({ page: { name: 'my_page' } }) 229 | }) 230 | 231 | it('Dot.object should disallow to set __proto__', function () { 232 | var row = { '__proto__.toString': 'hi' } 233 | 234 | var dot = new Dot() 235 | ;(() => dot.object(row)).should.throw(/Refusing to update/) 236 | }) 237 | }) 238 | -------------------------------------------------------------------------------- /test/dot.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var Dot = require('../index') 5 | var pkg = require('./fixtures/package.json') 6 | 7 | describe('dot():', function () { 8 | var obj 9 | 10 | // Dot.useBrackets = false; 11 | 12 | beforeEach(function () { 13 | obj = { 14 | id: 'my-id', 15 | nes: { 16 | ted: { 17 | value: true 18 | } 19 | }, 20 | other: { 21 | nested: { 22 | stuff: 5 23 | } 24 | }, 25 | nested: { 26 | array: [ 27 | { 28 | with: 'object1' 29 | }, 30 | { 31 | and: 'object2' 32 | } 33 | ] 34 | }, 35 | some: { 36 | array: ['A', 'B'] 37 | }, 38 | ehrm: 123, 39 | dates: { 40 | first: new Date('Mon Oct 13 2014 00:00:00 GMT+0100 (BST)') 41 | }, 42 | arrays: [ 43 | [ 44 | [ 45 | { 46 | all: [ 47 | [ 48 | { 49 | the: [ 50 | 'way', 51 | ['down'] 52 | ] 53 | } 54 | ] 55 | ] 56 | } 57 | ] 58 | ] 59 | ] 60 | } 61 | }) 62 | 63 | it('Should be able to convert to dotted-key/value pairs', function () { 64 | var expected = { 65 | id: 'my-id', 66 | 'nes.ted.value': true, 67 | 'other.nested.stuff': 5, 68 | 'nested.array[0].with': 'object1', 69 | 'nested.array[1].and': 'object2', 70 | 'some.array[0]': 'A', 71 | 'some.array[1]': 'B', 72 | ehrm: 123, 73 | 'dates.first': new Date('Mon Oct 13 2014 00:00:00 GMT+0100 (BST)'), 74 | 'arrays[0][0][0].all[0][0].the[0]': 'way', 75 | 'arrays[0][0][0].all[0][0].the[1][0]': 'down' 76 | } 77 | 78 | Dot.dot(obj).should.eql(expected) 79 | }) 80 | 81 | it('dot() should equal object()', function () { 82 | Dot.object(Dot.dot(pkg)).should.eql(pkg) 83 | }) 84 | 85 | it('keepArray prevents arrays from being dotted', function () { 86 | var expected = { 87 | id: 'my-id', 88 | 'nes.ted.value': true, 89 | 'other.nested.stuff': 5, 90 | 'nested.array': [{ 91 | with: 'object1' 92 | }, { 93 | and: 'object2' 94 | }], 95 | 'some.array': ['A', 'B'], 96 | ehrm: 123, 97 | 'dates.first': new Date('Mon Oct 13 2014 00:00:00 GMT+0100 (BST)'), 98 | arrays: JSON.parse(JSON.stringify(obj.arrays)) 99 | } 100 | 101 | Dot.keepArray = true 102 | 103 | Dot.dot(obj).should.eql(expected) 104 | 105 | Dot.keepArray = false 106 | }) 107 | 108 | it('useBrackets wrap indexes with brackets', function () { 109 | var expected = { 110 | id: 'my-id', 111 | 'nes.ted.value': true, 112 | 'other.nested.stuff': 5, 113 | 'nested.array[0].with': 'object1', 114 | 'nested.array[1].and': 'object2', 115 | 'some.array[0]': 'A', 116 | 'some.array[1]': 'B', 117 | ehrm: 123, 118 | 'dates.first': new Date('Mon Oct 13 2014 00:00:00 GMT+0100 (BST)'), 119 | 'arrays[0][0][0].all[0][0].the[0]': 'way', 120 | 'arrays[0][0][0].all[0][0].the[1][0]': 'down' 121 | } 122 | 123 | Dot.dot(obj).should.eql(expected) 124 | }) 125 | 126 | it('useBrackets wrap indexes without brackets', function () { 127 | var expected = { 128 | id: 'my-id', 129 | 'nes.ted.value': true, 130 | 'other.nested.stuff': 5, 131 | 'nested.array.0.with': 'object1', 132 | 'nested.array.1.and': 'object2', 133 | 'some.array.0': 'A', 134 | 'some.array.1': 'B', 135 | ehrm: 123, 136 | 'dates.first': new Date('Mon Oct 13 2014 00:00:00 GMT+0100 (BST)'), 137 | 'arrays.0.0.0.all.0.0.the.0': 'way', 138 | 'arrays.0.0.0.all.0.0.the.1.0': 'down' 139 | } 140 | 141 | Dot.useBrackets = false 142 | Dot.dot(obj).should.eql(expected) 143 | Dot.useBrackets = true 144 | }) 145 | 146 | it('Always keeps empty arrays', function () { 147 | Dot.dot({ hello: [] }).should.eql({ hello: [] }) 148 | Dot.dot({ hello: { world: [] } }).should.eql({ 'hello.world': [] }) 149 | }) 150 | 151 | it('Always keeps empty objects', function () { 152 | Dot.dot({ hello: {} }).should.eql({ hello: {} }) 153 | Dot.dot({ hello: { world: {} } }).should.eql({ 'hello.world': {} }) 154 | }) 155 | }) 156 | -------------------------------------------------------------------------------- /test/fixtures/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dot-object", 3 | "description": "dot-object makes it possible to transform and read (JSON) objects using dot notation.", 4 | "author": { 5 | "name": "Rob Halff", 6 | "email": "rob.halff@gmail.com" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/rhalff/dot-object.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/rhalff/dot-object/issues" 14 | }, 15 | "main": "index", 16 | "bin": "./bin/dot-object", 17 | "scripts": { 18 | "test": "gulp test" 19 | }, 20 | "devDependencies": { 21 | "gulp": "^3.9.0", 22 | "gulp-beautify": "^1.1.2", 23 | "gulp-headerfooter": "^1.0.3", 24 | "gulp-jscs": "^2.0.0", 25 | "gulp-jshint": "^1.11.2", 26 | "gulp-mocha": "^2.1.3", 27 | "gulp-rename": "^1.2.2", 28 | "gulp-uglify": "^1.2.0", 29 | "gulp-util": "^3.0.6", 30 | "jscs": "^2.0.0", 31 | "jscs-jsdoc": "1.1.0", 32 | "mocha": "2.x.x" 33 | }, 34 | "keywords": [ 35 | "json", 36 | "filter", 37 | "transform", 38 | "dot notation", 39 | "dot" 40 | ], 41 | "dependencies": { 42 | "commander": "^2.8.1", 43 | "glob": "^5.0.14" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/merge.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var Dot = require('../index') 5 | 6 | describe('Should be able to merge:', function () { 7 | it('to property', function () { 8 | var link = { 9 | other: { 10 | three: 'Three Things', 11 | four: 'Four Things' 12 | }, 13 | things: { 14 | one: 'One Thing', 15 | two: 'Two Things' 16 | } 17 | } 18 | 19 | var expected = { 20 | things: { 21 | one: 'One Thing', 22 | two: 'Two Things', 23 | three: 'Three Things', 24 | four: 'Four Things' 25 | } 26 | } 27 | 28 | Dot.move('other', 'things', link, true) 29 | 30 | link.should.eql(expected) 31 | }) 32 | 33 | it('to nested property', function () { 34 | var link = { 35 | other: { 36 | three: 'Three Things', 37 | four: 'Four Things' 38 | }, 39 | things: { 40 | one: 'One Thing', 41 | two: 'Two Things', 42 | target: { 43 | im: 'already here' 44 | } 45 | } 46 | } 47 | 48 | var expected = { 49 | things: { 50 | one: 'One Thing', 51 | two: 'Two Things', 52 | target: { 53 | im: 'already here', 54 | three: 'Three Things', 55 | four: 'Four Things' 56 | } 57 | } 58 | } 59 | 60 | Dot.move('other', 'things.target', link, true) 61 | 62 | link.should.eql(expected) 63 | }) 64 | 65 | it('array to array', function () { 66 | var link = { 67 | other: [ 68 | 'Three Things', 69 | 'Four Things' 70 | ], 71 | things: { 72 | one: 'One Thing', 73 | two: 'Two Things', 74 | target: [ 75 | 'already here' 76 | ] 77 | } 78 | } 79 | 80 | var expected = { 81 | things: { 82 | one: 'One Thing', 83 | two: 'Two Things', 84 | target: [ 85 | 'already here', 86 | 'Three Things', 87 | 'Four Things' 88 | ] 89 | } 90 | } 91 | 92 | Dot.move('other', 'things.target', link, true) 93 | 94 | link.should.eql(expected) 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /test/move.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var Dot = require('../index') 5 | 6 | describe('Move test:', function () { 7 | it('Should be able to move properties', function () { 8 | var link = { 9 | id: '527423a65e380f0000588e47', 10 | source: '526dd5c6b4c4aa8770000001', 11 | target: '527402d6b15d1800008755cf', 12 | out: 'github', 13 | in: 'in' 14 | } 15 | 16 | var expected = { 17 | id: '527423a65e380f0000588e47', 18 | source: { id: '526dd5c6b4c4aa8770000001', port: 'github' }, 19 | target: { id: '527402d6b15d1800008755cf', port: 'in' } 20 | } 21 | 22 | Dot.move('source', 'source.id', link) 23 | Dot.move('out', 'source.port', link) 24 | Dot.move('target', 'target.id', link) 25 | Dot.move('in', 'target.port', link) 26 | 27 | link.should.eql(expected) 28 | }) 29 | 30 | it('Undefined properties should be ignored', function () { 31 | var link = { 32 | source: '526dd5c6b4c4aa8770000001', 33 | target: '527402d6b15d1800008755cf', 34 | out: 'github', 35 | in: 'in' 36 | } 37 | 38 | var expected = { 39 | source: { id: '526dd5c6b4c4aa8770000001' }, 40 | target: { port: 'in' }, 41 | out: 'github' 42 | } 43 | 44 | Dot.move('source', 'source.id', link) 45 | Dot.move('out.er.nope', 'source.port', link) 46 | Dot.move('target.bla.di.bla', 'target.id', link) 47 | Dot.move('in', 'target.port', link) 48 | 49 | link.should.eql(expected) 50 | }) 51 | 52 | it('Should process modifiers', function () { 53 | var link = { 54 | source: 'one', 55 | target: 'two' 56 | } 57 | 58 | var expected = { 59 | source: { id: 'ONE' }, 60 | target: { port: 'TWO' } 61 | } 62 | 63 | function up (val) { return val.toUpperCase() } 64 | 65 | Dot.move('source', 'source.id', link, up) 66 | Dot.move('target', 'target.port', link, up) 67 | 68 | link.should.eql(expected) 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /test/override.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var _s = require('underscore.string') 5 | var Dot = require('../index') 6 | 7 | describe('Override test:', function () { 8 | it('Redefinition should _not_ fail if override is true', function () { 9 | var dot = new Dot('.', true) 10 | 11 | var obj = { 12 | some: 'value', 13 | already: 'set' 14 | } 15 | 16 | dot.str('already.new', 'value', obj) 17 | 18 | obj.should.eql({ 19 | some: 'value', 20 | already: { new: 'value' } 21 | }) 22 | }) 23 | 24 | it('Redefinition should _not_ fail if override is true (2)', function () { 25 | var dot = new Dot('.', true) 26 | 27 | var obj = { 28 | some: 'value', 29 | already: 'set' 30 | } 31 | 32 | dot.str('already.new', 'value', obj) 33 | dot.str('some', 'new_value', obj) 34 | 35 | obj.should.eql({ 36 | some: 'new_value', 37 | already: { new: 'value' } 38 | }) 39 | }) 40 | 41 | it('Allow override even when target is non-empty object', 42 | function () { 43 | var obj = { 44 | sample: { 45 | dotted: { 46 | bar: { 47 | baz: 'baz' 48 | } 49 | } 50 | } 51 | } 52 | 53 | Dot.override = true 54 | 55 | Dot.str('sample.dotted.bar', { baz: 'boom' }, obj) 56 | 57 | Dot.override = false 58 | 59 | obj.should.eql({ 60 | sample: { 61 | dotted: { 62 | bar: { 63 | baz: 'boom' 64 | } 65 | } 66 | } 67 | }) 68 | } 69 | ) 70 | 71 | it('should process non dot notation value with modifier if override is true', 72 | function () { 73 | var dot = new Dot('.', true) 74 | 75 | var row = { 76 | title: 'my page', 77 | slug: 'My Page' 78 | } 79 | 80 | var mods = { 81 | title: _s.titleize, 82 | slug: _s.slugify 83 | } 84 | 85 | dot.object(row, mods) 86 | 87 | row.should.eql({ 88 | title: 'My Page', 89 | slug: 'my-page' 90 | }) 91 | } 92 | ) 93 | }) 94 | -------------------------------------------------------------------------------- /test/pick.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* jshint -W030 */ 4 | 5 | require('should') 6 | var Dot = require('../index') 7 | 8 | describe('Pick:', function () { 9 | it('Should be able to pick a value', function () { 10 | var obj = { 11 | some: 'value', 12 | already: 'set' 13 | } 14 | 15 | var val = Dot.pick('some', obj) 16 | 17 | val.should.eql('value') 18 | }) 19 | 20 | it('Should be able to pick dotted value', function () { 21 | var obj = { 22 | some: { 23 | other: 'value' 24 | } 25 | } 26 | 27 | var val = Dot.pick('some.other', obj) 28 | 29 | val.should.eql('value') 30 | }) 31 | 32 | it('Should be able to pick null properties', function () { 33 | var obj = { 34 | some: null 35 | } 36 | 37 | var val = Dot.pick('some', obj) 38 | 39 | ;(val === null).should.equal(true) 40 | }) 41 | 42 | it('Should return undefined when picking an non-existing value', function () { 43 | var obj = { 44 | some: null 45 | } 46 | 47 | var val = Dot.pick('other', obj) 48 | 49 | ;(val === undefined).should.equal(true) 50 | }) 51 | 52 | it('Should return undefined when picking an non-existing dotted value', 53 | function () { 54 | var obj = { 55 | some: null 56 | } 57 | 58 | var val = Dot.pick('some.other', obj) 59 | 60 | ;(val === undefined).should.equal(true) 61 | } 62 | ) 63 | 64 | it("Should check down the object's prototype chain", function () { 65 | var obj = { 66 | some: { 67 | other: 'value' 68 | } 69 | } 70 | 71 | var objIns = Object.create(obj) 72 | 73 | objIns.should.have.property('some') 74 | 75 | var val = Dot.pick('some.other', objIns) 76 | val.should.be.instanceOf(String) 77 | }) 78 | 79 | it('Should be able to delete picked value', function () { 80 | var obj = { 81 | some: { 82 | other: 'value', 83 | foo: 'bar' 84 | } 85 | } 86 | 87 | var val = Dot.pick('some.foo', obj, true) 88 | 89 | val.should.eql('bar') 90 | obj.should.eql({ 91 | some: { 92 | other: 'value' 93 | } 94 | }) 95 | }) 96 | 97 | it('Should be able to delete picked array value', function () { 98 | var obj = { 99 | some: { 100 | other: 'value', 101 | arrayItems: ['foo', 'bar', 'baz'] 102 | } 103 | } 104 | 105 | var val = Dot.pick('some.arrayItems[1]', obj, true) 106 | 107 | val.should.eql('bar') 108 | obj.should.eql({ 109 | some: { 110 | other: 'value', 111 | arrayItems: ['foo', , 'baz'] /* eslint-disable-line no-sparse-arrays */ 112 | } 113 | }) 114 | }) 115 | 116 | it('Should be able to delete picked array value and reindex', function () { 117 | var obj = { 118 | some: { 119 | other: 'value', 120 | arrayItems: ['foo', 'bar', 'baz'] 121 | } 122 | } 123 | 124 | var val = Dot.pick('some.arrayItems[1]', obj, true, true) 125 | 126 | val.should.eql('bar') 127 | obj.should.eql({ 128 | some: { 129 | other: 'value', 130 | arrayItems: ['foo', 'baz'] 131 | } 132 | }) 133 | }) 134 | }) 135 | -------------------------------------------------------------------------------- /test/remove.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var Dot = require('../index') 5 | 6 | describe('Remove/del:', function () { 7 | var obj 8 | var expected 9 | 10 | beforeEach(function () { 11 | obj = { 12 | id: 'my-id', 13 | nes: { 14 | ted: { 15 | gone: 'value', 16 | still: 'there' 17 | } 18 | }, 19 | ehrm: 123 20 | } 21 | 22 | expected = { 23 | id: 'my-id', 24 | nes: { 25 | ted: { 26 | still: 'there' 27 | } 28 | } 29 | } 30 | }) 31 | 32 | it('Should be able to remove() properties', function () { 33 | Dot.remove('ehrm', obj).should.equal(123) 34 | Dot.remove('nes.ted.gone', obj).should.equal('value') 35 | obj.should.eql(expected) 36 | }) 37 | 38 | it('Should be able to use del() alias', function () { 39 | Dot.del('ehrm', obj).should.equal(123) 40 | Dot.del('nes.ted.gone', obj).should.equal('value') 41 | obj.should.eql(expected) 42 | }) 43 | 44 | it('Should be able to remove() array item and reindex array', function () { 45 | var obj = { 46 | some: { 47 | other: 'value', 48 | arrayItems: ['foo', 'bar', 'baz'] 49 | } 50 | } 51 | 52 | var val = Dot.remove('some.arrayItems[1]', obj, true, true) 53 | 54 | val.should.eql('bar') 55 | obj.should.eql({ 56 | some: { 57 | other: 'value', 58 | arrayItems: ['foo', 'baz'] 59 | } 60 | }) 61 | }) 62 | 63 | it('Should be handle being told to reindex an object by ignoring reindex rule', function () { 64 | var obj = { 65 | some: { 66 | other: 'value', 67 | arrayItems: ['foo', 'bar', 'baz'] 68 | } 69 | } 70 | 71 | var val = Dot.remove('some.other', obj, true, true) 72 | 73 | val.should.eql('value') 74 | obj.should.eql({ 75 | some: { 76 | arrayItems: ['foo', 'bar', 'baz'] 77 | } 78 | }) 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /test/str.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var Dot = require('../index') 5 | 6 | describe('str:', function () { 7 | it('can set root property', function () { 8 | Dot.str('b', 2, { 9 | a: 1 10 | }).should.deepEqual({ 11 | a: 1, 12 | b: 2 13 | }) 14 | }) 15 | 16 | it('can set nested property', function () { 17 | Dot.str('b.a', 2, { 18 | a: 1 19 | }).should.deepEqual({ 20 | a: 1, 21 | b: { 22 | a: 2 23 | } 24 | }) 25 | }) 26 | 27 | it('can set nested with array notation', function () { 28 | var obj = { 29 | a: 1 30 | } 31 | Dot.str('object.fields[0].subfield', 'value', obj) 32 | Dot.str('object.fields[1].subfield', 'value1', obj) 33 | 34 | obj.should.deepEqual({ 35 | a: 1, 36 | object: { 37 | fields: [ 38 | { 39 | subfield: 'value' 40 | }, 41 | { 42 | subfield: 'value1' 43 | } 44 | ] 45 | } 46 | }) 47 | }) 48 | 49 | it('can set root level property regardless whether override is set', function () { 50 | Dot.str('a', 'b', { 51 | a: 1 52 | }).should.deepEqual({ 53 | a: 'b' 54 | }) 55 | }) 56 | 57 | it('cannot set __proto__ property', function () { 58 | (() => Dot.str('__proto__.toString', 'hi', {})).should.throw( 59 | /Refusing to update/ 60 | ); 61 | ({}.toString().should.deepEqual('[object Object]')) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /test/test_data.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var Dot = require('../index') 5 | 6 | var testData = require('./data') 7 | 8 | function singleTest (dot, input, expected) { 9 | dot.object(input) 10 | JSON.stringify(input).should.eql(JSON.stringify(expected)) 11 | } 12 | 13 | describe('Test Data:', function () { 14 | var dot = new Dot() 15 | function testIt (test) { 16 | it(test.name, function () { 17 | if (test.options) { 18 | Object.keys(test.options).forEach(function (name) { 19 | dot[name] = test.options[name] 20 | }) 21 | } 22 | 23 | if (Array.isArray(test.input)) { 24 | if ( 25 | !Array.isArray(test.expected) || 26 | test.input.length !== test.expected.length 27 | ) { 28 | throw Error('Input and Expected tests length must be the same') 29 | } 30 | test.expected.forEach((expected, i) => { 31 | singleTest(dot, test.input[i], expected) 32 | }) 33 | } else { 34 | singleTest(dot, test.input, test.expected) 35 | } 36 | }) 37 | } 38 | 39 | // note with object() it is possible to cleanup, with del it is not. 40 | 41 | testData.forEach(testIt) 42 | }) 43 | -------------------------------------------------------------------------------- /test/test_transforms.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var Dot = require('../index') 5 | 6 | var testData = [ 7 | require('./transforms/twitter'), 8 | require('./transforms/contact') 9 | ] 10 | 11 | describe('Test Transforms:', function () { 12 | var dot = new Dot() 13 | function testIt (test) { 14 | it(test.name, function () { 15 | if (test.options) { 16 | Object.keys(test.options).forEach(function (name) { 17 | dot[name] = test.options[name] 18 | }) 19 | } 20 | var tgt1 = {} 21 | var tgt2 = dot.transform(test.transform, test.input, tgt1) 22 | JSON.stringify(tgt1).should.eql(JSON.stringify(test.expected)) 23 | JSON.stringify(tgt2).should.eql(JSON.stringify(test.expected)) 24 | }) 25 | } 26 | 27 | testData.forEach(testIt) 28 | }) 29 | -------------------------------------------------------------------------------- /test/transfer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var Dot = require('../index') 5 | 6 | describe('Transfer:', function () { 7 | it('Should be able to transfer properties', function () { 8 | var src = { 9 | name: 'John', 10 | stuff: { 11 | phone: { 12 | brand: 'iphone', 13 | version: 6 14 | } 15 | } 16 | } 17 | 18 | var tgt = { 19 | name: 'Brandon' 20 | } 21 | 22 | var srcExpected = { name: 'John', stuff: {} } 23 | 24 | var tgtExpected = { 25 | name: 'Brandon', 26 | wanna: { 27 | haves: { 28 | phone: { 29 | brand: 'iphone', 30 | version: 6 31 | } 32 | } 33 | } 34 | } 35 | 36 | Dot.transfer('stuff.phone', 'wanna.haves.phone', src, tgt) 37 | 38 | src.should.eql(srcExpected) 39 | tgt.should.eql(tgtExpected) 40 | }) 41 | 42 | it('Should process modifiers', function () { 43 | var up = function (val) { 44 | val.brand = val.brand.toUpperCase() 45 | return val 46 | } 47 | 48 | var src = { 49 | name: 'John', 50 | stuff: { 51 | phone: { 52 | brand: 'iphone', 53 | version: 6 54 | } 55 | } 56 | } 57 | 58 | var tgt = { 59 | name: 'Brandon' 60 | } 61 | 62 | var srcExpected = { name: 'John', stuff: {} } 63 | 64 | var tgtExpected = { 65 | name: 'Brandon', 66 | wanna: { 67 | haves: { 68 | phone: { 69 | brand: 'IPHONE', 70 | version: 6 71 | } 72 | } 73 | } 74 | } 75 | 76 | Dot.transfer('stuff.phone', 'wanna.haves.phone', src, tgt, up) 77 | 78 | src.should.eql(srcExpected) 79 | tgt.should.eql(tgtExpected) 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /test/transforms/contact.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Contact Transform", 3 | "input": { 4 | "id": 1, 5 | "contact": { 6 | "firstName": "John", 7 | "lastName": "Doe", 8 | "email": "example@test.com" 9 | } 10 | }, 11 | "transform": { 12 | "id": "nr", 13 | "contact.firstName": "name.first", 14 | "contact.lastName": "name.last", 15 | "contact.email": "email" 16 | }, 17 | "expected": { 18 | "nr": 1, 19 | "name": { 20 | "first": "John", 21 | "last": "Doe" 22 | }, 23 | "email": "example@test.com" 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /test/transforms/twitter.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Twitter Transform", 3 | "input": { 4 | "text": "RT @PostGradProblem: In preparation for the NFL lockout, I will be spending twice as much time analyzing my fantasy baseball team during ...", 5 | "truncated": true, 6 | "in_reply_to_user_id": null, 7 | "in_reply_to_status_id": null, 8 | "favorited": false, 9 | "source": "Twitter for iPhone", 10 | "in_reply_to_screen_name": null, 11 | "in_reply_to_status_id_str": null, 12 | "id_str": "54691802283900928", 13 | "entities": { 14 | "user_mentions": [ 15 | { 16 | "indices": [ 17 | 3, 18 | 19 19 | ], 20 | "screen_name": "PostGradProblem", 21 | "id_str": "271572434", 22 | "name": "PostGradProblems", 23 | "id": 271572434 24 | } 25 | ], 26 | "urls": [ 27 | 28 | ], 29 | "hashtags": [ 30 | 31 | ] 32 | }, 33 | "contributors": null, 34 | "retweeted": false, 35 | "in_reply_to_user_id_str": null, 36 | "place": null, 37 | "retweet_count": 4, 38 | "created_at": "Sun Apr 03 23:48:36 +0000 2011", 39 | "retweeted_status": { 40 | "text": "In preparation for the NFL lockout, I will be spending twice as much time analyzing my fantasy baseball team during company time. #PGP", 41 | "truncated": false, 42 | "in_reply_to_user_id": null, 43 | "in_reply_to_status_id": null, 44 | "favorited": false, 45 | "source": "HootSuite", 46 | "in_reply_to_screen_name": null, 47 | "in_reply_to_status_id_str": null, 48 | "id_str": "54640519019642881", 49 | "entities": { 50 | "user_mentions": [ 51 | 52 | ], 53 | "urls": [ 54 | 55 | ], 56 | "hashtags": [ 57 | { 58 | "text": "PGP", 59 | "indices": [ 60 | 130, 61 | 134 62 | ] 63 | } 64 | ] 65 | }, 66 | "contributors": null, 67 | "retweeted": false, 68 | "in_reply_to_user_id_str": null, 69 | "place": null, 70 | "retweet_count": 4, 71 | "created_at": "Sun Apr 03 20:24:49 +0000 2011", 72 | "user": { 73 | "notifications": null, 74 | "profile_use_background_image": true, 75 | "statuses_count": 31, 76 | "profile_background_color": "C0DEED", 77 | "followers_count": 3066, 78 | "profile_image_url": "http://a2.twimg.com/profile_images/1285770264/PGP_normal.jpg", 79 | "listed_count": 6, 80 | "profile_background_image_url": "http://a3.twimg.com/a/1301071706/images/themes/theme1/bg.png", 81 | "description": "", 82 | "screen_name": "PostGradProblem", 83 | "default_profile": true, 84 | "verified": false, 85 | "time_zone": null, 86 | "profile_text_color": "333333", 87 | "is_translator": false, 88 | "profile_sidebar_fill_color": "DDEEF6", 89 | "location": "", 90 | "id_str": "271572434", 91 | "default_profile_image": false, 92 | "profile_background_tile": false, 93 | "lang": "en", 94 | "friends_count": 21, 95 | "protected": false, 96 | "favourites_count": 0, 97 | "created_at": "Thu Mar 24 19:45:44 +0000 2011", 98 | "profile_link_color": "0084B4", 99 | "name": "PostGradProblems", 100 | "show_all_inline_media": false, 101 | "follow_request_sent": null, 102 | "geo_enabled": false, 103 | "profile_sidebar_border_color": "C0DEED", 104 | "url": null, 105 | "id": 271572434, 106 | "contributors_enabled": false, 107 | "following": null, 108 | "utc_offset": null 109 | }, 110 | "id": 54640519019642880, 111 | "coordinates": null, 112 | "geo": null 113 | }, 114 | "user": { 115 | "notifications": null, 116 | "profile_use_background_image": true, 117 | "statuses_count": 351, 118 | "profile_background_color": "C0DEED", 119 | "followers_count": 48, 120 | "profile_image_url": "http://a1.twimg.com/profile_images/455128973/gCsVUnofNqqyd6tdOGevROvko1_500_normal.jpg", 121 | "listed_count": 0, 122 | "profile_background_image_url": "http://a3.twimg.com/a/1300479984/images/themes/theme1/bg.png", 123 | "description": "watcha doin in my waters?", 124 | "screen_name": "OldGREG85", 125 | "default_profile": true, 126 | "verified": false, 127 | "time_zone": "Hawaii", 128 | "profile_text_color": "333333", 129 | "is_translator": false, 130 | "profile_sidebar_fill_color": "DDEEF6", 131 | "location": "Texas", 132 | "id_str": "80177619", 133 | "default_profile_image": false, 134 | "profile_background_tile": false, 135 | "lang": "en", 136 | "friends_count": 81, 137 | "protected": false, 138 | "favourites_count": 0, 139 | "created_at": "Tue Oct 06 01:13:17 +0000 2009", 140 | "profile_link_color": "0084B4", 141 | "name": "GG", 142 | "show_all_inline_media": false, 143 | "follow_request_sent": null, 144 | "geo_enabled": false, 145 | "profile_sidebar_border_color": "C0DEED", 146 | "url": null, 147 | "id": 80177619, 148 | "contributors_enabled": false, 149 | "following": null, 150 | "utc_offset": -36000 151 | }, 152 | "id": 54691802283900930, 153 | "coordinates": null, 154 | "geo": null 155 | }, 156 | "transform": { 157 | "text": "content", 158 | "created_at": "time", 159 | "user.location": "location", 160 | "user.favourites_count": "stats.favs", 161 | "user.followers_count": "stats.followers", 162 | "user.statuses_count": "stats.statuses" 163 | }, 164 | "expected": { 165 | "content": "RT @PostGradProblem: In preparation for the NFL lockout, I will be spending twice as much time analyzing my fantasy baseball team during ...", 166 | "time": "Sun Apr 03 23:48:36 +0000 2011", 167 | "location": "Texas", 168 | "stats": { 169 | "favs": 0, 170 | "followers": 48, 171 | "statuses": 351 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /test/useArray.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('should') 4 | var Dot = require('../index') 5 | 6 | describe('useArray:', function () { 7 | var dotObject, arrayObject, object 8 | var arrayObjectExpected, objectExpected, dotObjectExpected 9 | 10 | beforeEach(function () { 11 | dotObject = { 12 | 'a.0': 'value' 13 | } 14 | dotObjectExpected = { 15 | 'a.0': 'value' 16 | } 17 | arrayObject = { 18 | a: ['value'] 19 | } 20 | arrayObjectExpected = { 21 | a: ['value'] 22 | } 23 | object = { 24 | a: { 25 | 0: 'value' 26 | } 27 | } 28 | objectExpected = { 29 | a: { 30 | 0: 'value' 31 | } 32 | } 33 | }) 34 | 35 | it('default is using array ', function () { 36 | var dotLocal = require('../index') 37 | arrayObjectExpected.should.eql(dotLocal.object(dotObject)) 38 | }) 39 | it('Should convert dot using arrays ', function () { 40 | Dot.useArray = true 41 | arrayObjectExpected.should.eql(Dot.object(dotObject)) 42 | }) 43 | it('Should convert dot not using arrays ', function () { 44 | Dot.useArray = false 45 | objectExpected.should.eql(Dot.object(dotObject)) 46 | }) 47 | it('Should convert object using arrays ', function () { 48 | Dot.useArray = true 49 | arrayObjectExpected.should.eql(Dot.object(dotObject)) 50 | }) 51 | it('Should convert dot using arrays and convert back equals source', function () { 52 | Dot.useArray = true 53 | Dot.useBrackets = false 54 | dotObjectExpected.should.eql(Dot.dot(Dot.object(dotObject))) 55 | }) 56 | it('Should convert object using arrays and convert back equals source', function () { 57 | Dot.useArray = true 58 | arrayObjectExpected.should.eql(Dot.object(Dot.dot(object))) 59 | }) 60 | it('Should convert dot not using arrays and convert back equals source', function () { 61 | Dot.useArray = false 62 | dotObjectExpected.should.eql(Dot.dot(Dot.object(dotObject))) 63 | }) 64 | it('Should convert object not using arrays and convert back equals source', function () { 65 | Dot.useArray = false 66 | objectExpected.should.eql(Dot.object(Dot.dot(arrayObject))) 67 | }) 68 | }) 69 | --------------------------------------------------------------------------------