Skip to content

Commit

Permalink
Merge pull request #281 from kanongil/symbol-support
Browse files Browse the repository at this point in the history
Symbol support
  • Loading branch information
hueniverse authored Nov 28, 2018
2 parents c123e61 + bec8b86 commit b3b5134
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 33 deletions.
22 changes: 14 additions & 8 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,28 +137,32 @@ var config = Hoek.applyToDefaults(defaults, options, true); // results in { host
```

### applyToDefaultsWithShallow(defaults, options, keys)
keys is an array of key names to shallow copy
keys is an array of dot-separated, or array-based, key paths to shallow copy

Apply options to a copy of the defaults. Keys specified in the last parameter are shallow copied from options instead of merged.

```javascript

var defaults = {
db: {
server: {
host: "localhost",
port: 8000
},
name: 'example'
};
}
};

var options = { server: { port: 8080 } };

var config = Hoek.applyToDefaultsWithShallow(defaults, options, ['server']); // results in { server: { port: 8080 }, name: 'example' }
var config = Hoek.applyToDefaultsWithShallow(defaults, options, ['db.server']); // results in { db: { server: { port: 8080 }, name: 'example' } }
var config = Hoek.applyToDefaultsWithShallow(defaults, options, [['db', 'server']]); // results in { db: { server: { port: 8080 }, name: 'example' } }
```

### deepEqual(b, a, [options])

Performs a deep comparison of the two values including support for circular dependencies, prototype, and properties. To skip prototype comparisons, use `options.prototype = false`
Performs a deep comparison of the two values including support for circular dependencies, prototype, and enumerable properties.
To skip prototype comparisons, use `options.prototype = false`

```javascript
Hoek.deepEqual({ a: [1, 2], b: 'string', c: { d: true } }, { a: [1, 2], b: 'string', c: { d: true } }); //results in true
Expand Down Expand Up @@ -218,16 +222,18 @@ flattenedArray = Hoek.flatten(array, target); // results in [4, [5], 1, 2, 3]

### reach(obj, chain, [options])

Converts an object key chain string to reference
Converts an object key chain string or array to reference

- `options` - optional settings
- `separator` - string to split chain path on, defaults to '.'
- `default` - value to return if the path or value is not present, default is `undefined`
- `strict` - if `true`, will throw an error on missing member, default is `false`
- `functions` - if `true` allow traversing functions for properties. `false` will throw an error if a function is part of the chain.

A chain including negative numbers will work like negative indices on an
array.
A chain can be a string that will be split into key names using `separator`,
or an array containing each individual key name.

A chain including negative numbers will work like negative indices on an array.

If chain is `null`, `undefined` or `false`, the object itself will be returned.

Expand All @@ -238,7 +244,7 @@ var obj = {a : {b : { c : 1}}};

Hoek.reach(obj, chain); // returns 1

var chain = 'a.b.-1';
var chain = ['a', 'b', -1];
var obj = {a : {b : [2,3,6]}};

