Skip to content

Commit

Permalink
Support for (non- &) enumerable props & symbols 🦄
Browse files Browse the repository at this point in the history
  • Loading branch information
mesqueeb committed Jul 13, 2019
1 parent 607e8ab commit 0e6327d
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 66 deletions.
57 changes: 36 additions & 21 deletions dist/index.cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ Object.defineProperty(exports, '__esModule', { value: true });

var isWhat = require('is-what');

function assignProp(carry, key, newVal, originalObject) {
var propType = originalObject.propertyIsEnumerable(key)
? 'enumerable'
: 'nonenumerable';
if (propType === 'enumerable')
carry[key] = newVal;
if (propType === 'nonenumerable') {
Object.defineProperty(carry, key, {
value: newVal,
enumerable: false,
writable: true,
configurable: true
});
}
}
function mergeRecursively(origin, newComer, extensions) {
// work directly on newComer if its not an object
if (!isWhat.isPlainObject(newComer)) {
Expand All @@ -16,21 +31,27 @@ function mergeRecursively(origin, newComer, extensions) {
return newComer;
}
// define newObject to merge all values upon
var newObject = (isWhat.isPlainObject(origin))
? Object.keys(origin)
.reduce(function (carry, key) {
var targetVal = origin[key];
var newObject = {};
if (isWhat.isPlainObject(origin)) {
var props_1 = Object.getOwnPropertyNames(origin);
var symbols_1 = Object.getOwnPropertySymbols(origin);
newObject = props_1.concat(symbols_1).reduce(function (carry, key) {
// @ts-ignore
if (!Object.keys(newComer).includes(key))
carry[key] = targetVal;
var targetVal = origin[key];
if ((!isWhat.isSymbol(key) && !Object.getOwnPropertyNames(newComer).includes(key)) ||
(isWhat.isSymbol(key) && !Object.getOwnPropertySymbols(newComer).includes(key))) {
assignProp(carry, key, targetVal, origin);
}
return carry;
}, {})
: {};
return Object.keys(newComer)
.reduce(function (carry, key) {
}, {});
}
var props = Object.getOwnPropertyNames(newComer);
var symbols = Object.getOwnPropertySymbols(newComer);
var result = props.concat(symbols).reduce(function (carry, key) {
// re-define the origin and newComer as targetVal and newVal
var newVal = newComer[key];
var targetVal = (isWhat.isPlainObject(origin))
// @ts-ignore
? origin[key]
: undefined;
// extend merge rules
Expand All @@ -39,20 +60,14 @@ function mergeRecursively(origin, newComer, extensions) {
newVal = extend(targetVal, newVal);
});
}
// early return when targetVal === undefined
if (targetVal === undefined) {
carry[key] = newVal;
return carry;
}
// When newVal is an object do the merge recursively
if (isWhat.isPlainObject(newVal)) {
carry[key] = mergeRecursively(targetVal, newVal, extensions);
return carry;
if (targetVal !== undefined && isWhat.isPlainObject(newVal)) {
newVal = mergeRecursively(targetVal, newVal, extensions);
}
// all the rest
carry[key] = newVal;
assignProp(carry, key, newVal, newComer);
return carry;
}, newObject);
return result;
}
/**
* Merge anything recursively.
Expand All @@ -63,7 +78,7 @@ function mergeRecursively(origin, newComer, extensions) {
* @param {...any[]} newComers
* @returns the result
*/
function merge (origin) {
function merge(origin) {
var newComers = [];
for (var _i = 1; _i < arguments.length; _i++) {
newComers[_i - 1] = arguments[_i];
Expand Down
59 changes: 37 additions & 22 deletions dist/index.esm.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
import { isArray, isPlainObject } from 'is-what';
import { isArray, isPlainObject, isSymbol } from 'is-what';

function assignProp(carry, key, newVal, originalObject) {
var propType = originalObject.propertyIsEnumerable(key)
? 'enumerable'
: 'nonenumerable';
if (propType === 'enumerable')
carry[key] = newVal;
if (propType === 'nonenumerable') {
Object.defineProperty(carry, key, {
value: newVal,
enumerable: false,
writable: true,
configurable: true
});
}
}
function mergeRecursively(origin, newComer, extensions) {
// work directly on newComer if its not an object
if (!isPlainObject(newComer)) {
Expand All @@ -12,21 +27,27 @@ function mergeRecursively(origin, newComer, extensions) {
return newComer;
}
// define newObject to merge all values upon
var newObject = (isPlainObject(origin))
? Object.keys(origin)
.reduce(function (carry, key) {
var targetVal = origin[key];
var newObject = {};
if (isPlainObject(origin)) {
var props_1 = Object.getOwnPropertyNames(origin);
var symbols_1 = Object.getOwnPropertySymbols(origin);
newObject = props_1.concat(symbols_1).reduce(function (carry, key) {
// @ts-ignore
if (!Object.keys(newComer).includes(key))
carry[key] = targetVal;
var targetVal = origin[key];
if ((!isSymbol(key) && !Object.getOwnPropertyNames(newComer).includes(key)) ||
(isSymbol(key) && !Object.getOwnPropertySymbols(newComer).includes(key))) {
assignProp(carry, key, targetVal, origin);
}
return carry;
}, {})
: {};
return Object.keys(newComer)
.reduce(function (carry, key) {
}, {});
}
var props = Object.getOwnPropertyNames(newComer);
var symbols = Object.getOwnPropertySymbols(newComer);
var result = props.concat(symbols).reduce(function (carry, key) {
// re-define the origin and newComer as targetVal and newVal
var newVal = newComer[key];
var targetVal = (isPlainObject(origin))
// @ts-ignore
? origin[key]
: undefined;
// extend merge rules
Expand All @@ -35,20 +56,14 @@ function mergeRecursively(origin, newComer, extensions) {
newVal = extend(targetVal, newVal);
});
}
// early return when targetVal === undefined
if (targetVal === undefined) {
carry[key] = newVal;
return carry;
}
// When newVal is an object do the merge recursively
if (isPlainObject(newVal)) {
carry[key] = mergeRecursively(targetVal, newVal, extensions);
return carry;
if (targetVal !== undefined && isPlainObject(newVal)) {
newVal = mergeRecursively(targetVal, newVal, extensions);
}
// all the rest
carry[key] = newVal;
assignProp(carry, key, newVal, newComer);
return carry;
}, newObject);
return result;
}
/**
* Merge anything recursively.
Expand All @@ -59,7 +74,7 @@ function mergeRecursively(origin, newComer, extensions) {
* @param {...any[]} newComers
* @returns the result
*/
function merge (origin) {
function merge(origin) {
var newComers = [];
for (var _i = 1; _i < arguments.length; _i++) {
newComers[_i - 1] = arguments[_i];
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "merge-anything",
"version": "2.3.5",
"version": "2.4.0",
"description": "Merge objects & other types recursively. A simple & small integration.",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
Expand Down
56 changes: 38 additions & 18 deletions src/merge.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { isArray, isPlainObject } from 'is-what'
import { isArray, isPlainObject, isSymbol } from 'is-what'

type Extension = (param1: any, param2: any) => any

interface IConfig {
extensions: Extension[]
}

function assignProp (carry, key, newVal, originalObject) {
const propType = originalObject.propertyIsEnumerable(key)
? 'enumerable'
: 'nonenumerable'
if (propType === 'enumerable') carry[key] = newVal
if (propType === 'nonenumerable') {
Object.defineProperty(carry, key, {
value: newVal,
enumerable: false,
writable: true,
configurable: true
})
}
}

function mergeRecursively(origin: any, newComer: any, extensions: Extension[]) {
// work directly on newComer if its not an object
if (!isPlainObject(newComer)) {
Expand All @@ -18,20 +33,31 @@ function mergeRecursively(origin: any, newComer: any, extensions: Extension[]) {
return newComer
}
// define newObject to merge all values upon
const newObject = (isPlainObject(origin))
? Object.keys(origin)
let newObject = {}
if (isPlainObject(origin)) {
const props = Object.getOwnPropertyNames(origin)
const symbols = Object.getOwnPropertySymbols(origin)
newObject = [...props, ...symbols]
.reduce((carry, key) => {
const targetVal = origin[key]
// @ts-ignore
if (!Object.keys(newComer).includes(key)) carry[key] = targetVal
const targetVal = origin[key]
if (
(!isSymbol(key) && !Object.getOwnPropertyNames(newComer).includes(key)) ||
(isSymbol(key) && !Object.getOwnPropertySymbols(newComer).includes(key))
) {
assignProp(carry, key, targetVal, origin)
}
return carry
}, {})
: {}
return Object.keys(newComer)
}
const props = Object.getOwnPropertyNames(newComer)
const symbols = Object.getOwnPropertySymbols(newComer)
let result = [...props, ...symbols]
.reduce((carry, key) => {
// re-define the origin and newComer as targetVal and newVal
let newVal = newComer[key]
const targetVal = (isPlainObject(origin))
// @ts-ignore
? origin[key]
: undefined
// extend merge rules
Expand All @@ -40,20 +66,14 @@ function mergeRecursively(origin: any, newComer: any, extensions: Extension[]) {
newVal = extend(targetVal, newVal)
})
}
// early return when targetVal === undefined
if (targetVal === undefined) {
carry[key] = newVal
return carry
}
// When newVal is an object do the merge recursively
if (isPlainObject(newVal)) {
carry[key] = mergeRecursively(targetVal, newVal, extensions)
return carry
if (targetVal !== undefined && isPlainObject(newVal)) {
newVal = mergeRecursively(targetVal, newVal, extensions)
}
// all the rest
carry[key] = newVal
assignProp(carry, key, newVal, newComer)
return carry
}, newObject)
return result
}

/**
Expand All @@ -65,7 +85,7 @@ function mergeRecursively(origin: any, newComer: any, extensions: Extension[]) {
* @param {...any[]} newComers
* @returns the result
*/
export default function (origin: IConfig | any, ...newComers: any[]) {
export default function merge (origin: IConfig | any, ...newComers: any[]) {
let extensions = null
let base = origin
if (isPlainObject(origin) && origin.extensions && Object.keys(origin).length === 1) {
Expand Down
53 changes: 50 additions & 3 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -372,11 +372,58 @@ test('works with unlimited depth', t => {
})

test('symbols as keys', t => {
let res, x, y
const mySymbol = Symbol('mySymbol')
const x = { value: 42, [mySymbol]: 'hello' }
const y = { other: 33 }
const res = merge(x, y)
x = { value: 42, [mySymbol]: 'hello' }
y = { other: 33 }
res = merge(x, y)
t.is(res.value, 42)
t.is(res.other, 33)
t.is(res[mySymbol], 'hello')
x = { value: 42 }
y = { other: 33, [mySymbol]: 'hello' }
res = merge(x, y)
t.is(res.value, 42)
t.is(res.other, 33)
t.is(res[mySymbol], 'hello')
})

test('nonenumerable keys', t => {
let x, y, res
const mySymbol = Symbol('mySymbol')
x = { value: 42 }
y = { other: 33 }
Object.defineProperty(x, 'xid', {
value: 1,
writable: true,
enumerable: false,
configurable: true
})
Object.defineProperty(x, mySymbol, {
value: 'original',
writable: true,
enumerable: false,
configurable: true
})
Object.defineProperty(y, 'yid', {
value: 2,
writable: true,
enumerable: false,
configurable: true
})
Object.defineProperty(y, mySymbol, {
value: 'new',
writable: true,
enumerable: false,
configurable: true
})
res = merge(x, y)
t.is(res.value, 42)
t.is(res.other, 33)
t.is(res.xid, 1)
t.is(res.yid, 2)
t.is(res[mySymbol], 'new')
t.is(Object.keys(res).length, 2)
t.true(Object.keys(res).includes('value'))
t.true(Object.keys(res).includes('other'))
})
2 changes: 1 addition & 1 deletion types/merge.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ interface IConfig {
* @param {...any[]} newComers
* @returns the result
*/
export default function (origin: IConfig | any, ...newComers: any[]): any;
export default function merge(origin: IConfig | any, ...newComers: any[]): any;
export {};

0 comments on commit 0e6327d

Please sign in to comment.