├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── index.d.ts ├── bench ├── package.json └── index.js ├── .editorconfig ├── src └── index.js ├── package.json ├── license ├── readme.md └── test └── index.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: lukeed 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | *-lock.* 4 | *.lock 5 | *.log 6 | 7 | /dist 8 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export function nestie, Y=unknown>(input: X, delimiter?: string): Y; 2 | -------------------------------------------------------------------------------- /bench/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "benchmark": "2.1.4", 4 | "dset": "2.0.1", 5 | "flat": "5.0.2", 6 | "klona": "2.0.3", 7 | "lodash": "4.17.20" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 2 10 | 11 | [*.{json,yml,md}] 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | function empty(key) { 2 | return key === key ? [] : {}; 3 | } 4 | 5 | export function nestie(input, glue) { 6 | glue = glue || '.'; 7 | var arr, tmp, output; 8 | var i=0, k, key; 9 | 10 | for (k in input) { 11 | tmp = output; // reset 12 | arr = k.split(glue); 13 | 14 | for (i=0; i < arr.length;) { 15 | key = arr[i++]; 16 | 17 | if (tmp == null) { 18 | tmp = empty(+key); 19 | output = output || tmp; 20 | } 21 | 22 | if (key == '__proto__' || key == 'constructor' || key == 'prototype') break; 23 | 24 | if (i < arr.length) { 25 | if (key in tmp) { 26 | tmp = tmp[key]; 27 | } else { 28 | tmp = tmp[key] = empty(+arr[i]); 29 | } 30 | } else { 31 | tmp[key] = input[k]; 32 | } 33 | } 34 | } 35 | 36 | return output; 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestie", 3 | "version": "1.0.3", 4 | "repository": "lukeed/nestie", 5 | "description": "A tiny (215B) and fast utility to expand a flattened object", 6 | "unpkg": "dist/index.min.js", 7 | "module": "dist/index.mjs", 8 | "main": "dist/index.js", 9 | "types": "index.d.ts", 10 | "license": "MIT", 11 | "author": { 12 | "name": "Luke Edwards", 13 | "email": "luke.edwards05@gmail.com", 14 | "url": "https://lukeed.com" 15 | }, 16 | "exports": { 17 | ".": { 18 | "types": "./index.d.ts", 19 | "import": "./dist/index.mjs", 20 | "require": "./dist/index.js" 21 | }, 22 | "./package.json": "./package.json" 23 | }, 24 | "files": [ 25 | "*.d.ts", 26 | "dist" 27 | ], 28 | "engines": { 29 | "node": ">=8" 30 | }, 31 | "scripts": { 32 | "build": "bundt", 33 | "bench": "node bench", 34 | "test": "uvu -r esm test" 35 | }, 36 | "keywords": [ 37 | "keys", 38 | "expand", 39 | "unflatten", 40 | "object", 41 | "nested" 42 | ], 43 | "devDependencies": { 44 | "bundt": "1.1.3", 45 | "esm": "3.2.25", 46 | "uvu": "0.5.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Luke Edwards (lukeed.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | tags-ignore: 8 | - '**' 9 | paths-ignore: 10 | - 'bench/**' 11 | - '*.md' 12 | 13 | pull_request: 14 | branches: 15 | - master 16 | paths-ignore: 17 | - 'bench/**' 18 | - '*.md' 19 | 20 | jobs: 21 | test: 22 | name: Node.js v${{ matrix.nodejs }} 23 | runs-on: ubuntu-latest 24 | strategy: 25 | matrix: 26 | nodejs: [8, 10, 12, 14, 16, 18] 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: actions/setup-node@v1 30 | with: 31 | node-version: ${{ matrix.nodejs }} 32 | 33 | - name: (cache) restore 34 | uses: actions/cache@master 35 | with: 36 | path: node_modules 37 | key: ${{ runner.os }}-${{ hashFiles('**/package.json') }} 38 | 39 | - name: Install 40 | run: npm install 41 | 42 | - name: (coverage) Install 43 | if: matrix.nodejs >= 14 44 | run: npm install -g c8 45 | 46 | - name: Test 47 | run: npm test 48 | if: matrix.nodejs < 14 49 | 50 | - name: (coverage) Test 51 | run: c8 --include=src npm test 52 | if: matrix.nodejs >= 14 53 | 54 | - name: (coverage) Report 55 | if: matrix.nodejs >= 14 56 | run: | 57 | c8 report --reporter=text-lcov > coverage.lcov 58 | bash <(curl -s https://codecov.io/bash) 59 | env: 60 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 61 | -------------------------------------------------------------------------------- /bench/index.js: -------------------------------------------------------------------------------- 1 | const { klona } = require('klona'); 2 | const assert = require('uvu/assert'); 3 | const { Suite } = require('benchmark'); 4 | 5 | const FIXTURE = { 6 | 'a': 'hi', 7 | 'b.b.0': 'foo', 8 | 'b.b.1': '', 9 | 'b.b.3': 'bar', 10 | 'b.d': 'hello', 11 | 'b.e.a': 'yo', 12 | 'b.e.c': 'sup', 13 | 'b.e.d': 0, 14 | 'b.e.f.0.foo': 123, 15 | 'b.e.f.0.bar': 123, 16 | 'b.e.f.1.foo': 465, 17 | 'b.e.f.1.bar': 456, 18 | 'c': 'world' 19 | }; 20 | 21 | console.log('\nLoad Time: '); 22 | 23 | console.time('dset'); 24 | const dset = require('dset'); 25 | console.timeEnd('dset'); 26 | 27 | console.time('lodash/set'); 28 | const lodash = require('lodash/set'); 29 | console.timeEnd('lodash/set'); 30 | 31 | console.time('flat'); 32 | const { unflatten } = require('flat'); 33 | console.timeEnd('flat'); 34 | 35 | console.time('nestie'); 36 | const { nestie } = require('../dist'); 37 | console.timeEnd('nestie'); 38 | 39 | // Not 1:1 (nestie does more) 40 | function mock(lib, value) { 41 | var k, output = {}; 42 | for (k in value) lib(output, k); 43 | return output; 44 | } 45 | 46 | const contenders = { 47 | 'lodash/set ≠': mock.bind(0, lodash), 48 | 'dset ≠': mock.bind(0, dset), 49 | 'flat.unflatten': unflatten, 50 | 'nestie': nestie, 51 | }; 52 | 53 | console.log('\nValidation: '); 54 | Object.keys(contenders).forEach(name => { 55 | try { 56 | const input = klona(FIXTURE); 57 | const output = contenders[name](input); 58 | 59 | assert.is(input['b.b.0'], 'foo', 'no mutate'); 60 | assert.is(output['b.b.0'], undefined, 'no direct'); 61 | 62 | assert.type(output['b'], 'object', 'created "b" object'); 63 | assert.instance(output['b']['b'], Array, 'created "b.b" array'); 64 | assert.equal(output['b']['b'], ['foo', '', , 'bar'], 'array w/ holes'); 65 | 66 | console.log(' ✔', name); 67 | } catch (err) { 68 | console.log(' ✘', name, `(FAILED) @ "${err.message}"`); 69 | // if (err.details) console.log(err.details); 70 | } 71 | }); 72 | 73 | 74 | console.log('\nBenchmark:'); 75 | const bench = new Suite().on('cycle', e => { 76 | console.log(' ' + e.target); 77 | }); 78 | 79 | Object.keys(contenders).forEach(name => { 80 | bench.add(name + ' '.repeat(16 - name.length), () => { 81 | contenders[name](FIXTURE); 82 | }); 83 | }); 84 | 85 | bench.run(); 86 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # nestie [![CI](https://github.com/lukeed/nestie/workflows/CI/badge.svg)](https://github.com/lukeed/nestie/actions) [![codecov](https://badgen.now.sh/codecov/c/github/lukeed/nestie)](https://codecov.io/gh/lukeed/nestie) 2 | 3 | > A tiny (215B) and [fast](#benchmarks) utility to expand a flattened object 4 | 5 | This module expands an Object who's keys are delimited/condensed representatives of multiple levels. 6 | 7 | By default, the `.` character is used as a delimiter. This is customizable.
Keys are split using the delimiter, each signifying a new level/depth. 8 | 9 | 10 | ## Install 11 | 12 | ``` 13 | $ npm install --save nestie 14 | ``` 15 | 16 | 17 | ## Usage 18 | 19 | > Please see [Keys](#keys) for pathing options 20 | 21 | ```js 22 | import { nestie } from 'nestie'; 23 | 24 | nestie({ 25 | 'a': 'hi', 26 | 'b.b.0': 'foo', 27 | 'b.b.1': '', 28 | 'b.b.3': 'bar', 29 | 'b.d': 'hello', 30 | 'b.e.a': 'yo', 31 | 'b.e.b': null, 32 | 'b.e.c': 'sup', 33 | 'b.e.d': 0, 34 | 'b.e.f.0.foo': 123, 35 | 'b.e.f.0.bar': 123, 36 | 'b.e.f.1.foo': 465, 37 | 'b.e.f.1.bar': 456, 38 | 'c': 'world' 39 | }); 40 | //=> { 41 | //=> a: 'hi', 42 | //=> b: { 43 | //=> b: ['foo', '', , 'bar'], 44 | //=> d: 'hello', 45 | //=> e: { 46 | //=> a: 'yo', 47 | //=> b: null, 48 | //=> c: 'sup', 49 | //=> d: 0, 50 | //=> f: [ 51 | //=> { foo: 123, bar: 123 }, 52 | //=> { foo: 465, bar: 456 }, 53 | //=> ] 54 | //=> } 55 | //=> }, 56 | //=> c: 'world' 57 | //=> } 58 | ``` 59 | 60 | ## Keys 61 | 62 | Here are additional examples using different key-notation combinations in order represent different Array/Object structures. 63 | 64 | ```js 65 | nestie({ 66 | 'hello.there': 123, 67 | 'hello.world': 456, 68 | }); 69 | //=> { 70 | //=> hello: { 71 | //=> there: 123, 72 | //=> world: 456 73 | //=> } 74 | //=> } 75 | 76 | nestie({ 77 | 'foo.0.bar': 1, 78 | 'foo.1': 'hello', 79 | 'foo.2.bar': 3, 80 | }); 81 | //=> { 82 | //=> foo: [ 83 | //=> { bar: 1 }, 84 | //=> 'hello', 85 | //=> { bar: 3 } 86 | //=> ] 87 | //=> } 88 | 89 | nestie({ 90 | '0.0': 'foo', 91 | '0.1': 'bar', 92 | '1.foo.bar': 123, 93 | '1.foo.baz.0': 4, 94 | '1.foo.baz.1': 5, 95 | '1.foo.baz.2': 6, 96 | '1.hello': 'world', 97 | '2': 'howdy' 98 | }); 99 | //=> [ 100 | //=> ['foo', 'bar'], 101 | //=> { 102 | //=> foo: { 103 | //=> bar: 123, 104 | //=> baz: [4, 5, 6] 105 | //=> }, 106 | //=> hello: 'world' 107 | //=> }, 108 | //=> 'howdy' 109 | //=> ] 110 | ``` 111 | 112 | ## API 113 | 114 | ### nestie(input, delimiter?) 115 | Returns: `Object` or `Array` 116 | 117 | Returns a new Object or Array, depending on the keys. 118 | 119 | > **Note:** A `null` or `undefined` input will return `undefined`~! 120 | 121 | #### input 122 | Type: `Object` 123 | 124 | The object to expand. 125 | 126 | #### delimiter 127 | Type: `String`
128 | Default: `.` 129 | 130 | The "glue" used to join multi-level keys together.
131 | Keys will be split using this `delimiter` string, signifying a new level/depth. 132 | 133 | ```js 134 | const input = { 135 | 'foo.bar': 123, 136 | 'hello_world': 456, 137 | }; 138 | 139 | nestie(input); 140 | //=> { 141 | //=> foo: { bar: 123 }, 142 | //=> hello_world: 456, 143 | //=> } 144 | 145 | nestie(input, '_'); 146 | //=> { 147 | //=> 'foo.bar': 123, 148 | //=> hello: { world: 456 }, 149 | //=> } 150 | ``` 151 | 152 | 153 | ## Benchmarks 154 | 155 | > Running on Node.js v18.12.1 156 | 157 | > **Note:** The `≠` denotes that the candidate has a different API and is not a direct comparison. 158 | 159 | ``` 160 | Load Time: 161 | dset 0.421ms 162 | lodash/set 5.472ms 163 | flat 0.926ms 164 | nestie 0.131ms 165 | 166 | Validation: 167 | ✘ lodash/set ≠ (FAILED) @ "array w/ holes" 168 | ✘ dset ≠ (FAILED) @ "array w/ holes" 169 | ✔ flat.unflatten 170 | ✔ nestie 171 | 172 | Benchmark: 173 | lodash/set ≠ x 365,431 ops/sec ±0.46% (96 runs sampled) 174 | dset ≠ x 528,696 ops/sec ±0.12% (99 runs sampled) 175 | flat.unflatten x 235,161 ops/sec ±0.16% (98 runs sampled) 176 | nestie x 565,665 ops/sec ±0.13% (99 runs sampled) 177 | ``` 178 | 179 | 180 | ## Related 181 | 182 | * [flattie](https://github.com/lukeed/flattie) – flatten an object using customizable glue in 187 bytes
_This is `nestie`'s reverse / counterpart._ 183 | * [dset](https://github.com/lukeed/dset) – safely write deep Object values in 160 bytes 184 | * [dlv](https://github.com/developit/dlv) – safely read from deep properties in 120 bytes 185 | 186 | 187 | ## License 188 | 189 | MIT © [Luke Edwards](https://lukeed.com) 190 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import { test } from 'uvu'; 2 | import * as assert from 'uvu/assert'; 3 | import { nestie } from '../src'; 4 | 5 | function run(input, output, msg) { 6 | const value = JSON.stringify(input); 7 | assert.equal(nestie(input), output, msg); 8 | assert.is(JSON.stringify(input), value, 'does not mutate'); 9 | } 10 | 11 | test('exports', () => { 12 | assert.type(nestie, 'function'); 13 | }); 14 | 15 | test('wrong inputs', () => { 16 | run(1, undefined); 17 | run('', undefined); 18 | run(0, undefined); 19 | 20 | run(null, undefined); 21 | run(undefined, undefined); 22 | run(NaN, undefined); 23 | }); 24 | 25 | test('custom glue', () => { 26 | const input = { 27 | 'foo.bar': 123, 28 | 'bar.baz': 456, 29 | 'baz_bat': 789, 30 | }; 31 | 32 | const input_string = JSON.stringify(input); 33 | 34 | assert.equal( 35 | nestie(input, '_'), 36 | { 37 | 'foo.bar': 123, 38 | 'bar.baz': 456, 39 | 'baz': { bat: 789 }, 40 | } 41 | ); 42 | 43 | assert.is( 44 | input_string, 45 | JSON.stringify(input), 46 | 'does not mutate original' 47 | ); 48 | 49 | assert.equal( 50 | nestie(input, '~'), 51 | { 52 | 'foo.bar': 123, 53 | 'bar.baz': 456, 54 | 'baz_bat': 789, 55 | } 56 | ); 57 | 58 | assert.is( 59 | input_string, 60 | JSON.stringify(input), 61 | 'does not mutate original' 62 | ); 63 | }); 64 | 65 | test('keep nullish', () => { 66 | run({ 67 | 'foo.bar': null, 68 | 'bar.baz': undefined, 69 | 'baz.bat': NaN, 70 | 'foo.baz': 0, 71 | }, { 72 | foo: { bar: null, baz: 0 }, 73 | bar: { baz: undefined }, 74 | baz: { bat: NaN }, 75 | }); 76 | 77 | run({ 78 | 'a.0.x': null, 79 | 'a.1.x': undefined, 80 | 'a.3.x': false, 81 | 'a.1.bat': NaN, 82 | 'a.1.baz': 0, 83 | }, { 84 | a: [ 85 | { x: null }, 86 | { x: undefined, bat: NaN, baz: 0 }, 87 | , 88 | { x: false }, 89 | ] 90 | }); 91 | }); 92 | 93 | test('readme/demo', () => { 94 | run({ 95 | 'a': 'hi', 96 | 'b.b.0': 'foo', 97 | 'b.b.1': '', 98 | 'b.b.3': 'bar', 99 | 'b.d': 'hello', 100 | 'b.e.a': 'yo', 101 | 'b.e.b': null, 102 | 'b.e.c': 'sup', 103 | 'b.e.d': 0, 104 | 'b.e.f.0.foo': 123, 105 | 'b.e.f.0.bar': 123, 106 | 'b.e.f.1.foo': 465, 107 | 'b.e.f.1.bar': 456, 108 | 'c': 'world' 109 | }, { 110 | a: 'hi', 111 | b: { 112 | b: ['foo', '', , 'bar'], 113 | d: 'hello', 114 | e: { 115 | a: 'yo', 116 | b: null, 117 | c: 'sup', 118 | d: 0, 119 | f: [ 120 | { foo: 123, bar: 123 }, 121 | { foo: 465, bar: 456 }, 122 | ] 123 | } 124 | }, 125 | c: 'world' 126 | }); 127 | }); 128 | 129 | test('object :: simple', () => { 130 | run({ 131 | 'aaa': 1, 132 | 'bbb': 2, 133 | 'ccc.foo': 'bar', 134 | 'ccc.baz': 'bat', 135 | 'ddd': 4, 136 | }, { 137 | aaa: 1, 138 | bbb: 2, 139 | ccc: { 140 | foo: 'bar', 141 | baz: 'bat' 142 | }, 143 | ddd: 4 144 | }); 145 | }); 146 | 147 | test('object :: nested', () => { 148 | run({ 149 | 'aaa': 1, 150 | 'bbb.aaa': 2, 151 | 'bbb.bbb': 3, 152 | 'bbb.ccc.foo': 'bar', 153 | 'bbb.ccc.baz': 'bat', 154 | 'bbb.ddd': 4, 155 | 'ccc': 3 156 | }, { 157 | aaa: 1, 158 | bbb: { 159 | aaa: 2, 160 | bbb: 3, 161 | ccc: { 162 | foo: 'bar', 163 | baz: 'bat' 164 | }, 165 | ddd: 4 166 | }, 167 | ccc: 3 168 | }); 169 | }); 170 | 171 | // https://github.com/lukeed/nestie/issues/3 172 | test('NaN keys', () => { 173 | run({ 174 | 'foo.7f4d': 12 175 | }, { 176 | 'foo': { 177 | '7f4d': 12 178 | } 179 | }); 180 | 181 | run( 182 | { '1.1a': 5 }, 183 | [ , { '1a': 5 }] 184 | ); 185 | }); 186 | 187 | test('object :: kitchen', () => { 188 | run({ 189 | 'a': 1, 190 | 191 | 'b.0.0.a': 1, 192 | 'b.0.0.b.0': 2, 193 | 'b.0.0.b.2': 9, 194 | 'b.0.0.c.a.0': 1, 195 | 'b.0.0.c.b.foo.0': 2, 196 | 'b.0.0.c.b.foo.1': 2, 197 | 'b.0.0.d': 4, 198 | 199 | 'b.1.0.a': 2, 200 | 'b.1.0.b.0': 4, 201 | 'b.1.0.b.1': null, 202 | 'b.1.0.b.2': 9, 203 | 'b.1.0.c.a.0': 2, 204 | 'b.1.0.c.b.foo.0': 4, 205 | 'b.1.0.c.b.foo.1': 4, 206 | 'b.1.0.d': 5, 207 | 208 | 'b.2.0.a': 3, 209 | 'b.2.0.b.0': 6, 210 | 'b.2.0.b.2': 9, 211 | 'b.2.0.c.a.0': 4, 212 | 'b.2.0.c.b.foo.0': 8, 213 | 'b.2.0.c.b.foo.1': 8, 214 | 'b.2.0.d': 6, 215 | 216 | 'c': 3, 217 | 218 | 'd.foo': undefined, 219 | 'd.bar.0.a': 1, 220 | 'd.bar.0.b': 2, 221 | 'd.bar.0.c.0.a': 1, 222 | 'd.bar.0.c.0.b.c': 3, 223 | 'd.bar.0.c.1.a': 2, 224 | 'd.bar.0.c.1.b.c': 4, 225 | 'd.bar.0.d': 4, 226 | 227 | 'd.baz.0.a': 2, 228 | 'd.baz.0.b': 3, 229 | 'd.baz.0.c.0.a': 2, 230 | 'd.baz.0.c.0.b.c': 4, 231 | 'd.baz.0.c.1.a': 3, 232 | 'd.baz.0.c.1.b.c': 5, 233 | 'd.baz.0.d': 5, 234 | }, { 235 | a: 1, 236 | b: [ 237 | [{ a:1, b:[2,,9], c:{ a:[1], b: { foo: [2, 2] } }, d:4 }], 238 | [{ a:2, b:[4,null,9], c:{ a:[2], b: { foo: [4, 4] } }, d:5 }], 239 | [{ a:3, b:[6,,9], c:{ a:[4], b: { foo: [8, 8] } }, d:6 }], 240 | ], 241 | c: 3, 242 | d: { 243 | foo: undefined, 244 | bar: [{ a:1, b:2, c:[{ a:1, b:{ c:3 } }, { a:2, b:{ c:4 } }], d:4 }], 245 | baz: [{ a:2, b:3, c:[{ a:2, b:{ c:4 } }, { a:3, b:{ c:5 } }], d:5 }], 246 | } 247 | }); 248 | }); 249 | 250 | test('array :: simple', () => { 251 | run({ 252 | '0': 0, 253 | '2': null, 254 | '3': undefined, 255 | '4': 1, 256 | '5': 2, 257 | '6': '', 258 | '7': 3 259 | }, [ 260 | 0, , null, undefined, 1, 2, '', 3 261 | ]); 262 | }); 263 | 264 | test('array :: nested', () => { 265 | run({ 266 | '0.0': 1, 267 | '0.1': 2, 268 | '0.2': null, 269 | '0.3': 3, 270 | '0.4': 4, 271 | '1.0': 'foo', 272 | '1.1': 'bar', 273 | '1.2.0': 'hello', 274 | '1.2.2': 'world', 275 | '1.3': 'baz', 276 | '2.0': 6, 277 | '2.1': 7, 278 | '2.2': 8, 279 | '2.3': undefined, 280 | '2.4': 9, 281 | }, [ 282 | [1, 2, null, 3, 4], 283 | ['foo', 'bar', ['hello', , 'world'], 'baz'], 284 | [6, 7, 8, undefined, 9] 285 | ]); 286 | }); 287 | 288 | test('array :: object', () => { 289 | let baz = ['hello', null, 'world']; 290 | let bbb = { foo: 123, bar: 456, baz }; 291 | 292 | run({ 293 | '0.aaa': 1, 294 | '0.bbb.foo': 123, 295 | '0.bbb.bar': 456, 296 | '0.bbb.baz.0': 'hello', 297 | '0.bbb.baz.1': null, 298 | '0.bbb.baz.2': 'world', 299 | '0.ccc.0': 4, 300 | '0.ccc.1': 5, 301 | 302 | '1.aaa': 2, 303 | '1.bbb.foo': 123, 304 | '1.bbb.bar': 456, 305 | '1.bbb.baz.0': 'hello', 306 | '1.bbb.baz.1': null, 307 | '1.bbb.baz.2': 'world', 308 | '1.ccc': [], 309 | 310 | '2.aaa': 3, 311 | '2.bbb.foo': 123, 312 | '2.bbb.bar': 456, 313 | '2.bbb.baz.0': 'hello', 314 | '2.bbb.baz.1': null, 315 | '2.bbb.baz.2': 'world', 316 | '2.ccc.0': 9999, 317 | }, [ 318 | { aaa: 1, bbb, ccc: [4, 5] }, 319 | { aaa: 2, bbb, ccc: [] }, 320 | { aaa: 3, bbb, ccc: [9999] }, 321 | ]); 322 | }); 323 | 324 | test('array :: kitchen', () => { 325 | run({ 326 | '0': 'hello', 327 | 328 | '1.a': 1, 329 | '1.b.0.0.a': 1, 330 | '1.b.0.0.b.0': 2, 331 | '1.b.0.0.b.1': null, 332 | '1.b.0.0.b.2': 9, 333 | '1.b.0.0.c.a.0': 1, 334 | '1.b.0.0.c.b.foo.0': 2, 335 | '1.b.0.0.c.b.foo.1': 2, 336 | '1.b.0.0.d': 4, 337 | '1.b.1.0.a': 2, 338 | '1.b.1.0.b.0': 4, 339 | '1.b.1.0.b.1': undefined, 340 | '1.b.1.0.b.2': 9, 341 | '1.b.1.0.c.a.0': 2, 342 | '1.b.1.0.c.b.foo.0': 4, 343 | '1.b.1.0.c.b.foo.1': 4, 344 | '1.b.1.0.d': 5, 345 | '1.b.2.0.a': 3, 346 | '1.b.2.0.b.0': 6, 347 | '1.b.2.0.b.1': null, 348 | '1.b.2.0.b.2': 9, 349 | '1.b.2.0.c.a.0': 4, 350 | '1.b.2.0.c.b.foo.0': 8, 351 | '1.b.2.0.c.b.foo.1': 8, 352 | '1.b.2.0.d': 6, 353 | '1.c': 3, 354 | '1.d.foo': undefined, 355 | '1.d.bar.0.a': 1, 356 | '1.d.bar.0.b': 2, 357 | '1.d.bar.0.c.0.a': 1, 358 | '1.d.bar.0.c.0.b.c': 3, 359 | '1.d.bar.0.c.1.a': 2, 360 | '1.d.bar.0.c.1.b.c': 4, 361 | '1.d.bar.0.d': 4, 362 | '1.d.baz.0.a': 2, 363 | '1.d.baz.0.b': 3, 364 | '1.d.baz.0.c.0.a': 2, 365 | '1.d.baz.0.c.0.b.c': 4, 366 | '1.d.baz.0.c.1.a': 3, 367 | '1.d.baz.0.c.1.b.c': 5, 368 | '1.d.baz.0.d': 5, 369 | 370 | '2': 'world', 371 | 372 | '3.a': 1, 373 | '3.b.0.0.a': 1, 374 | '3.b.0.0.b.0': 2, 375 | '3.b.0.0.b.1': null, 376 | '3.b.0.0.b.2': 9, 377 | '3.b.0.0.c.a.0': 1, 378 | '3.b.0.0.c.b.foo.0': 2, 379 | '3.b.0.0.c.b.foo.1': 2, 380 | '3.b.0.0.d': 4, 381 | '3.b.1.0.a': 2, 382 | '3.b.1.0.b.0': 4, 383 | '3.b.1.0.b.1': undefined, 384 | '3.b.1.0.b.2': 9, 385 | '3.b.1.0.c.a.0': 2, 386 | '3.b.1.0.c.b.foo.0': 4, 387 | '3.b.1.0.c.b.foo.1': 4, 388 | '3.b.1.0.d': 5, 389 | '3.b.2.0.a': 3, 390 | '3.b.2.0.b.0': 6, 391 | '3.b.2.0.b.1': null, 392 | '3.b.2.0.b.2': 9, 393 | '3.b.2.0.c.a.0': 4, 394 | '3.b.2.0.c.b.foo.0': 8, 395 | '3.b.2.0.c.b.foo.1': 8, 396 | '3.b.2.0.d': 6, 397 | '3.c': 3, 398 | '3.d.foo': undefined, 399 | '3.d.bar.0.a': 1, 400 | '3.d.bar.0.b': 2, 401 | '3.d.bar.0.c.0.a': 1, 402 | '3.d.bar.0.c.0.b.c': 3, 403 | '3.d.bar.0.c.1.a': 2, 404 | '3.d.bar.0.c.1.b.c': 4, 405 | '3.d.bar.0.d': 4, 406 | '3.d.baz.0.a': 2, 407 | '3.d.baz.0.b': 3, 408 | '3.d.baz.0.c.0.a': 2, 409 | '3.d.baz.0.c.0.b.c': 4, 410 | '3.d.baz.0.c.1.a': 3, 411 | '3.d.baz.0.c.1.b.c': 5, 412 | '3.d.baz.0.d': 5, 413 | }, [ 414 | 'hello', 415 | { 416 | a: 1, 417 | b: [ 418 | [{ a:1, b:[2,null,9], c:{ a:[1], b: { foo: [2, 2] } }, d:4 }], 419 | [{ a:2, b:[4,undefined,9], c:{ a:[2], b: { foo: [4, 4] } }, d:5 }], 420 | [{ a:3, b:[6,null,9], c:{ a:[4], b: { foo: [8, 8] } }, d:6 }], 421 | ], 422 | c: 3, 423 | d: { 424 | foo: undefined, 425 | bar: [{ a:1, b:2, c:[{ a:1, b:{ c:3 } }, { a:2, b:{ c:4 } }], d:4 }], 426 | baz: [{ a:2, b:3, c:[{ a:2, b:{ c:4 } }, { a:3, b:{ c:5 } }], d:5 }], 427 | } 428 | }, 429 | 'world', 430 | { 431 | a: 1, 432 | b: [ 433 | [{ a:1, b:[2,null,9], c:{ a:[1], b: { foo: [2, 2] } }, d:4 }], 434 | [{ a:2, b:[4,undefined,9], c:{ a:[2], b: { foo: [4, 4] } }, d:5 }], 435 | [{ a:3, b:[6,null,9], c:{ a:[4], b: { foo: [8, 8] } }, d:6 }], 436 | ], 437 | c: 3, 438 | d: { 439 | foo: undefined, 440 | bar: [{ a:1, b:2, c:[{ a:1, b:{ c:3 } }, { a:2, b:{ c:4 } }], d:4 }], 441 | baz: [{ a:2, b:3, c:[{ a:2, b:{ c:4 } }, { a:3, b:{ c:5 } }], d:5 }], 442 | } 443 | }, 444 | ]); 445 | }); 446 | 447 | test('proto pollution :: __proto__ :: toplevel', () => { 448 | let output = nestie({ 449 | '__proto__.foobar': 123 450 | }); 451 | 452 | let tmp = {}; 453 | assert.equal(output, {}); 454 | assert.is(tmp.foobar, undefined); 455 | }); 456 | 457 | test('proto pollution :: __proto__ :: midlevel', () => { 458 | let output = nestie({ 459 | 'aaa.__proto__.foobar': 123 460 | }); 461 | 462 | let tmp = {}; 463 | assert.equal(output, { aaa: {} }); 464 | assert.is(tmp.foobar, undefined); 465 | }); 466 | 467 | test('proto pollution :: __proto__ :: sibling', () => { 468 | let output = nestie({ 469 | 'aaa.bbb': 'abc', 470 | '__proto__.foobar': 123, 471 | 'aaa.xxx': 'xxx', 472 | 'foo.bar': 456, 473 | }); 474 | 475 | assert.equal(output, { 476 | aaa: { 477 | bbb: 'abc', 478 | xxx: 'xxx', 479 | }, 480 | foo: { 481 | bar: 456 482 | } 483 | }); 484 | 485 | let tmp = {}; 486 | assert.is(tmp.foobar, undefined); 487 | }); 488 | 489 | test('proto pollution :: prototype', () => { 490 | let output = nestie({ 491 | 'a.prototype.hello': 'world', 492 | }); 493 | 494 | assert.equal(output, { 495 | a: { 496 | // converted, then aborted 497 | } 498 | }); 499 | 500 | assert.is.not({}.hello, 'world'); 501 | assert.is({}.hello, undefined); 502 | }); 503 | 504 | test('proto pollution :: constructor :: direct', () => { 505 | function Custom() { 506 | // 507 | } 508 | 509 | let output = nestie({ 510 | 'a.constructor': Custom, 511 | 'foo.bar': 123, 512 | }); 513 | 514 | assert.equal(output, { 515 | a: { 516 | // stopped 517 | }, 518 | foo: { 519 | bar: 123, 520 | } 521 | }); 522 | 523 | // Check existing object 524 | assert.is.not(output.a.constructor, Custom); 525 | assert.not.instance(output.a, Custom); 526 | assert.instance(output.a.constructor, Object, '~> 123 -> {}'); 527 | assert.is(output.a.hasOwnProperty('constructor'), false); 528 | 529 | let tmp = {}; // Check new object 530 | assert.is.not(tmp.constructor, Custom); 531 | assert.not.instance(tmp, Custom); 532 | 533 | assert.instance(tmp.constructor, Object, '~> 123 -> {}'); 534 | assert.is(tmp.hasOwnProperty('constructor'), false); 535 | }); 536 | 537 | test('proto pollution :: constructor :: nested', () => { 538 | let output = nestie({ 539 | 'constructor.prototype.hello': 'world', 540 | 'foo': 123, 541 | }); 542 | 543 | assert.equal(output, { 544 | foo: 123 545 | }); 546 | 547 | assert.is(output.hasOwnProperty('constructor'), false); 548 | assert.is(output.hasOwnProperty('hello'), false); 549 | 550 | let tmp = {}; 551 | assert.is(tmp.hasOwnProperty('constructor'), false); 552 | assert.is(tmp.hasOwnProperty('hello'), false); 553 | }); 554 | 555 | test.run(); 556 | --------------------------------------------------------------------------------