Hoek.reach(obj, chain); // returns 6
Expand Down
32 changes: 31 additions & 1 deletion lib/deep-equal.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ internals.isSetSimpleEqual = function (obj, ref) {
internals.isDeepEqualObj = function (instanceType, obj, ref, options, seen) {

const { isDeepEqual, valueOf, hasOwnEnumerableProperty } = internals;
const { keys } = Object;
const { keys, getOwnPropertySymbols } = Object;

if (instanceType === internals.arrayType) {
if (options.part) {
Expand Down Expand Up @@ -226,6 +226,36 @@ internals.isDeepEqualObj = function (instanceType, obj, ref, options, seen) {
}
}

// Check symbols

const objSymbols = getOwnPropertySymbols(obj);
const refSymbols = new Set(getOwnPropertySymbols(ref));

for (let i = 0; i < objSymbols.length; ++i) {
const key = objSymbols[i];

if (hasOwnEnumerableProperty(obj, key)) {
if (!hasOwnEnumerableProperty(ref, key)) {
return false;
}

if (!isDeepEqual(obj[key], ref[key], options, seen)) {
return false;
}
}
else if (hasOwnEnumerableProperty(ref, key)) {
return false;
}

refSymbols.delete(key);
}

for (const key of refSymbols) {
if (hasOwnEnumerableProperty(ref, key)) {
return false;
}
}

return true;
};

Expand Down
49 changes: 30 additions & 19 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ exports.clone = function (obj, seen) {
seen.set(obj, newObj);

if (cloneDeep) {
const keys = Object.getOwnPropertyNames(obj);
const keys = Reflect.ownKeys(obj);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];

Expand Down Expand Up @@ -126,10 +126,12 @@ exports.merge = function (target, source, isNullOverride /* = true */, isMergeAr
return target;
}

const keys = Object.keys(source);
const keys = Reflect.ownKeys(source);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
if (key === '__proto__') {
if (key === '__proto__' ||
!Object.prototype.propertyIsEnumerable.call(source, key)) {

continue;
}

Expand Down Expand Up @@ -199,19 +201,21 @@ exports.cloneWithShallow = function (source, keys) {

const storage = internals.store(source, keys); // Move shallow copy items to storage
const copy = exports.clone(source); // Deep copy the rest
internals.restore(copy, source, storage); // Shallow copy the stored items and restore
internals.restore(copy, source, storage); // Shallow copy the stored items and restore
return copy;
};


internals.store = function (source, keys) {

const storage = {};
const storage = new Map();
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
const value = exports.reach(source, key);
if (value !== undefined) {
storage[key] = value;
if (typeof value === 'object' ||
typeof value === 'function') {

storage.set(key, value);
internals.reachSet(source, key, undefined);
}
}
Expand All @@ -222,18 +226,16 @@ internals.store = function (source, keys) {

internals.restore = function (copy, source, storage) {

const keys = Object.keys(storage);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
internals.reachSet(copy, key, storage[key]);
internals.reachSet(source, key, storage[key]);
for (const [key, value] of storage) {
internals.reachSet(copy, key, value);
internals.reachSet(source, key, value);
}
};


internals.reachSet = function (obj, key, value) {

const path = key.split('.');
const path = Array.isArray(key) ? key : key.split('.');
let ref = obj;
for (let i = 0; i < path.length; ++i) {
const segment = path[i];
Expand Down Expand Up @@ -334,7 +336,8 @@ exports.contain = function (ref, values, options) {
!Array.isArray(values)) {

valuePairs = values;
values = Object.keys(values);
const symbols = Object.getOwnPropertySymbols(values).filter(Object.prototype.propertyIsEnumerable.bind(values));
values = [...Object.keys(values), ...symbols];
}
else {
values = [].concat(values);
Expand Down Expand Up @@ -409,7 +412,7 @@ exports.contain = function (ref, values, options) {
}
}
else {
const keys = Object.getOwnPropertyNames(ref);
const keys = Reflect.ownKeys(ref);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
const pos = values.indexOf(key);
Expand Down Expand Up @@ -483,13 +486,21 @@ exports.reach = function (obj, chain, options) {
options = { separator: options };
}

const path = chain.split(options.separator || '.');
const isChainArray = Array.isArray(chain);

exports.assert(!isChainArray || !options.separator, 'Separator option no valid for array-based chain');

const path = isChainArray ? chain : chain.split(options.separator || '.');
let ref = obj;
for (let i = 0; i < path.length; ++i) {
let key = path[i];
if (key[0] === '-' && Array.isArray(ref)) {
key = key.slice(1, key.length);
key = ref.length - key;

if (Array.isArray(ref)) {
const number = Number(key);

if (Number.isInteger(number) && number < 0) {
key = ref.length + number;
}
}

if (!ref ||
Expand Down
Loading

0 comments on commit b3b5134

Please sign in to comment.