├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── index.js ├── license ├── package.json ├── readme.md └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | - '5' 5 | - '4' 6 | - '0.12' 7 | - '0.10' 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var isObj = require('is-obj'); 3 | 4 | var hasOwnProperty = Object.prototype.hasOwnProperty; 5 | var propIsEnumerable = Object.prototype.propertyIsEnumerable; 6 | 7 | function toObject(val) { 8 | if (val === null || val === undefined) { 9 | throw new TypeError('Cannot convert undefined or null to object'); 10 | } 11 | 12 | return Object(val); 13 | } 14 | 15 | function assignKey(to, from, key) { 16 | var val = from[key]; 17 | 18 | if (val === undefined || val === null) { 19 | return; 20 | } 21 | 22 | if (hasOwnProperty.call(to, key)) { 23 | if (to[key] === undefined || to[key] === null) { 24 | throw new TypeError('Cannot convert undefined or null to object (' + key + ')'); 25 | } 26 | } 27 | 28 | if (!hasOwnProperty.call(to, key) || !isObj(val)) { 29 | to[key] = val; 30 | } else { 31 | to[key] = assign(Object(to[key]), from[key]); 32 | } 33 | } 34 | 35 | function assign(to, from) { 36 | if (to === from) { 37 | return to; 38 | } 39 | 40 | from = Object(from); 41 | 42 | for (var key in from) { 43 | if (hasOwnProperty.call(from, key)) { 44 | assignKey(to, from, key); 45 | } 46 | } 47 | 48 | if (Object.getOwnPropertySymbols) { 49 | var symbols = Object.getOwnPropertySymbols(from); 50 | 51 | for (var i = 0; i < symbols.length; i++) { 52 | if (propIsEnumerable.call(from, symbols[i])) { 53 | assignKey(to, from, symbols[i]); 54 | } 55 | } 56 | } 57 | 58 | return to; 59 | } 60 | 61 | module.exports = function deepAssign(target) { 62 | target = toObject(target); 63 | 64 | for (var s = 1; s < arguments.length; s++) { 65 | assign(target, arguments[s]); 66 | } 67 | 68 | return target; 69 | }; 70 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Sindre Sorhus (sindresorhus.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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deep-assign", 3 | "version": "2.0.0", 4 | "description": "Recursive Object.assign()", 5 | "license": "MIT", 6 | "repository": "sindresorhus/deep-assign", 7 | "author": { 8 | "name": "Sindre Sorhus", 9 | "email": "sindresorhus@gmail.com", 10 | "url": "sindresorhus.com" 11 | }, 12 | "engines": { 13 | "node": ">=0.10.0" 14 | }, 15 | "scripts": { 16 | "test": "xo && ava" 17 | }, 18 | "files": [ 19 | "index.js" 20 | ], 21 | "keywords": [ 22 | "object", 23 | "obj", 24 | "assign", 25 | "extend", 26 | "properties", 27 | "merge", 28 | "clone", 29 | "copy", 30 | "mixin", 31 | "deep", 32 | "recursive", 33 | "key", 34 | "keys", 35 | "values", 36 | "prop", 37 | "properties" 38 | ], 39 | "dependencies": { 40 | "is-obj": "^1.0.0" 41 | }, 42 | "devDependencies": { 43 | "ava": "*", 44 | "xo": "*" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | Version 2 of this module ended up being [something I don't want](https://github.com/sindresorhus/deep-assign/issues/6) and I don't have time to fix it. 4 | 5 | Check out [`lodash.merge`](https://lodash.com/docs/#merge) or [`merge-options`](https://github.com/schnittstabil/merge-options) instead. 6 | 7 | --- 8 | 9 | # deep-assign [![Build Status](https://travis-ci.org/sindresorhus/deep-assign.svg?branch=master)](https://travis-ci.org/sindresorhus/deep-assign) 10 | 11 | > Recursive [`Object.assign()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) 12 | 13 | 14 | ## Install 15 | 16 | ``` 17 | $ npm install --save deep-assign 18 | ``` 19 | 20 | 21 | ## Usage 22 | 23 | ```js 24 | var deepAssign = require('deep-assign'); 25 | 26 | deepAssign({a: {b: 0}}, {a: {b: 1, c: 2}}, {a: {c: 3}}); 27 | //=> {a: {b: 1, c: 3}} 28 | ``` 29 | 30 | 31 | ### deepAssign(target, source, [source, ...]) 32 | 33 | Recursively assigns own enumerable properties of `source` objects to the `target` object and returns the `target` object. Additional `source` objects will overwrite previous ones. 34 | 35 | 36 | ## Related 37 | 38 | - [object-assign](https://github.com/sindresorhus/object-assign) - ES2015 Object.assign() ponyfill 39 | 40 | 41 | ## License 42 | 43 | MIT © [Sindre Sorhus](http://sindresorhus.com) 44 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import fn from './'; 3 | 4 | const nativeSymbols = Object(Symbol.for('')) !== Symbol.for(''); 5 | 6 | test('assign own enumerable propreties from source to target object', t => { 7 | t.deepEqual(fn({foo: 0}, {bar: 1}), {foo: 0, bar: 1}); 8 | t.deepEqual(fn({foo: 0}, null, undefined), {foo: 0}); 9 | t.deepEqual(fn({foo: 0}, null, undefined, {bar: 1}, null), {foo: 0, bar: 1}); 10 | }); 11 | 12 | test('do not assign null values', t => { 13 | t.deepEqual(fn({}, {foo: null}), {}); 14 | }); 15 | 16 | test('throw TypeError on null targets', t => { 17 | t.throws(() => fn({foo: null}, {foo: {}}), TypeError); 18 | }); 19 | 20 | test('assign proprety, if proprety is null in the prototype chain', t => { 21 | const Unicorn = () => {}; 22 | Unicorn.prototype.rainbows = null; 23 | const unicorn = new Unicorn(); 24 | t.is(fn(unicorn, {rainbows: 'many'}).rainbows, 'many'); 25 | }); 26 | 27 | test('do not assign undefined values', t => { 28 | t.deepEqual(fn({}, {foo: undefined}), {}); 29 | }); 30 | 31 | test('throw TypeError on undefined targets', t => { 32 | t.throws(() => fn({foo: undefined}, {foo: {}}), TypeError); 33 | }); 34 | 35 | test('assign proprety, if proprety is undefined in the prototype chain', t => { 36 | const Unicorn = () => {}; 37 | Unicorn.prototype.rainbows = undefined; 38 | const unicorn = new Unicorn(); 39 | t.is(fn(unicorn, {rainbows: 'many'}).rainbows, 'many'); 40 | }); 41 | 42 | test('do not merge with a target proprety in the prototype chain', t => { 43 | const amountOfRainbows = {amount: 'many'}; 44 | const Unicorn = () => {}; 45 | Unicorn.prototype.rainbows = amountOfRainbows; 46 | const unicorn = fn(new Unicorn(), {rainbows: 'none'}); 47 | t.is(unicorn.rainbows, 'none'); 48 | t.is(unicorn.rainbows.amount, undefined); 49 | t.is(Unicorn.prototype.rainbows, amountOfRainbows); 50 | }); 51 | 52 | test('support numbers as targets', t => { 53 | const target = fn({answer: 42}, {answer: {rainbows: 'many'}}); 54 | t.is(target.answer / 7, 6); 55 | t.is(target.answer.constructor, Number); 56 | t.is(target.answer.rainbows, 'many'); 57 | }); 58 | 59 | test('support boolean as targets', t => { 60 | const target = fn({foo: true}, {foo: {rainbows: 'many'}}); 61 | t.is(target.foo.toString(), 'true'); 62 | t.is(target.foo.constructor, Boolean); 63 | t.is(target.foo.rainbows, 'many'); 64 | }); 65 | 66 | test('support strings as targets', t => { 67 | const target = fn({rainbows: 'many'}, {rainbows: {answer: 42}}); 68 | t.is(String(target.rainbows), 'many'); 69 | t.is(target.rainbows.constructor, String); 70 | t.is(target.rainbows.answer, 42); 71 | }); 72 | 73 | test('support arrays as targets', t => { 74 | const target = {a: ['many']}; 75 | const source = {a: []}; 76 | source.a[2] = 'unicorns'; 77 | fn(target, source, {a: {answer: 42}}); 78 | t.is(target.a[0], 'many'); 79 | t.is(target.a[1], undefined); 80 | t.is(target.a[2], 'unicorns'); 81 | t.is(target.a.constructor, Array); 82 | t.is(target.a.answer, 42); 83 | }); 84 | 85 | test('support functions', t => { 86 | const oracle42 = () => 42; 87 | const oracle666 = () => 666; 88 | oracle42.foo = true; 89 | oracle42.bar = true; 90 | oracle666.bar = false; 91 | const target = fn({}, {oracle: oracle42}, {oracle: oracle666}); 92 | t.is(target.oracle(), 42); 93 | t.is(target.oracle.foo, true); 94 | t.is(target.oracle.bar, false); 95 | }); 96 | 97 | test('support multiple sources', t => { 98 | t.deepEqual(fn({foo: 0}, {bar: 1}, {bar: 2}), {foo: 0, bar: 2}); 99 | t.deepEqual(fn({}, {}, {foo: 1}), {foo: 1}); 100 | }); 101 | 102 | test('only iterate own keys', t => { 103 | const Unicorn = () => {}; 104 | Unicorn.prototype.rainbows = 'many'; 105 | const unicorn = new Unicorn(); 106 | unicorn.bar = 1; 107 | t.deepEqual(fn({foo: 1}, unicorn), {foo: 1, bar: 1}); 108 | }); 109 | 110 | test('return the modified target object', t => { 111 | const target = {}; 112 | const returned = fn(target, {a: 1}); 113 | t.is(returned, target); 114 | }); 115 | 116 | test('support `Object.create(null)` objects', t => { 117 | const obj = Object.create(null); 118 | obj.foo = true; 119 | t.deepEqual(fn({}, obj), {foo: true}); 120 | }); 121 | 122 | test('support `Object.create(null)` targets', t => { 123 | const target = Object.create(null); 124 | const expected = Object.create(null); 125 | target.foo = true; 126 | expected.foo = true; 127 | expected.bar = false; 128 | t.deepEqual(fn(target, {bar: false}), expected); 129 | }); 130 | 131 | test('preserve property order', t => { 132 | const letters = 'abcdefghijklmnopqrst'; 133 | const source = {}; 134 | letters.split('').forEach(letter => { 135 | source[letter] = letter; 136 | }); 137 | const target = fn({}, source); 138 | t.is(Object.keys(target).join(''), letters); 139 | }); 140 | 141 | test('deep', t => { 142 | t.deepEqual(fn({ 143 | foo: { 144 | foo: { 145 | foo: true 146 | }, 147 | bar: { 148 | bar: false 149 | } 150 | } 151 | }, { 152 | foo: { 153 | foo: { 154 | foo: false, 155 | bar: true 156 | } 157 | }, 158 | bar: true 159 | }), { 160 | foo: { 161 | foo: { 162 | foo: false, 163 | bar: true 164 | }, 165 | bar: { 166 | bar: false 167 | } 168 | }, 169 | bar: true 170 | }); 171 | }); 172 | 173 | test('support symbols as targets', t => { 174 | const target = fn({sym: Symbol.for('foo')}, {sym: {rainbows: 'many'}}); 175 | t.true(target.sym instanceof Symbol); 176 | t.is(target.sym.rainbows, 'many'); 177 | }); 178 | 179 | (nativeSymbols ? test : test.skip)('support symbol properties', t => { 180 | const target = {}; 181 | const source = {}; 182 | const sym = Symbol('foo'); 183 | source[sym] = 'bar'; 184 | fn(target, source); 185 | t.is(target[sym], 'bar'); 186 | }); 187 | 188 | (nativeSymbols ? test : test.skip)('only copy enumerable symbols', t => { 189 | const target = {}; 190 | const source = {}; 191 | const sym = Symbol('foo'); 192 | Object.defineProperty(source, sym, { 193 | enumerable: false, 194 | value: 'bar' 195 | }); 196 | fn(target, source); 197 | t.false(sym in target); 198 | }); 199 | 200 | test('do not transform functions', t => { 201 | const target = {foo: function bar() {}}; 202 | const source = {}; 203 | t.is(typeof fn({}, target, source).foo, 'function'); 204 | }); 205 | 206 | test('bug - reuses object in deep assignment', t => { 207 | const fixture = { 208 | foo: { 209 | bar: false 210 | } 211 | }; 212 | 213 | const run = x => { 214 | const opts = fn({}, fixture); 215 | 216 | if (x === true) { 217 | opts.foo.bar = true; 218 | } 219 | 220 | return opts.foo.bar; 221 | }; 222 | 223 | t.true(run(true)); 224 | t.false(run()); 225 | }); 226 | --------------------------------------------------------------------------------