├── .gitignore ├── .travis.yml ├── package.json ├── test ├── mutate-leftmost-argument.test.js ├── dont-merge-arrays.test.js ├── compatibility.test.js └── recursive.test.js ├── LICENSE ├── README.md ├── appveyor.yml └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 2 | # ╔╦╗╦═╗╔═╗╦ ╦╦╔═╗ ┬ ┬┌┬┐┬ # 3 | # ║ ╠╦╝╠═╣╚╗╔╝║╚═╗ └┬┘││││ # 4 | # o ╩ ╩╚═╩ ╩ ╚╝ ╩╚═╝o ┴ ┴ ┴┴─┘ # 5 | # # 6 | # This file configures Travis CI. # 7 | # (i.e. how we run the tests... mainly) # 8 | # # 9 | # https://docs.travis-ci.com/user/customizing-the-build # 10 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 11 | 12 | language: node_js 13 | 14 | node_js: 15 | - "6" 16 | - "8" 17 | - "10" 18 | - "node" 19 | 20 | branches: 21 | only: 22 | - master 23 | 24 | notifications: 25 | email: 26 | - ci@sailsjs.com 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "merge-defaults", 3 | "version": "0.2.2", 4 | "description": "A recursive version of _.defaults.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/mikermcneil/merge-defaults.git" 12 | }, 13 | "keywords": [ 14 | "lodash", 15 | "_", 16 | "defaults", 17 | "recursive", 18 | "deep", 19 | "merge", 20 | "underscore", 21 | "_.defaults" 22 | ], 23 | "author": "Mike McNeil", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/mikermcneil/merge-defaults/issues" 27 | }, 28 | "homepage": "https://github.com/mikermcneil/merge-defaults", 29 | "dependencies": { 30 | "@sailshq/lodash": "^3.10.2" 31 | }, 32 | "devDependencies": { 33 | "should": "~2.1.1", 34 | "mocha": "~1.20.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/mutate-leftmost-argument.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var should = require('should'); 3 | var _ = require('@sailshq/lodash'); 4 | _.mergeDefaults = require('../'); 5 | 6 | 7 | 8 | describe('4-level-deep merge', function() { 9 | 10 | var X = { 11 | baz: { 12 | foo: { 13 | a: 1, 14 | b: 2, 15 | bar: { 16 | a: 1, 17 | b: 2 18 | } 19 | } 20 | } 21 | }; 22 | var Y = { 23 | foon: 82, 24 | baz: { 25 | foo: { 26 | a: 100, 27 | c: 3, 28 | bar: { 29 | a: 10, 30 | c: 3 31 | } 32 | } 33 | } 34 | }; 35 | var result = _.mergeDefaults(X, Y); 36 | 37 | it('should retain the values in X (first arg)', function() { 38 | assert.equal(X.baz.foo.a, 1); 39 | assert.equal(X.baz.foo.bar.a, 1); 40 | assert.equal(X.baz.foo.bar.b, 2); 41 | }); 42 | 43 | it('should receive new values from Y (second arg)', function() { 44 | assert.equal(X.baz.foo.bar.c, 3); 45 | assert.equal(X.foon, 82); 46 | }); 47 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mike McNeil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Note: Lodash now includes a [`defaultsDeep`](https://lodash.com/docs/4.17.10#defaultsDeep) function. 2 | 3 | # merge-defaults 4 | 5 | Implements a deep version of `_.defaults`. 6 | 7 | > **Important!** 8 | > 9 | > This module DOES NOT merge arrays or dates. 10 | 11 | 12 | ## Installation 13 | 14 | ```sh 15 | $ npm install merge-defaults 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```javascript 21 | 22 | var _ = require('lodash'); 23 | 24 | // Override basic `_.defaults` 25 | _.defaults = require('merge-defaults'); 26 | 27 | // Or you can add it as a new method 28 | _.mergeDefaults = require('merge-defaults'); 29 | 30 | ``` 31 | 32 | ## Why? 33 | 34 | This module is a temporary solution, until lodash has something 35 | similar in core that can be called as a single method. 36 | In the mean time, this is a hack to make our code more readable. 37 | i.e. I know what `_.defaults` means intuitively, but I have to look 38 | up `_.partialRight` every time. 39 | 40 | To get the latest status, see the [original issue in the lodash repo](https://github.com/lodash/lodash/issues/154#issuecomment-32140379). 41 | 42 | I'll update this repo with install/version info if something comparable is 43 | added to lodash core at some point. 44 | 45 | 46 | 47 | ## License 48 | 49 | MIT © Mike McNeil 2014 50 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # # # # # # # # # # # # # # # # # # # # # # # # # # 2 | # ╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╦╔═╗╦═╗ ┬ ┬┌┬┐┬ # 3 | # ╠═╣╠═╝╠═╝╚╗╔╝║╣ ╚╦╝║ ║╠╦╝ └┬┘││││ # 4 | # ╩ ╩╩ ╩ ╚╝ ╚═╝ ╩ ╚═╝╩╚═o ┴ ┴ ┴┴─┘ # 5 | # # 6 | # This file configures Appveyor CI. # 7 | # (i.e. how we run the tests on Windows) # 8 | # # 9 | # https://www.appveyor.com/docs/lang/nodejs-iojs/ # 10 | # # # # # # # # # # # # # # # # # # # # # # # # # # 11 | 12 | 13 | # Test against these versions of Node.js. 14 | environment: 15 | matrix: 16 | - nodejs_version: "6" 17 | - nodejs_version: "8" 18 | - nodejs_version: "10" 19 | 20 | # Install scripts. (runs after repo cloning) 21 | install: 22 | # Get the latest stable version of Node.js 23 | # (Not sure what this is for, it's just in Appveyor's example.) 24 | - ps: Install-Product node $env:nodejs_version 25 | # Install declared dependencies 26 | - npm install 27 | 28 | 29 | # Post-install test scripts. 30 | test_script: 31 | # Output Node and NPM version info. 32 | # (Presumably just in case Appveyor decides to try any funny business? 33 | # But seriously, always good to audit this kind of stuff for debugging.) 34 | - node --version 35 | - npm --version 36 | # Run the actual tests. 37 | - npm run custom-tests 38 | 39 | 40 | # Don't actually build. 41 | # (Not sure what this is for, it's just in Appveyor's example. 42 | # I'm not sure what we're not building... but I'm OK with not 43 | # building it. I guess.) 44 | build: off 45 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var _ = require('@sailshq/lodash'); 2 | 3 | /** 4 | * defaultsDeep 5 | * 6 | * Implement a deep version of `_.defaults`. 7 | * 8 | * This method is hopefully temporary, until lodash has something 9 | * similar that can be called in a single method. For now, it's 10 | * worth it to use a temporary module for readability. 11 | * (i.e. I know what `_.defaults` means offhand- not true for `_.partialRight`) 12 | */ 13 | 14 | // In case the end user decided to do `_.defaults = require('merge-defaults')`, 15 | // before doing anything else, let's make SURE we have a reference to the original 16 | // `_.defaults()` method definition. 17 | var origLodashDefaults = _.defaults; 18 | 19 | // Corrected: see https://github.com/lodash/lodash/issues/540 20 | // module.exports = function (/* ... */) { 21 | 22 | // if (typeof arguments[0] !== 'object') return origLodashDefaults.apply(_, Array.prototype.slice.call(arguments)); 23 | // else { 24 | // var result = _mergeDefaults.apply(_, Array.prototype.slice.call(arguments)); 25 | 26 | // // Ensure that original object is mutated 27 | // _.merge(arguments[0], result); 28 | 29 | // return result; 30 | // } 31 | // }; 32 | 33 | module.exports = _.partialRight(_.merge, function recursiveDefaults ( /* ... */ ) { 34 | 35 | // Ensure dates and arrays are not recursively merged 36 | if (_.isArray(arguments[0]) || _.isDate(arguments[0])) { 37 | return arguments[0]; 38 | } 39 | return _.merge(arguments[0], arguments[1], recursiveDefaults); 40 | }); 41 | 42 | //origLodashDefaults.apply(_, Array.prototype.slice.call(arguments)); 43 | 44 | // module.exports = _.partialRight(_.merge, _.defaults); 45 | 46 | // module.exports = _.partialRight(_.merge, function deep(a, b) { 47 | // // Ensure dates and arrays are not recursively merged 48 | // if (_.isArray(a) || _.isDate(a)) { 49 | // return a; 50 | // } 51 | // else return _.merge(a, b, deep); 52 | // }); -------------------------------------------------------------------------------- /test/dont-merge-arrays.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var _ = require('@sailshq/lodash'); 3 | _.mergeDefaults = require('../'); 4 | 5 | 6 | describe('mergeDefaults', function() { 7 | 8 | describe('dont merge arrays', function() { 9 | 10 | var X, Y, result; 11 | 12 | beforeEach(function() { 13 | X = { 14 | z: 1, 15 | a: 2, 16 | b: 3, 17 | d: {}, 18 | e: [] 19 | }; 20 | Y = { 21 | a: 1, 22 | b: 22, 23 | c: 33, 24 | d: { 25 | x: 10 26 | }, 27 | e: ['a','b'] 28 | }; 29 | result = _.mergeDefaults(X, Y); 30 | }); 31 | 32 | it('should return an object', function() { 33 | assert(typeof result === 'object'); 34 | }); 35 | it('should NOT MERGE ARRAYS in sub-objects', function() { 36 | assert.deepEqual(result.e, []); 37 | }); 38 | }); 39 | 40 | describe('complex (recursive)', function() { 41 | 42 | var X, Y, result; 43 | 44 | beforeEach(function() { 45 | X = { 46 | views: { 47 | foo: {}, 48 | blueprints: { 49 | someArray: ['z'], 50 | enabled: true 51 | } 52 | }, 53 | connections: {}, 54 | z: 1, 55 | a: 2, 56 | b: 3, 57 | d: {} 58 | }; 59 | Y = { 60 | views: { 61 | locales: ['en', 'es'], 62 | 63 | }, 64 | controllers: { 65 | foo: { 66 | bar: 'asdf' 67 | }, 68 | blueprints: { 69 | someArray: ['a','b'], 70 | enabled: false 71 | } 72 | }, 73 | connections: { 74 | mysql: { 75 | host: 'localhost', 76 | port: 1835913851 77 | } 78 | }, 79 | a: 1, 80 | b: 22, 81 | c: 33, 82 | d: { 83 | x: 10 84 | } 85 | }; 86 | result = _.mergeDefaults(X, Y); 87 | }); 88 | 89 | it('should return an object', function() { 90 | assert(typeof result === 'object'); 91 | }); 92 | it('should NOT MERGE ARRAYS in sub-objects', function() { 93 | assert.deepEqual(result.views.blueprints.someArray, ['z']); 94 | }); 95 | }); 96 | }); -------------------------------------------------------------------------------- /test/compatibility.test.js: -------------------------------------------------------------------------------- 1 | var _ = require('@sailshq/lodash'); 2 | var should = require('should'); 3 | _.defaultsDeep = require('../'); 4 | 5 | 6 | 7 | /** 8 | * Purpose: 9 | * This test exists to make sure I didn't break anything when 10 | * using mergeDefaults to override `_.defaults`. 11 | */ 12 | 13 | // From Lodash core tests: 14 | // https://github.com/lodash/lodash/blob/master/test/test.js#L1843 15 | describe('Test that _.mergeDefaults is backwards compatible with _.defaults \n', function() { 16 | it('should assign properties of a source object if missing on the destination object', function() { 17 | deepEqual(_.defaultsDeep({ 18 | 'a': 1 19 | }, { 20 | 'a': 2, 21 | 'b': 2 22 | }), { 23 | 'a': 1, 24 | 'b': 2 25 | }); 26 | }); 27 | 28 | it('should assign own source properties', function() { 29 | function Foo() { 30 | this.a = 1; 31 | this.c = 3; 32 | } 33 | 34 | Foo.prototype.b = 2; 35 | deepEqual(_.defaultsDeep({ 36 | 'c': 2 37 | }, new Foo()), { 38 | 'a': 1, 39 | 'c': 2 40 | }); 41 | }); 42 | 43 | it('should accept multiple source objects', function() { 44 | var expected = { 45 | 'a': 1, 46 | 'b': 2, 47 | 'c': 3 48 | }; 49 | deepEqual(_.defaultsDeep({ 50 | 'a': 1, 51 | 'b': 2 52 | }, { 53 | 'b': 3 54 | }, { 55 | 'c': 3 56 | }), expected); 57 | deepEqual(_.defaultsDeep({ 58 | 'a': 1, 59 | 'b': 2 60 | }, { 61 | 'b': 3, 62 | 'c': 3 63 | }, { 64 | 'c': 2 65 | }), expected); 66 | }); 67 | 68 | it('should not overwrite `null` values', function() { 69 | var actual = _.defaultsDeep({ 70 | 'a': null 71 | }, { 72 | 'a': 1 73 | }); 74 | strictEqual(actual.a, null); 75 | }); 76 | 77 | it('should overwrite `undefined` values', function() { 78 | var actual = _.defaultsDeep({ 79 | 'a': undefined 80 | }, { 81 | 'a': 1 82 | }); 83 | strictEqual(actual.a, 1); 84 | }); 85 | 86 | it('should not error on `null` or `undefined` sources (test in IE < 9)', function() { 87 | try { 88 | deepEqual(_.defaultsDeep({ 89 | 'a': 1 90 | }, null, undefined, { 91 | 'a': 2, 92 | 'b': 2 93 | }), { 94 | 'a': 1, 95 | 'b': 2 96 | }); 97 | } catch (e) { 98 | throw e; 99 | } 100 | }); 101 | }); 102 | 103 | 104 | // helper methods 105 | function strictEqual(x, y) { 106 | return should(x).equal(y); 107 | } 108 | 109 | function deepEqual(x, y) { 110 | return should(x).eql(y); 111 | } -------------------------------------------------------------------------------- /test/recursive.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var should = require('should'); 3 | var _ = require('@sailshq/lodash'); 4 | _.mergeDefaults = require('../'); 5 | 6 | describe('mergeDefaults', function() { 7 | 8 | describe('basic (recursive)', function() { 9 | 10 | var X, Y, result; 11 | 12 | beforeEach(function() { 13 | X = { 14 | z: 1, 15 | a: 2, 16 | b: 3, 17 | d: {} 18 | }; 19 | Y = { 20 | a: 1, 21 | b: 22, 22 | c: 33, 23 | d: { 24 | x: 10 25 | } 26 | }; 27 | result = _.mergeDefaults(X, Y); 28 | }); 29 | 30 | it('should return an object', function() { 31 | should(result).be.an.Object; 32 | }); 33 | it('should have the expected values from first arg', function() { 34 | should(result).have.property('z', 1); 35 | should(result).have.property('a', 2); 36 | should(result).have.property('b', 3); 37 | }); 38 | it('should have the expected values from second arg', function() { 39 | should(result).have.property('c', 33); 40 | }); 41 | it('should have recursively merged the sub-objects', function() { 42 | should(result).have 43 | .property('d').with.a 44 | .property('x', 10); 45 | }); 46 | }); 47 | 48 | describe('complex (recursive)', function() { 49 | 50 | var X, Y, result; 51 | 52 | beforeEach(function() { 53 | X = { 54 | views: { 55 | foo: {}, 56 | blueprints: { 57 | enabled: true 58 | } 59 | }, 60 | connections: {}, 61 | z: 1, 62 | a: 2, 63 | b: 3, 64 | d: {} 65 | }; 66 | Y = { 67 | views: { 68 | locales: ['en', 'es'], 69 | 70 | }, 71 | controllers: { 72 | foo: { 73 | bar: 'asdf' 74 | }, 75 | blueprints: { 76 | enabled: false 77 | } 78 | }, 79 | connections: { 80 | mysql: { 81 | host: 'localhost', 82 | port: 1835913851 83 | } 84 | }, 85 | a: 1, 86 | b: 22, 87 | c: 33, 88 | d: { 89 | x: 10 90 | } 91 | }; 92 | result = _.mergeDefaults(X, Y); 93 | }); 94 | 95 | it('should return an object', function() { 96 | should(result).be.an.Object; 97 | }); 98 | it('should have the expected values from first arg', function() { 99 | should(result).have.property('z', 1); 100 | should(result).have.property('a', 2); 101 | should(result).have.property('b', 3); 102 | }); 103 | it('should have the expected values from second arg', function() { 104 | should(result).have.property('c', 33); 105 | }); 106 | it('should have recursively merged the sub-objects', function() { 107 | should(result).have 108 | .property('d').with.a 109 | .property('x', 10); 110 | }); 111 | 112 | it('should still work even when shit gets crazy', function() { 113 | // .views.locales 114 | should(result).have 115 | .property('views').with.a 116 | .property('locales'); 117 | 118 | // .views.blueprints.enabled = true 119 | should(result).have 120 | .property('views').with 121 | .property('blueprints').with 122 | .property('enabled', true); 123 | 124 | // controllers.foo.bar 125 | should(result).have 126 | .property('controllers').with 127 | .property('foo').with 128 | .property('bar'); 129 | 130 | // connections.mysql.host = 'localhost' 131 | should(result).have 132 | .property('connections').with 133 | .property('mysql').with 134 | .property('host', 'localhost'); 135 | }); 136 | 137 | }); 138 | 139 | describe('3-level-deep merge', function() { 140 | 141 | var X = { 142 | foo: { 143 | a:1, 144 | b:2, 145 | bar: { 146 | a:1, 147 | b:2 148 | } 149 | } 150 | }; 151 | var Y = { 152 | foo: { 153 | a:100, 154 | c:3, 155 | bar: { 156 | a:10, 157 | c:3 158 | } 159 | } 160 | }; 161 | var result = _.mergeDefaults(X, Y); 162 | 163 | it('should retain the values in X (first arg)', function (){ 164 | assert.equal(result.foo.a, 1); 165 | assert.equal(result.foo.bar.a, 1); 166 | assert.equal(result.foo.bar.b, 2); 167 | }); 168 | 169 | it('should receive new values from Y (second arg)', function () { 170 | assert.equal(result.foo.bar.c, 3); 171 | }); 172 | }); 173 | 174 | 175 | describe('4-level-deep merge', function() { 176 | 177 | var X = { 178 | baz: { 179 | foo: { 180 | a:1, 181 | b:2, 182 | bar: { 183 | a:1, 184 | b:2 185 | } 186 | } 187 | } 188 | }; 189 | var Y = { 190 | baz: { 191 | foo: { 192 | a:100, 193 | c:3, 194 | bar: { 195 | a:10, 196 | c:3 197 | } 198 | } 199 | } 200 | }; 201 | var result = _.mergeDefaults(X, Y); 202 | 203 | it('should retain the values in X (first arg)', function (){ 204 | assert.equal(result.baz.foo.a, 1); 205 | assert.equal(result.baz.foo.bar.a, 1); 206 | assert.equal(result.baz.foo.bar.b, 2); 207 | }); 208 | 209 | it('should receive new values from Y (second arg)', function () { 210 | assert.equal(result.baz.foo.bar.c, 3); 211 | }); 212 | }); 213 | }); --------------------------------------------------------------------------------