How do I exclude null values when merging objects by key? - javascript

Lets say I have an array of objects in Javascript:
id: 1,
name: 'Snowy',
},
{
id: 2,
name: 'Quacky',
age: 13
},
{
id: 3,
name: 'Snowy',
age: 1,
},
{
name: null
}
]
I have created a function to arrange them by a key:
const filter =
(pets, key) =>
_(pets)
.filter(pets => _.has(pets, key))
.groupBy(key)
.value()
Although, the output of this function when called persists the null entry:
{
Snowy: [ { id: 1, name: 'Snowy' }, { id: 3, name: 'Snowy', age: 1 } ],
Quacky: [ { id: 2, name: 'Quacky', age: 13 } ],
null: [ { name: null } ]
}
Is there I way I can filter out any of the null values here?

You could try by changing the method you use in in your predicate function from _.has to _.get.
So, instead of checking if a path/ key exist in an object, you instead check the value of a path/ key within an object it its null or not.
note: you might also want to check for falsy values here (such as undefined) instead of just null, since _.get returns undefined if the path/ key does not exist
const pets = [{
id: 1,
name: 'Snowy',
},
{
id: 2,
name: 'Quacky',
age: 13
},
{
id: 3,
name: 'Snowy',
age: 1,
},
{
name: null
}
];
const filter =
(pets, key) =>
_(pets)
.filter(pets => _.get(pets, key) !== null)
.groupBy(key)
.value();
console.log(filter(pets, 'name'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

Why not first filter the array for objects that have a truthy name? For example someArray.filter(a => a.name)...group()...

Related

How do I check that the correct objects are being returned via a function (expect function returns [Function Anonymous])?

I have a function:
const sort =
(pets,attribute) =>
_(pets)
.filter(pets=> _.get(pets, attribute) !== null)
.groupBy(attribute)
.value()
Some data:
const pets= [{
id: 1,
name: 'snowy',
},
{
id: 2,
name: 'quacky',
},
{
id: 3,
name: 'snowy',
age: 5,
},
{
id: null,
name: null,
age: null
}
]
const attribute = 'name'
I am currently trying to write some Jest unit tests for this, that tests if the function returns the correct resultant object after being sorted based off an attribute.
The result of:
sort(pets,attribute) is something like this:
{
snowy: [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5} ],
quacky: [ { id: 2, name: 'quacky' } ]
}
Is there a way I can do a expect to match the two objects snowy and quacky here?
The thing I want to test for is that the objects are being correctly grouped by the key.
I've tried using something like
const res = sort(users,key)
expect(res).toEqual(
expect.arrayContaining([
expect.objectContaining({'snowy' : [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5 } ]},
expect.objectContaining({'quacky' : [ { id: 2, name: 'quacky' } ]}))
])
)
which doesn't seem to work, the received output seems to output:
Expected: ArrayContaining [ObjectContaining {"snowy": [{"id": 1, "name": "snowy"}, {"age": 5, "id": 3, "name": "snowy"}]}]
Received: [Function anonymous]
I am unsure what the best method to test this kind of function is either so advice on that would be appreciated.
If this is what your arrangeBy() returns:
{
snowy: [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5} ],
quacky: [ { id: 2, name: 'quacky' } ]
}
Then you can just do:
const expected = {
snowy: [ { id: 1, name: 'snowy' }, { id: 3, name: 'snowy', age: 5} ],
quacky: [ { id: 2, name: 'quacky' } ]
}
const res = arrangeBy(users,key)
expect(res).toEqual(expected)
But looking at your Error message I guess you have something else mixed up. In the beginning you listed the implementation of a sort function which seems to not be used in the test. Where is arrangeBy coming from now.
Please provide more code examples.

Find Duplicate Object in List and Add Parameters

I am trying to find duplicate objects in a list of objects and add new parameters to the duplicate one.
Below snipped code is what I implemented so far. The problem is that it adds desired parameters to every object in the list.
const list = [{
id: 1,
name: 'test1'
},
{
id: 2,
name: 'test2'
},
{
id: 3,
name: 'test3'
},
{
id: 2,
name: 'test2'
}
];
const newList = list.reduce(
(unique, item) => (unique.includes(item) ? unique : [...unique, {
...item,
duplicated: true,
name: `${item.name}_${item.id}`
}]), []
);
console.log(newList);
Since there are two duplicate objects by id, the duplicated one should have duplicated and new name parameters. What part is wrong in my implementation?
By using findIndex method:
const list = [{
id: 1,
name: 'test1'
},
{
id: 2,
name: 'test2'
},
{
id: 3,
name: 'test3'
},
{
id: 2,
name: 'test2'
}
];
const newList = list.reduce(
(unique, item) => (unique.findIndex(x => x.id === item.id) > -1 ? [...unique, {
...item,
duplicated: true,
name: `${item.name}_${item.id}`
}] : [...unique, item]), []);
console.log(newList);
It can be written simply:
const
list = [
{ id: 1, name: 'test1' },
{ id: 2, name: 'test2' },
{ id: 3, name: 'test3' },
{ id: 2, name: 'test2' }
],
uniqueList = list.reduce((arr, { id, name }) =>
arr.concat({
id,
name,
...arr.some(item => id === item.id) && { duplicate: true, name: `${name}_${id}` }
}), []);
console.log(uniqueList);
The problem was that when you called includes you were actually looking for an object whose pointer exists in the array.
In order to find an object which has property are the same as a requested property, you have no choice but to use functions such as some or every that is different from includes - you can send them a callback and not just an object.

