Related
I am writing some daily challenges for my coding bootcamp and I am running into an issue on one problem. I wrote a function that combines objects and it works correctly. Here is what the problem prompt is
Prompt: Write a function named mergeObjects that accepts at least two objects as arguments, merges the properties of the second through n objects into the first object, then finally returns the first object. If any objects have the same property key, values from the object(s) later in the arguments list should overwrite earlier values.
Examples:
mergeObjects({}, {a: 1}); //=> {a: 1} (same object as first arg)
mergeObjects({a: 1, b: 2, c: 3}, {d: 4}); //=> {a: 1, b: 2, c: 3, d: 4}
mergeObjects({a: 1, b: 2, c: 3}, {d: 4}, {b: 22, d: 44}); //=> {a: 1, b: 22, c: 3, d: 44
My function
function mergeObjects(...obj) {
let obj1 = {}
obj.forEach(element => {
obj1 = {...obj1, ...element}
});
return obj1
}
Solution function
function mergeObjects1(target, ...objects) {
objects.forEach(function (obj) {
// using ES2015's 'for in' loop
for (var key in obj) {
target[key] = obj[key]
}
})
return target
}
In my eyes these two functions provide the same result. However, when I run my code through the jest test they created it fails on the first test. But the solution they provide, does not fail. The jest test is below.
describe('15-mergeObjects', function () {
it('returns same object', function () {
var obj = {}
expect(mergeObjects(obj, { a: 1 })).toBe(obj)
})
it('adds additional properties', function () {
expect(mergeObjects({ a: 1, b: 2, c: 3 }, { d: 4 })).toEqual({
a: 1,
b: 2,
c: 3,
d: 4
})
})
it('merges props from left to right', function () {
expect(
mergeObjects({ a: 1, b: 2, c: 3 }, { d: 4 }, { b: 22, d: 44 })
).toEqual({ a: 1, b: 22, c: 3, d: 44 })
})
})
describe('15-mergeObjects', function () {
it('returns same object', function () {
var obj = {}
expect(mergeObjects1(obj, { a: 1 })).toBe(obj)
})
it('adds additional properties', function () {
expect(mergeObjects1({ a: 1, b: 2, c: 3 }, { d: 4 })).toEqual({
a: 1,
b: 2,
c: 3,
d: 4
})
})
it('merges props from left to right', function () {
expect(
mergeObjects1({ a: 1, b: 2, c: 3 }, { d: 4 }, { b: 22, d: 44 })
).toEqual({ a: 1, b: 22, c: 3, d: 44 })
})
})
Can anyone provide an explanation as to why the solution function passes while my function does not?
While the results look the same, the two functions are slightly different.
In your function, you are creating a new object and then adding to it all of the required properties.
In the solution function, they are adding properties to the original target object and then returning it.
The returned objects from both functions will have the same keys and the same values, but different references, so they are not considered the same object in JavaScript.
In the test
expect(mergeObjects1(obj, { a: 1 })).toBe(obj)
.toBe() checks whether two objects are the same (identity), therefore it fails the case of your function.
Note that there is a different test, .toEqual(), which checks whether two objects have the same keys and the same values (but possibly different references). Your function would pass this test
expect(mergeObjects1(obj, { a: 1 })).toEqual(obj)
i know there has many answer for unique array
but they can't handle with array of array
what i want is
source array
[
1,
0,
true,
undefined,
null,
false,
['a', 'b', 'c'],
['a', 'b', 'c'],
['a', 'c', 'b'],
{ a: { b: 2 } },
{ a: { b: 2 } },
{ a: { b: 3 } },
{ a: { b: undefined } },
{ a: { } },
{ a: { b: 3, c: undefined } },
]
the return
[
1,
0,
true,
undefined,
null,
false,
['a', 'b', 'c'],
['a', 'c', 'b'],
{ a: { b: 2 } },
{ a: { b: 3 } },
{ a: { b: undefined } },
{ a: { } },
{ a: { b: 3, c: undefined } },
]
arr-unique can handle object[], but can't handle array of array
Set can't too
fail code
console.log(array_unique(data));
console.log([...new Set(data)]);
console.log(data.filter(function (el, index, arr)
{
return index == arr.indexOf(el);
}));
===================
update
i create a module for this array-hyper-unique, but didn't use json stringify because it has a bug when valuse is regexp
One easy method would be to stringify the arrays and objects in order to identify duplicates:
const input = [
1,
true,
['a', 'b', 'c'],
['a', 'b', 'c'],
{ a: { b: 2 } },
{ a: { b: 2 } },
{ a: { b: 3 } },
{ a: { b: 3, c: undefined } },
];
const outputSet = new Set();
const stringifiedObjs = new Set();
input.forEach(item => {
if (typeof item !== 'object') outputSet.add(item);
else {
// replace undefineds with something, else they'll be ignored by JSON.stringify:
const stringified = JSON.stringify(
item,
(k, v) => v === undefined ? 'undefined-value' : v
);
if (!stringifiedObjs.has(stringified)) {
outputSet.add(item);
stringifiedObjs.add(stringified)
}
}
});
console.log([...outputSet]);
Try by converting elements to string using JSON.stringify and use indexOf to push these elements to another array,only if the another array does not contain this element. Then again use map & JSON.parse to convert string to the original format
var data = [
1,
true, ['a', 'b', 'c'],
['a', 'b', 'c'],
{
a: {
b: 2
}
},
{
a: {
b: 2
}
},
{
a: {
b: 3
}
},
]
// Create a new array of only string
// map will give new array and JSON.stringify will convert elements to string
var newData = data.map(function(item) {
return JSON.stringify(item)
})
//An empty array which will contain only unique values
var uniques = [];
// loop over the array of stirngs and check
//if that value is present in uniques array
//if not then push the element
newData.forEach(function(item) {
if (uniques.indexOf(item) === -1) {
uniques.push(item)
}
});
//Convert array of string to json
var parsedArr = uniques.map(function(item) {
return JSON.parse(item)
});
console.log(parsedArr)
The reason you method does not work, is because the first ['a', 'b', 'c'], and the second ['a', 'b', 'c'] are different objects, as are the first and second instances of { a: { b: 2 } }.
Because of this, even though you add them to Set, they will be considered non-equivalent to each other, and therefore, not be filtered for uniqueness.
It seems you want to get a unique array based on the absolute values in each object. One easy way to do this is to use the ES6 Map like so:
function uniq(arr) {
var uniqMap = new Map()
arr.forEach(element => {
uniqMap.set(JSON.stringify(element), element)
})
return [...uniqMap.values()]
}
You can then get the result you are looking for:
uniq(data)
//Result: [ 1, true, [ 'a', 'b', 'c' ], { a: { b: 2 } }, { a: { b: 3 } } ]
You could take a recursive approach for objects and check the values.
function check(a, b) {
if (!a || typeof a !== 'object') {
return a === b;
}
var keys = Object.keys(a);
return keys.length === Object.keys(b).length
&& keys.every(k => k in b && check(a[k], b[k]));
}
var array = [1, 0, true, undefined, null, false, ['a', 'b', 'c'], ['a', 'b', 'c'], ['a', 'c', 'b'], { a: { b: 2 } }, { a: { b: 2 } }, { a: { b: 3 } }, { a: { b: undefined } }, { a: {} }, { a: { b: 3, c: undefined } }],
unique = array.reduce((r, b) => (r.some(a => check(a, b)) || r.push(b), r), []);
console.log(unique);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have javascript objects which looks similar to this:
{
id: 43,
name: 'ajajaj'
nestedObj1: {
id: 53,
name: 'ababab'
foo: 'xxxx'
nestedObj2: {
id: 90,
name: 'akakaka'
foo2: 'sdsddd'
surname: 'sdadasd'
nestedObj3: {
id: ..
name: ..
...
},
objectsArr: [
{id: .., name: ..., nestedOb4: {...} },
{id: .., name: ..., nestedOb4: {...} }
]
},
foo0: ...
}
name: 'blabla'
}
Each objects which I have, has property id and name. My goal is pick all properties in first level, so properties of object with id 43 I pick all, and from every nested objects, I just pick properties id and name, and another will be thrown away.
I am using in project lodash, so I found function pick, but I don't know how to use this function for nested objects. Also note that nested objects could be also in array. Exist some elegant way for that please? Thanks in advice.
pick function of lodash is able to fetch nested property like so:
_.pick(obj, [
'id',
'name',
'nestedObj1.id',
'nestedObj1.name',
'nestedObj1.nestedObj2.id',
'nestedObj1.nestedObj2.name',
'nestedObj1.nestedObj2.nestedObj3.id',
'nestedObj1.nestedObj2.nestedObj3.name',
'nestedObj1.nestedObj2.objectsArr[0].id',
'nestedObj1.nestedObj2.objectsArr[0].name',
'nestedObj1.nestedObj2.objectsArr[0].nestedObj4.id',
'nestedObj1.nestedObj2.objectsArr[0].nestedObj4.name',
'nestedObj1.nestedObj2.objectsArr[1].id',
'nestedObj1.nestedObj2.objectsArr[1].name',
'nestedObj1.nestedObj2.objectsArr[1].nestedObj4.id',
'nestedObj1.nestedObj2.objectsArr[1].nestedObj4.name',
])
The problem is, you must know the array length to pick all objects in them.
To mitigate this, you can use this function to iterate over arrays:
const _ = require('lodash')
const pickExtended = (object, paths) => {
return paths.reduce((result, path) => {
if (path.includes("[].")) {
const [collectionPath, itemPath] = path.split(/\[]\.(.+)/);
const collection = _.get(object, collectionPath);
if (!_.isArray(collection)) {
return result;
}
const partialResult = {};
_.set(
partialResult,
collectionPath,
_.map(collection, item => pickExtended(item, [itemPath]))
);
return _.merge(result, partialResult);
}
return _.merge(result, _.pick(object, [path]));
}, {});
};
You can then get the result you want with:
pickExtended(obj, [
'id',
'name',
'nestedObj1.id',
'nestedObj1.name',
'nestedObj1.nestedObj2.id',
'nestedObj1.nestedObj2.name',
'nestedObj1.nestedObj2.nestedObj3.id',
'nestedObj1.nestedObj2.nestedObj3.name',
'nestedObj1.nestedObj2.objectsArr[].id',
'nestedObj1.nestedObj2.objectsArr[].name',
'nestedObj1.nestedObj2.objectsArr[].nestedObj4.id',
'nestedObj1.nestedObj2.objectsArr[].nestedObj4.name',
])
Here is the associated mocha test:
describe("pickExtended", () => {
const object = {
a: 1,
b: "2",
c: { d: 3, e: 4 },
f: [
{ a: 11, b: 12, c: [{ d: 21, e: 22 }, { d: 31, e: 32 }] },
{ a: 13, b: 14, c: [{ d: 23, e: 24 }, { d: 33, e: 34 }] }
],
g: [],
h: [{ a: 41 }, { a: 42 }, { a: 43 }]
};
it("should pick properties in nested collection", () => {
expect(
pickExtended(object, ["a", "c.d", "f[].c[].d", "g[].a", "b[].a", "h[]"])
).to.deep.equal({
a: 1,
c: { d: 3 },
f: [{ c: [{ d: 21 }, { d: 31 }] }, { c: [{ d: 23 }, { d: 33 }] }],
g: []
});
});
});
Keep in mind that no performance test was done on this. It might get slow for big object.
You can do this using recursion, as long as your objects do have an end to the levels of nesting. Otherwise, you run the risk of causing a stack limit error.
function pickProperties(obj) {
var retObj = {};
Object.keys(obj).forEach(function (key) {
if (key === 'id' || key === 'name') {
retObj[key] = obj[key];
} else if (_.isObject(obj[key])) {
retObj[key] = pickProperties(obj[key]);
} else if (_.isArray(obj[key])) {
retObj[key] = obj[key].map(function(arrayObj) {
return pickProperties(arrayObj);
});
}
});
return retObj;
}
I'm trying to validate that an array of objects like this:
[
{
a: 1,
b: 2,
c: 3
},
{
a: 4,
b: 5,
c: 6
},
...
]
contains at least one object with both { a: 1 } and { c: 3 }:
I thought I could do this with chai-things, but I don't know all the properties of the object to be able to use
expect(array).to.include.something.that.deep.equals({ ??, a: 1, c: 3});
and contain.a.thing.with.property doesn't work with multiple properties :/
What's the best way to test something like this?
The desired solution seems to be something like:
expect(array).to.include.something.that.includes({a: 1, c: 3});
I.e. array contains an item which includes those properties. Unfortunately, it appears to not be supported by chai-things at the moment. For the foreseeable future.
After a number of different attempts, I've found that converting the original array makes the task easier. This should work without additional libraries:
// Get items that interest us/remove items that don't.
const simplifiedArray = array.map(x => ({a: x.a, c: x.c}));
// Now we can do a simple comparison.
expect(simplifiedArray).to.deep.include({a: 1, c: 3});
This also allows you to check for several objects at the same time (my use case).
expect(simplifiedArray).to.include.deep.members([{
a: 1,
c: 3
}, {
a: 3,
c: 5
}]);
Most elegant solution I could come up with (with the help of lodash):
expect(_.some(array, { 'a': 1, 'c': 3 })).to.be.true;
Most straight forward way I can think of is:
const chai = require('chai');
const expect = chai.expect;
chai.use(require('chai-like'));
chai.use(require('chai-things'));
expect([
{
a: 1,
b: 2,
c: 3,
},
{
a: 4,
b: 5,
c: 6,
},
]).to.be.an('array').that.contains.something.like({ a: 1, c: 3 });
Seems like the chai-subset plugin from chai seems to have done the trick. Here is something I have working:
const chai = require('chai');
const chaiSubset = require('chai-subset');
chai.use(chaiSubset);
const expect = chai.expect;
expect([ { type: 'text',
id: 'name',
name: 'name',
placeholder: 'John Smith',
required: 'required' },
{ type: 'email',
id: 'email',
name: 'email',
placeholder: 'example#gmail.com',
required: 'required' },
{ type: 'submit' } ]).containSubset([{ type: 'text', type: 'email', type: 'submit' }]);
Solution without third libraries or plugins:
let array = [
{ a: 1, b: 2, c: 3 },
{ a: 4, b: 5, c: 6 }
];
let match = array.map(({a, c}) => ({a, c})).to.deep.include({ a:1, c:3});
You could write your own function to test the array. In this example you pass in the array and the object containing the relevant key/value pairs:
function deepContains(arr, search) {
// first grab the keys from the search object
// we'll use the length later to check to see if we can
// break out of the loop
var searchKeys = Object.keys(search);
// loop over the array, setting our check variable to 0
for (var check = 0, i = 0; i < arr.length; i++) {
var el = arr[i], keys = Object.keys(el);
// loop over each array object's keys
for (var ii = 0; ii < keys.length; ii++) {
var key = keys[ii];
// if there is a corresponding key/value pair in the
// search object increment the check variable
if (search[key] && search[key] === el[key]) check++;
}
// if we have found an object that contains a match
// for the search object return from the function, otherwise
// iterate again
if (check === searchKeys.length) return true;
}
return false;
}
deepContains(data, { a: 4, c: 6 }); // true
deepContains(data, { a: 1, c: 6 }); // false
DEMO
I am trying to find a library able to say if an object B includes all property and values of an object A (and not the opposite !).
It means 2 things that the librairies i found do not handle:
The object B could have more keys than object A, and it could be
true.
The object B could have more items in its arrays than object
A, and it could be true.
This is basically what the method _.difference() of lodash does, but only for items in arrays.
i found interesting libraries like deep-diff, but anything for my need.
I could code something doing the job, but i am convinced other peoples met this need before.
Here is an example with 2 objects:
var A = {
name: 'toto',
pets: [ { name: 'woof', kind: 'dog' } ]
};
var B = {
name: 'toto',
pets: [ { name: 'gulp', kind: 'fish' }, { name: 'woof', kind: 'dog' } ],
favoriteColor: 'blue'
};
Here, A includes B since we can find in B every properties and values of A.
But librairies like diff would say no, since this is not the first but the second item of "pets" which is equal to A, and B has an additionnal property "favoriteColor".
Do you know a librairy able to do this kind of comparison?
You could use a modified version of the deepCompare that was linked in the comments. Really you just need to get past the keys length comparison, it seems.
// where b has all of a
function deepHas(a, b) {
if (typeof a !== typeof b) {
return false;
}
if (typeof a !== 'object') {
return a === b;
}
// you may need to polyfill array higher-order functions here
if(Array.isArray(a) && Array.isArray(b)) {
return a.every(function(av) {
return b.indexOf(av) !== -1;
});
}
if (Object.keys(a).length > Object.keys(b).length) {
return false;
}
for (var k in a) {
if (!(k in b)) {
return false;
}
if (!deepHas(a[k], b[k])) {
return false;
}
}
return true;
}
var a1 = {
foo: ['a', 'b', 'c'],
bar: 'bar',
baz: {
baz: {
baz: 'wee'
}
}
};
var b1 = {
foo: ['a', 'b', 'c', 'd'],
bar: 'bar',
baz: {
baz: {
baz: 'wee',
whatever: 'wat'
}
},
ok: 'test'
};
console.log('b1 has all of a1', deepHas(a1, b1));
var a2 = {
foo: ['a', 'b', 'c'],
bar: 'bar',
baz: {
baz: {
baz: 'wee'
}
}
};
var b2 = {
foo: ['a', 'b', 'c'],
baz: {
baz: {
baz: 'wee'
}
}
};
console.log('b2 does not have all of a2', !deepHas(a2, b2));
console.log('["a","b"] has ["b"]', deepHas(["b"], ["a","b"]));
console.log('{foo: ["a","b"]} has {foo: ["b"]}', deepHas({ foo: ["b"] }, { foo:["a","b"] }));