├── .gitignore ├── .travis.yml ├── History.md ├── LICENSE ├── Readme.md ├── example.js ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .nyc_output 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | before_install: npm install npm -g 3 | node_js: 4 | - "4" 5 | - "6" 6 | - "0.12" 7 | - "0.11" 8 | - "0.10" 9 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2.1.1 / 2016-08-10 2 | ================== 3 | 4 | * Update dev dependencies 5 | * Test node 4 & 6 on travis. Drop iojs target. 6 | * Format with standard 7 | * Check code coverage with nyc 8 | * Update metadata 9 | 10 | 2.1.0 / 2015-02-10 11 | ================== 12 | 13 | * [feature] Custom key/value names. 14 | * [docs] Make example more by mutating mapped object. 15 | * [docs] Add History.md as Changelog 16 | * [docs] Recommend hughsk/flat under "See Also" in Readme. 17 | 18 | 2.0.0 / 2015-02-10 19 | ================== 20 | 21 | * [api] Breaking change: Add hasOwnProperty check. (thanks hemanth.hm) 22 | * [tests] Test prototype properties are not iterated. 23 | * [docs] Remove old name from test description. 24 | 25 | 1.0.0 / 2015-01-03 26 | ================== 27 | 28 | * Birth 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tim Oxley 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 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # split-object 2 | 3 | [![Build Status](https://travis-ci.org/timoxley/split-object.svg?branch=master)](https://travis-ci.org/timoxley/split-object) 4 | 5 | Minimal tool for working with Objects using built-in functional Array 6 | methods. 7 | 8 | `split-object` will split an Object into an Array of keys & values, 9 | allowing you to manipulate the Object using Array methods then join 10 | values back into an Object. 11 | 12 | In comparison to `for..in` or `Object.keys().forEach()` an Array of the 13 | form `[{key1: value1}, {key2: value2}]` can be a far more natural & 14 | convenient structure to work with. 15 | 16 | `split-object` has both a `split` and a `join` method, similar to how 17 | `String#split`/`Array#join` combine to convert back & forward between 18 | Strings & Arrays. 19 | 20 | [Post ES6](https://esdiscuss.org/topic/es6-iteration-over-object-values) we 21 | might see some better methods for iterating over Objects. 22 | 23 | ## Installation 24 | 25 | ``` 26 | npm install split-object 27 | ``` 28 | 29 | ## Usage 30 | 31 | ```js 32 | var salad = { 33 | apples: 1, 34 | bananas: 3, 35 | carrots: 2 36 | } 37 | 38 | // transform each key/value using Array.prototype.map 39 | var loudIngredients = split(salad).map(function(ingredient) { 40 | ingredient.key = ingredient.key.toUpperCase() 41 | ingredient.value *= 3 42 | return ingredient 43 | }) 44 | 45 | // loudIngredients: 46 | // [ 47 | // { key: 'APPLES', value: 3 }, 48 | // { key: 'BANANAS', value: 9 }, 49 | // { key: 'CARROTS', value: 6 } 50 | // ] 51 | 52 | var loudSalad = split.join(loudIngredients) 53 | 54 | // loudSalad: 55 | // { 56 | // APPLES: 3, 57 | // BANANAS: 9, 58 | // CARROTS: 6 59 | // } 60 | // 61 | ``` 62 | 63 | ## Examples 64 | 65 | ### Iterating Keys & Values 66 | 67 | ```js 68 | // with object-split 69 | split(salad).forEach(function(item) { 70 | console.log(item.key, item.value) 71 | }) 72 | ``` 73 | 74 | ```js 75 | // without object-split 76 | Object.keys(salad).forEach(function(key) { 77 | var value = salad[key] 78 | console.log(key, value) 79 | }) 80 | 81 | // or 82 | for (var key in salad) { 83 | var value = salad[key] 84 | console.log(key, value) 85 | } 86 | ``` 87 | 88 | ### Chaining Transformations 89 | 90 | ```js 91 | // with object-split 92 | var pieces = split(salad) 93 | .map(function(kv) { 94 | kv.value = calculate(kv.value) 95 | return kv 96 | }) 97 | .map(function(kv) { 98 | kv.value = recalculate(kv.value) 99 | return kv 100 | }) 101 | var newSalad = split.join(pieces) 102 | ``` 103 | 104 | Without splitting the Object into a similar structure to `split-object`, you're 105 | stuck with losing the keys (which is acceptable if you can deduce keys from the 106 | value) or using multiple `reduce` calls/`for..of` iteration: 107 | 108 | ```js 109 | // without object-split 110 | var newSalad = Object.keys(salad) 111 | .reduce(function(obj, key) { 112 | var value = salad[key] 113 | obj[key] = calculate(value) 114 | return obj 115 | }, {}) 116 | newSalad = Object.keys(newSalad) 117 | .reduce(function(obj, key) { 118 | var value = newSalad[key] 119 | obj[key] = recalculate(value) 120 | return obj 121 | }, {}) 122 | ``` 123 | 124 | `split-object` doesn't save a huge number of lines, but it 125 | saves some complexity, enables easier chaining and removes the hassle 126 | of extracting the value from the object on each iteration. 127 | 128 | ## Custom Key/Value Names 129 | 130 | To provide more semantic key/value names, supply a second argument to 131 | either `split` or `join` with the key/value mapping: 132 | ```js 133 | var salad = { 134 | apples: 1, 135 | bananas: 3, 136 | carrots: 2 137 | } 138 | 139 | var loudIngredients = split(salad, {key: 'name', value: 'amount'}).map(function(ingredient) { 140 | ingredient.name = ingredient.name.toUpperCase() 141 | ingredient.amount *= 3 142 | return ingredient 143 | }) 144 | 145 | // loudIngredients: 146 | // [ 147 | // { name: 'APPLES', amount: 3 }, 148 | // { name: 'BANANAS', amount: 9 }, 149 | // { name: 'CARROTS', amount: 6 } 150 | // ] 151 | 152 | var loudSalad = split.join(loudIngredients, {key: 'name', value: 'amount'}) 153 | 154 | // loudSalad: 155 | // { 156 | // APPLES: 3, 157 | // BANANAS: 9, 158 | // CARROTS: 6 159 | // } 160 | // 161 | 162 | ``` 163 | 164 | This also means you can use `join` to create an Object out of any Array 165 | of Objects with two properties. 166 | 167 | Remember to also supply the mapping to join otherwise it won't be able to find the correct key/value pair to re-form the object. 168 | 169 | ## See Also 170 | 171 | * [hughsk/flat](https://github.com/hughsk/flat) – Flatten/unflatten nested Javascript objects (Highly recommended for use with `split-object`). 172 | 173 | ## License 174 | 175 | MIT 176 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var split = require('./') 2 | 3 | var salad = { 4 | apples: 1, 5 | bananas: 3, 6 | carrots: 2 7 | } 8 | 9 | var loudIngredients = split(salad).map(function (ingredient) { 10 | return { 11 | key: ingredient.key.toUpperCase(), 12 | value: ingredient.value * 3 13 | } 14 | }) 15 | 16 | inspect('loud ingredients', loudIngredients) 17 | 18 | var loudSalad = split.join(loudIngredients) 19 | 20 | inspect('loud salad', loudSalad) 21 | 22 | function inspect (msg, item) { 23 | console.log(msg + '\n', require('util').inspect(item, {colors: true, depth: 30})) 24 | return item 25 | } 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = split 4 | module.exports.split = split 5 | module.exports.join = join 6 | 7 | function split (obj, opts) { 8 | opts = opts || {} 9 | var keyName = opts.key || 'key' 10 | var valueName = opts.value || 'value' 11 | var items = [] 12 | for (var key in obj) { 13 | if (!Object.prototype.hasOwnProperty.call(obj, key)) continue 14 | var kv = {} 15 | kv[keyName] = key 16 | kv[valueName] = obj[key] 17 | items.push(kv) 18 | } 19 | return items 20 | } 21 | 22 | function join (arr, opts) { 23 | opts = opts || {} 24 | var keyName = opts.key || 'key' 25 | var valueName = opts.value || 'value' 26 | var obj = {} 27 | for (var i = 0; i < arr.length; i++) { 28 | var item = arr[i] 29 | obj[item[keyName]] = item[valueName] 30 | } 31 | return obj 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "split-object", 3 | "version": "2.1.1", 4 | "description": "Work with Objects using built-in functional Array methods", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "nyc -s `which tape` test.js | tap-spec && standard && nyc report --reporter=lcov --reporter=text --reporter=html" 8 | }, 9 | "keywords": [ 10 | "object", 11 | "map", 12 | "functional", 13 | "collection", 14 | "array", 15 | "dict", 16 | "hash", 17 | "dictionary" 18 | ], 19 | "author": "Tim Oxley ", 20 | "license": "MIT", 21 | "dependencies": {}, 22 | "devDependencies": { 23 | "nyc": "^7.1.0", 24 | "standard": "^7.1.2", 25 | "tap-spec": "^4.1.1", 26 | "tape": "^4.6.0" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/timoxley/split-object.git" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/timoxley/split-object/issues" 34 | }, 35 | "homepage": "https://github.com/timoxley/split-object" 36 | } 37 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var test = require('tape') 4 | var split = require('./') 5 | var join = split.join 6 | 7 | test('sanity', function (t) { 8 | t.equal(typeof split, 'function', 'has split function') 9 | t.equal(typeof split.join, 'function', 'has join function') 10 | t.equal(split.split, split, 'split.split is alias for split') 11 | t.notEqual(split.split, split.join, 'split.join is not split.split') 12 | t.end() 13 | }) 14 | 15 | test('empty object', function (t) { 16 | t.deepEqual(split({}), [], 'split({}) creates empty array') 17 | t.deepEqual(join([]), {}, 'join([]) creates empty object') 18 | t.end() 19 | }) 20 | 21 | test('simple object', function (t) { 22 | var object = { key: 'value' } 23 | var array = [{ 24 | key: 'key', 25 | value: 'value' 26 | }] 27 | t.deepEqual(split(object), array, 'split: Object -> Array') 28 | t.deepEqual(join(array), object, 'join: Array -> Object') 29 | t.end() 30 | }) 31 | 32 | test('multi-key object', function (t) { 33 | var object = { 34 | apple: 'an apple', 35 | banana: 'a banana' 36 | } 37 | var array = [{ 38 | key: 'apple', 39 | value: 'an apple' 40 | }, { 41 | key: 'banana', 42 | value: 'a banana' 43 | }] 44 | 45 | t.deepEqual(split(object), array, 'split: Object -> Array') 46 | t.deepEqual(join(array), object, 'join: Array -> Object') 47 | t.end() 48 | }) 49 | 50 | test('prototype properties are not iterated', function (t) { 51 | var proto = {bad: true} 52 | var obj = Object.create(proto) 53 | obj.good = true 54 | t.deepEqual(split(obj), [{key: 'good', value: true}], 'split ignores proto properties') 55 | proto.hasOwnProperty = function () { return true } 56 | t.deepEqual(split(obj), [{key: 'good', value: true}], 'split ignores overwritten hasOwnProperty on proto') 57 | obj.hasOwnProperty = function () { return true } 58 | t.deepEqual(split(obj), [{key: 'good', value: true}, {key: 'hasOwnProperty', value: obj.hasOwnProperty}], 'split ignores overwritten hasOwnProperty on instance') 59 | t.end() 60 | }) 61 | 62 | test('custom key/value names', function (t) { 63 | var object = { 64 | apple: 'an apple', 65 | banana: 'a banana' 66 | } 67 | var array = [{ 68 | name: 'apple', 69 | description: 'an apple' 70 | }, { 71 | name: 'banana', 72 | description: 'a banana' 73 | }] 74 | 75 | t.deepEqual(split(object, {key: 'name', value: 'description'}), array, 'split: Object -> Array') 76 | t.deepEqual(join(array, {key: 'name', value: 'description'}), object, 'join: Array -> Object') 77 | t.end() 78 | }) 79 | --------------------------------------------------------------------------------