How to groupBy an object key inside nested array of objects?

I have a nested array of objects and I want to groupBy id and form a new array. Here's my array:
mainArray = [
{ name: 'a',age: 10, company: [ { desc: 'test1' , id: 6 }, { desc: 'testa' , id: 10 }] },
{ name: 'b',age: 20, company: [ { desc: 'test2' , id: 30 }] },
{ name: 'c',age: 40, company: [ { desc: 'test3' , id: 10 }, { desc: 'testc' , id: 30 }] }
]
I can flatten the entire array but it doesn't seem like the right way to do it.
My new array should look like something like this:
result = [
comapny_6: [
{
name: 'a',
age: 10,
desc: 'test1'
},
],
comapny_10: [
{
name: 'a',
age: 10,
desc: 'testa'
},
{
name: 'c',
age: 40,
desc: 'test3'
}
],
company_30 :[
{
name: 'b',
age: 20,
desc: 'test2'
},
{
name: 'c',
age: 40,
desc: 'testc'
}
]
]
I am open to suggestions on how the final data structure should look like. The bottom line is I want groupBy id so that I have information about each company separated out.
You can use reduce to loop thru the array and construct the desired object output. Use forEach to loop thru company
var mainArray = [{"name":"a","age":10,"company":[{"desc":"test1","id":6},{"desc":"testa","id":10}]},{"name":"b","age":20,"company":[{"desc":"test2","id":30}]},{"name":"c","age":40,"company":[{"desc":"test3","id":10},{"desc":"testc","id":30}]}];
var result = mainArray.reduce((c, {name,age,company}) => {
company.forEach(({id,desc}) => (c["company_" + id] = c["company_" + id] || []).push({name,age,desc}));
return c;
}, {});
console.log(result);
You can first create a 1D array using flatMap() and then use reduce() to group
const mainArray = [
{ name: 'a',age: 10, company: [ { desc: 'test1' , id: 6 }, { desc: 'testa' , id: 10 }] },
{ name: 'b',age: 20, company: [ { desc: 'test2' , id: 30 }] },
{ name: 'c',age: 40, company: [ { desc: 'test3' , id: 10 }, { desc: 'testc' , id: 30 }] }
]
const flat = mainArray.flatMap(({company,...rest}) => company.map(a => ({...rest,...a})));
const res = flat.reduce((ac,{id,...rest}) => ((ac[`company_${id}`] || (ac[`company_${id}`] = [])).push(rest),ac),{})
console.log(res)
Explanation
reduce() is method with returns a single value after iterating through all the array. The accumulator i.e ac in above case is set to empty object {}(which is the second argument passed to function)
In each iteration we return the updated accumulator which becomes ac for next iteration. So what we return from function is following expression
((ac[`company_${id}`] || (ac[`company_${id}`] = [])).push(rest),ac)
ac[company_${id}] is using Bracket Notation which takes an expression company_${id}. It is same as
ac["company_" + id]
The above line checks if ac[company_${id}] exists in the ac then push() rest to the it.
If ac[company_${id}] is not created yet they set it to empty array [] then push() the rest to it.
The last part uses comma operator
((ac[`company_${id}`] || (ac[`company_${id}`] = [])).push(rest),ac)
The above whole expression will evaluate to the last value separated by comma , which is ac. So in each iteration we are pushing rest to the respective array and returning ac it the end. The code is equivalent to
const res = flat.reduce((ac,{id,...rest}) => {
//check if company id doesnot exist as key in ac then set it empty array
if(!ac[`company_${id}`]) ac[`company_${id}`] = [];
//push rest(which will be an object with all the keys expect id)
ac[`company_${id}`].push(rest)
//at last return ac
return ac;
})
You can achieve this with Array.reduce and inside it with an Array.forEach over the array of companies like this:
let data = [ { name: 'a',age: 10, company: [ { desc: 'test1' , id: 6 }, { desc: 'testa' , id: 10 }] }, { name: 'b',age: 20, company: [ { desc: 'test2' , id: 30 }] }, { name: 'c',age: 40, company: [ { desc: 'test3' , id: 10 }, { desc: 'testc' , id: 30 }] } ]
let result = data.reduce((r,{ name, age, company }) => {
company.forEach(({ id, desc }) =>
r[`company_${id}`] = (r[`company_${id}`] || []).concat({ name, age, desc }))
return r
}, {})
console.log(result)

add an object to an array if it doesn't have the same key with one of the objects in the array

