Related
I am trying to filter a Javascript array of objects with nested objects with specific properties. I can filter the name, slug, website, launch year without any issues. But, I can not filter the category name (category.name) which is an object within the object. Why is filtering the category name not working?
var search = "qui"; // does not work (category.name)
// var search = "Sauer"; // works (name)
var data = [{ "name": "Sauer-Metz", "slug": "ab-laborum",
"website": "https://test.com", "launch_year": 2017, "category_id": 6,
"category": { "id": 6, "name": "qui", "slug": "qui" } } ];
var results = data.filter(company => [
'name', 'launch_year', 'website', 'category.name'
].some(key => String(company[key]).toLowerCase().includes(search.toLowerCase())));
console.log(results);
One way you can go about it is to have a value extractor like the one getKey below
const getKey = (value, key) => {
return key.split('.').reduce((acc, curr) => value[curr], '');
}
var results = data.filter(company => [
'name', 'launch_year', 'website', 'category.name'
].some(key => String(getKey(company, key)).toLowerCase().includes(search.toLowerCase())));
I believe you have to do a separate condition for this specific nested property, although there might be a cleaner way I don't see right now:
var results = data.filter(
(company) =>
["name", "launch_year", "website"].some((key) =>
String(company[key]).toLowerCase().includes(search.toLowerCase())
) ||
String(company["category"]["name"])
.toLowerCase()
.includes(search.toLowerCase())
);
Dot notation doesn't work like that.
const testCase1 = 'qui';
const testCase2 = 'Sauer';
const data = [
{
name: 'Sauer-Metz',
slug: 'ab-laborum',
website: 'https://test.com',
launch_year: 2017,
category_id: 6,
category: { id: 6, name: 'qui', slug: 'qui' },
},
];
const searchResults = (data, search) => {
return data.filter((item) => {
return (
item?.category?.name.toLowerCase().includes(search.toLowerCase()) ||
['name', 'launch_year', 'website'].some((key) => `${item[key]}`.toLowerCase().includes(search.toLowerCase()))
);
});
};
console.log('**CASE 1**')
console.log(searchResults(data, testCase1));
console.log('**CASE 2**')
console.log(searchResults(data, testCase2));
To use your approach you can convert 'category.name' to ['category','name'] and then use String(company[key[0]][key[1]])... whenever key is an array.
const search = "qui"; // does not work (category.name)
//const search = "Sauer"; // works (name)
const data = [{ "name": "Sauer-Metz", "slug": "ab-laborum", "website": "https://test.com", "launch_year": 2017, "category_id": 6, "category": { "id": 6, "name": "qui", "slug": "qui" } } ];
const results = data.filter(
company => [
'name', 'launch_year', 'website', ['category','name']
].some(
key =>
Array.isArray(key) ?
String(company[key[0]][key[1]]).toLowerCase().includes(search.toLowerCase()) :
String(company[key]).toLowerCase().includes(search.toLowerCase())
)
);
console.log(results);
I have array of objects,
if the name is xx then push xitems to that object and
if the name is yy then push yitems to that object
Below is the code tried , and also should not use spread operator
const result = [];
var ss=arrobj.forEach(function(e){
if(e.name === 'xx'){
result.push({id: e.id, name: e.name, country:e.country, others: xitems})
}
if(e.name === 'yy'){
result.push({id: e.id, name: e.name, country:e.country, others: yitems})
}
return result;
});
var arrobj =[
{id:1, name: "xx", country: "IN"},
{id:2, name: "yy", country: "MY"},
]
xitems =[
{title: "Finance", valid: true}
]
yitems =[
{title: "Sales", valid: true}
]
Expected Output
[
{id:1, name: "xx", country: "IN",
others:[
{title: "Finance", valid: true}
]
},
{id:2, name: "yy", country: "MY",
others: [
{title: "Sales", valid: true}
]
},
]
You should use .map for this.
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
const result = arrobj.map((item) => {
if (item.name === "xx") {
item.others = xitems;
} else if (item.name === "yy") {
item.others = yitems;
}
return item;
});
console.log(result);
Your code works, the only issue that I identified are.
There is no need to assign var ss with arrobj.forEach. Because Array.forEach donot return a value.
No need of return result; inside Array.forEach.
Also as an improvement you can simply assign the object with key others like Object.assign({}, e, { others: xitems }), rather than returning individual key value.
Working Fiddle
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
]
const xitems = [
{ title: "Finance", valid: true }
]
const yitems = [
{ title: "Sales", valid: true }
]
const result = [];
arrobj.forEach(function (e) {
if (e.name === 'xx') {
result.push(Object.assign({}, e, { others: xitems }))
}
if (e.name === 'yy') {
result.push(Object.assign({}, e, { others: yitems }))
}
});
console.log(result)
Variables are references to an object that has a value, variables do not store values. It is pointless to try to use a variable in that manner unless you have specific parameters. If you insist on a condition then you need to identify xitems and yitems by the objects values and/or properties or by the order they came in. If you have dynamic data how would you know what xitems or yitems really is?
The example below has been made reusable as long as you meet these requirements:
Must have an array of objects as a primary parameter.
Must have at least one array of objects for each object in the primary array. If there's more the rest will be ignored.
The secondary array of objects must be in the order you want then to end up as.
The second parameter is a rest parameter (not a spread operator, although I have no idea why OP does not want to use it). This will allow us to stuff in as many object arrays as we want.
const distOther = (main, ...oAs) => {...
Next we create an array of pairs from all of the secondary arrays
let others = oAs.map(sub => ['others', sub]);
// [['others', [{...}]], [['others', [{...}]], ...]
Then we turn our attention to the primary array. We'll work our way from the inside out. .map() each object as an array of pairs by Object.entries():
main.map((obj, idx) =>
// ...
Object.entries(obj)
// ...
// [{A: 1, B: 2}, {...}] => [[['A', 1], ['B', 2]], [[...], [...]]]
Then .concat() (a spead operator would be more succinct) each array of pairs with that of the secondary array of pairs corresponding to the current index (you'll need to wrap each secondary array in another array, so the return will level off correctly):
// main.map((obj, idx) =>
// ...
// Object.entries(obj)
.concat([others[idx]])));
// [[['A', 1], ['B', 2], ['others', [{...}]], [[...], [...], ['others', [{...}]]]
Finally we'll use Object.fromEntries() to convert each array of pairs into an object.
// main.map((obj, idx) =>
Object.fromEntries(
// Object.entries(obj)
// .concat([others[idx]])));
// [{'A': 1, 'B': 2, 'others': [{...}]},...]
const objArr =[
{id:1, name: "xx", country: "IN"},
{id:2, name: "yy", country: "MY"},
];
const x =[
{title: "Finance", valid: true}
]
const y =[
{title: "Sales", valid: true}
]
const distOther = (main, ...oAs) => {
let others = oAs.map(sub => ['others', sub]);
return main.map((obj, idx) =>
Object.fromEntries(
Object.entries(obj)
.concat([others[idx]])));
};
console.log(distOther(objArr, x, y));
I would choose a map based approach as well but without the if clauses which explicitly check for expected values of the mapped item's name property.
The approach instead utilizes map's 2nd thisArg parameter which gets applied as the mapper functions this context. Such an additional object can be provided as a map/index of custom key value pairs where key equals a mapped item's name.
Thus the mapper implementation features generic code, and due to the this binding it will be provided as function statement which makes it also re-usable and, if properly named, readable / comprehensible / maintainable too.
function assignBoundNamedValueAsOthers(item) {
// the bound key value pairs.
const index = this;
// create new object and assign, according to
// `item.name`, bound named value as `others`.
return Object.assign(
{},
item,
{ others: index[item.name] ?? [] },
);
}
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
const result = arrobj
.map(assignBoundNamedValueAsOthers, {
// each `key` equals an expected item's `name`.
xx: xitems,
yy: yitems,
});
console.log({
result,
arrobj,
xitems,
yitems,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
As one can see, the above implementation via Object.assign creates a new object from each mapped arrobj item. Thus the original item-references remains untouched / non mutated. It does not apply for the items of xitems and yitems since both array references are directly assigned each to its newly created others property. The above log does reflect this.
In case the goal was an entirely reference free data structure one needs to slightly change the Object.assign part of assignBoundNamedValueAsOthers ...
function assignBoundNamedValueAsOthers(item) {
// the bound key value pairs.
const index = this;
// create new object and assign, according to
// `item.name`, bound named value as `others`.
return Object.assign(
{},
item, {
others: (index[item.name] ?? [])
// dereference the `others` items as well.
.map(othersItem =>
Object.assign({}, othersItem)
)
},
);
}
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
const result = arrobj
.map(assignBoundNamedValueAsOthers, {
// each `key` equals an expected item's `name`.
xx: xitems,
yy: yitems,
});
console.log({
result,
arrobj,
xitems,
yitems,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
In case the OP does not need to care about immutability, the entire process then changes from a map task to a forEach task, where assignBoundNamedValueAsOthers does directly change/mutate each currently processed item of arrobj, thus forEach does not return any data but always the undefined value ...
function assignBoundNamedValueAsOthers(item) {
// the bound key value pairs.
const index = this;
// mutate the original reference of the currently
// processed `item` by directly assigning, according
// to `item.name`, the bound named value as `others`.
Object.assign(
item,
{ others: index[item.name] ?? [] },
);
// no explicit return value due to
// going to be used as a `forEach` task.
}
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
// mutates each item of `arrobj`.
arrobj.forEach(assignBoundNamedValueAsOthers, {
// each `key` equals an expected item's `name`.
xx: xitems,
yy: yitems,
});
console.log({
arrobj,
xitems,
yitems,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
I am having difficulties formatting some data. Currently, I receive data in the following structure.
[
{
"q1":"5",
"q2":[
"13",
"12",
],
"q3":"test",
}
]
I essentially need to modify this or even create a new object, that takes the following structure.
[
{
id: 1, //q1
answers: [
{
answer: '5',
},
],
},
{
id: 2, //q2
answers: [
{
answer: '13',
},
{
answer: '12',
},
],
},
{
id: 3, //q3
answers: [
{
answer: 'test',
},
],
},
];
So the id in the above would be obtained by remove the q and getting the number in the first data object. It would then have an answers array that would have an object for each answer.
I have been attempting this but have gotten lost. I don't know if I should use loops, mapping, filters etc. To be honest, the furthest I have got so far is obtaining the keys
var modified = data.map(function(item) {
return Object.keys(item)
})
I have created a JSFiddle where I have been attempting to do this.
Is there any way I can achieve the data I am after?
Many thanks
Please use map function.
const data = {
"q1":"5",
"q2":[
"13",
"12",
],
"q3":"test",
};
const result = Object.keys(data).map(key => {
let item = {id: key.substring(1), answers: []};
if(typeof data[key] === "string")
item.answers.push({answer: data[key]});
else
item.answers = data[key].map(val => ({answer: val}));
return item;
});
console.log(result)
const inputData = [
{
"q1":"5",
"q2":[
"13",
"12",
],
"q3":"test",
}
]
function answerMapper(objVal, id){
return Array.isArray(objVal)
?
{ id, answers: objVal.map(answer => ({ answer }))}
:
{ id, answers: [{answer: objVal }] }
}
function formatObject(obj){
return Object.keys(obj).map((k, i) => answerMapper(obj[k], i+1));
}
const result = inputData.map(obj => formatObject(obj));
// remove flatMap if your inputData has more than one entry
console.log(result.flatMap(x => x));
map over the first element of the data with Object.entries, grab the key and value, create a new answers array and return a new object.
const data = [{
"q1": "5",
"q2": [
"13",
"12",
],
"q3": "test",
}];
const out = Object.entries(data[0]).map(obj => {
const [ key, value ] = obj;
const id = Number(key[1]);
// If the the value is an array
// return a new array of mapped data
// Otherwise return an array containing
// one object
const answers = Array.isArray(value)
? value.map(el => ({ answer: el }))
: [{ answer: value }];
// Return the new object
return { id, answers };
});
console.log(out);
lets create a pure function which accepts the object in the array like so
const processObject = obj => Object.keys(obj).map(id => {
const answer = obj[id];
const answers = Array.isArray(answer) ? answer : [answer]
const answerObjectArray = answers.map(ans => ({
answer: ans
}));
return {
id: +id.substring(1),
answers: answerObjectArray
}
});
const dataArray = [{
"q1": "5",
"q2": [
"13",
"12",
],
"q3": "test",
}];
const output = processObject(dataArray[0]);
console.log(output);
here is my data of array1 :
[ { members: [ '60ee9148104cc81bec3b97ab' ] } ]
and here is array2:
[{"_id": "60ee9148104cc81bec3b97ab","username": "user1", "email": "user1#gmail.com"}, {"_id": "60ee917f767bd11d687326c7","username": "user2","email": "user2#gmail.com"}]
and I want to remove the object from my array2 which _id is equal to 60ee9148104cc81bec3b97ab
I have tried so far as
let user = await User.find({ _id: { $ne: req.user._id } })
const getNonfriends = (one) => {
user.splice(user.indexOf(one.members[0]), 1)
//user.filter(entry => entry._id !== one.members[0])
}
array1.map(getNonfriends)
filter or splice non of them bring my solutions.
The mistake you make is that you have similar objects, yet you search for identical objects.
var array1 = [ { members: [ '60ee9148104cc81bec3b97ab' ] } ];
var array2 = [{"_id": "60ee9148104cc81bec3b97ab","username": "user1", "email": "user1#gmail.com"}, {"_id": "60ee917f767bd11d687326c7","username": "user2","email": "user2#gmail.com"}]
for (var x of array1) {
for (var member of x.members) {
var objects = array2.filter(item => item._id === member)
for (var obj of objects) array2.splice(array2.indexOf(obj), 1)
}
}
We can use the Array.prototype.filter()
I think there is no need to find the index of the array and slice.
Also, we can use the Array.prototype.map, it is similar to use the filter function
const obj1 = [{
members: ['60ee9148104cc81bec3b97ab']
}]
const obj2 = [{
"_id": "60ee9148104cc81bec3b97ab",
"username": "user1",
"email": "user1#gmail.com"
}, {
"_id": "60ee917f767bd11d687326c7",
"username": "user2",
"email": "user2#gmail.com"
}]
const getAnswer = (obj2) => {
const res = obj2.filter(item => !obj1[0].members.includes(item._id))
return res;
}
console.log(getAnswer(obj2));
As you are working with IDs, I am going to assume that the second array cannot contain two items with the same ID.
If that assumption is correct, you could do:
const data = [{
"_id": "60ee9148104cc81bec3b97ab",
"username": "user1",
"email": "user1#gmail.com"
}, {
"_id": "60ee917f767bd11d687326c7",
"username": "user2",
"email": "user2#gmail.com"
}];
const filter = [{
members: ['60ee9148104cc81bec3b97ab']
}];
// SOLUTION 1: using Array.prototype.findIndex() and Array.prototype.splice()
const filteredDataA = [...data] // as splice() modifies the original array, I think it is safer to work on a copy of the original data
filter[0].members.forEach(id => {
const index = filteredDataA.findIndex(item => item._id === id); // find the index of the item with the same ID
if (index > -1) filteredDataA.splice(index, 1); // (if found) remove the item
})
console.log('SOLUTION 1: using Array.prototype.findIndex() and Array.prototype.splice()');
console.log(filteredDataA);
console.log('--------------------');
// SOLUTION 2: using array.prototype.filter() and array.prototype.includes()
const filteredDataB = data.filter(item => !filter[0].members.includes(item._id));
console.log('SOLUTION 2: using array.prototype.filter() and array.prototype.includes()');
console.log(filteredDataB);
console.log('--------------------');
I'd prefer "Solution 2" as I think is more readable
From the answers, I got the clue and this brings my desire results. here is one thing that array1 can have multiple elements so it needs to be mapped over with invoking the getNonfriends finction.
so,
let user = await User.find({ _id: { $ne: req.user._id } })
const getNonfriends = (one) => {
user = user.filter(item => !one.members.includes(item._id))
return user;
}
await array1.map(getNonfriends)
I have some data from a collection named "service" which has data like this:
let service_data = [
{
"name":"Service 1",
"price":60,
"resource_group_ids" :
["5d5e5dea99d9b75ff2f78dcd","5d5e85d329782914332368c8"]
},
{
"name":"Service 2",
"price":60,
"resource_group_ids" : ["5d5e5dea99d9b75ff2f7cfe"]
}
]
I want to push the resource_group_ids in a variable let say resource_groups. I don't want to loop resource_group_ids inside service_data.
You could use flatMap
const resource_groups = service_data.flatMap(o => o.resource_group_ids)
const service_data = [{"name":"Service 1","price":60,"resource_group_ids":["5d5e5dea99d9b75ff2f78dcd","5d5e85d329782914332368c8"]},{"name":"Service 2","price":60,"resource_group_ids":["5d5e5dea99d9b75ff2f7cfe"]}]
const resource_groups = service_data.flatMap(o => o.resource_group_ids)
console.log(resource_groups)
If flatMap is not supported, use concat and spread syntax to merge the 2D array returned by map
const resource_groups = [].concat(...service_data.map(o=> o.resource_group_ids))
You can use flatMap
const extractFlat = (arr, key) => {
return arr.flatMap(e => e[key]);
};
let service_data = [
{
"name":"Service 1",
"price":60,
"resource_group_ids" :
["5d5e5dea99d9b75ff2f78dcd","5d5e85d329782914332368c8"]
},
{
"name":"Service 2",
"price":60,
"resource_group_ids" : ["5d5e5dea99d9b75ff2f7cfe"]
}
]
const resource_group_ids = extractFlat(service_data, 'resource_group_ids');
console.log(resource_group_ids);
or reduce on older environments
const extractFlat = (arr, key) => {
return arr.reduce((acc, x) => acc.concat(x[key]), []);
};
You can achieve this with .map(), .join() and .split()
const service_data = [
{
name: 'Service 1',
price: 60,
resource_group_ids:
['5d5e5dea99d9b75ff2f78dcd', '5d5e85d329782914332368c8']
},
{
name: 'Service 2',
price: 60,
resource_group_ids: ['5d5e5dea99d9b75ff2f7cfe']
},
];
const result = service_data.map((r) => { return r.resource_group_ids;
}).join(',').split(',');
console.log(result);
Using .reduce:
let service_data = [{
"name": "Service 1",
"price": 60,
"resource_group_ids": ["5d5e5dea99d9b75ff2f78dcd", "5d5e85d329782914332368c8"]
},
{
"name": "Service 2",
"price": 60,
"resource_group_ids": ["5d5e5dea99d9b75ff2f7cfe"]
}
]
const resource_groups = service_data.reduce(function(result, item) {
result.concat(item.resource_group_ids)
}, [])
console.log(resource_groups)