I'm using Lodash. I have the array below:
const array = [{id:1,name:a},{id:2,name:b},{id:3,name:c},{id:4,name:d},{id:5,name:e}];
and I'm about to add another object to this array but before that, I need to check if the new object's name is already in the array or not and if there is one with the name I won't add the new object anymore.
I know some ways to do it, for instance, a loop with _.map, but want to make sure if there is an easier way.
You could use Lodash's some which if provided with an appropriate predicate e.g. (item => item.name === newName) will return a boolean indicating whether or not the item already exists (in this case, true would mean the name already exists). The benefit of using this over other iterating methods is that it will stop as soon as it finds one that returns true resulting in better performance.
With native javascript , you can use findIndex, this will return the index of the object where the name matches. If it returns -1 then there is no such object with same name. In that case update the array.
const array = [{
id: 1,
name: 'a'
}, {
id: 2,
name: 'b'
}, {
id: 3,
name: 'c'
}, {
id: 4,
name: 'd'
}, {
id: 5,
name: 'e'
}];
let newObjToAdd = {
id: 1,
name: 'z'
};
let newObjNotToAdd = {
id: 1,
name: 'a'
}
function updateArray(obj) {
let k = array.findIndex((item) => {
return item.name === obj.name;
})
if (k === -1) {
array.push(obj)
} else {
console.log('Array contains object with this name')
}
}
updateArray(newObjToAdd);
console.log(array)
updateArray(newObjNotToAdd);
You don't need lodash for some. You get that with native JS too (ES6):
const array = [{id:1,name:'a'},{id:2,name:'b'},{id:3,name:'c'},{id:4,name:'d'},{id:5,name:'e'}];
console.log(array.some(e => e.name === 'a'));
if (!array.some(e => e.name === 'z')) {
array.push({id: 5, name: 'z'});
}
console.log(array);
Doing this with lodash is few chars shorter but here is how you could do it with ES6 and Array.some:
const array = [{ id: 1, name: "A" }, { id: 2, name: "B" }, { id: 3, name: "C" }, { id: 4, name: "D" }, { id: 5, name: "C" }];
const maybeUpdate = (arr, obj) => {
if(!array.some(x => x.id == obj.id))
array.push(obj)
}
maybeUpdate(array, {id: 2, name: "F"}) // id exists wont insert
maybeUpdate(array, {id: 12, name: "F"}) // will insert
console.log(array)
Same idea with lodash and _.some would be:
const array = [{ id: 1, name: "A" }, { id: 2, name: "B" }, { id: 3, name: "C" }, { id: 4, name: "D" }, { id: 5, name: "C" }];
const maybeUpdate = (arr, obj) => {
if(!_.some(array, {id: obj.id}))
array.push(obj)
}
maybeUpdate(array, {id: 2, name: "F"}) // id exists wont insert
maybeUpdate(array, {id: 12, name: "F"}) // will insert
console.log(array)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Note that you could also use various other ways to get the same result. Array.find or _.find would work as well since all you have to do is to check if there was a hit:
const maybeUpdate = (arr, obj) => {
if(!_.find(array, {id: obj.id})) // or if(!array.find(x => x.id == obj.id))
array.push(obj)
}

Lodash check if any object in array contains id match from another array

I have one user array like:
var users = [{
id: 1,
name: 'ABC',
isDisplay: true
}, {
id: 2,
name: 'XYZ',
isDisplay: true
}, {
id: 3,
name: 'JKL',
isDisplay: true
}];
And another array selectedUsers which contains some object from above array like:
var selectedUsers = [{
id: 1,
name: 'ABC'
},
{
id: 3,
name: 'JKL'
}];
Not with lodash, i want to indentify which object are present in second array with matching its ID.
_.each(users, (_u) => {
if(selectedUsers.includes(_u)) {
_u.isDisplay = false;
} else {
_u.isDisplay = true;
}
});
I have tried to match whole object with includes but it dint work, because i am using angularjs, so angular put some $$hashkey with object, so it will not match, Is there any other way to do this.
var users = [{
id: 1,
name: 'ABC',
isDisplay: true
}, {
id: 2,
name: 'XYZ',
isDisplay: true
}, {
id: 3,
name: 'JKL',
isDisplay: true
}];
var selectedUsers = [{
id: 1,
name: 'ABC'
},
{
id: 3,
name: 'JKL'
}];
var intersection = _.intersectionBy(users, selectedUsers, 'id');
console.log(intersection);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js"></script>
Create a Set of selected ids (selectedUsersIds). Iterate the users array with Array#forEach, assign the value of isDisplay by checking if the id exists in the selectedUsersIds set:
const users = [{"id":1,"name":"ABC","isDisplay":true},{"id":2,"name":"XYZ","isDisplay":true},{"id":3,"name":"JKL","isDisplay":true}];
const selectedUsers = [{"id":1,"name":"ABC"},{"id":3,"name":"JKL"}];
const selectedUsersIds = new Set(selectedUsers.map(({ id }) => id));
users.forEach((u) => u.isDisplay = selectedUsersIds.has(u.id));
console.log(users);

Categories

Resources