I'm having issues understanding how to best manipulate an array to get the data I want. From the research I've done, there's multiple ways, but I'm unclear on which is most optimized.
I want to display a simple list, with the items broken down by country, then state, then organized alphabetically by city. The array is formatted as follows:
[
{
id: 1,
name: "Place 1",
state: "Florida",
city: "Boca Raton",
country: "US",
},
{
id: 2,
name: "Place 2",
state: "Florida",
city: "Daytona Beach",
country: "US",
},
{
id: 3,
name: "Place 3",
state: "Kansas",
city: "Lenexa",
country: "US",
},
{
id: 4,
name: "Place 4",
state: "Harju",
city: "Tallinn",
country: "EE",
},
]
An example of the desired outcome is:
US
Florida
Place 1
Place 2
Kansas
Place 3
EE
Harju
Place 4
I see a lot of people saying to utilize ES6 for this, but I'm not sure the best way to approach it. Manipulate the original array response? Is there some way I can loop through them?
Here's an approach that only requires a single loop.
const data = [];
let result = {};
data.forEach(({ name, state, country }) => {
if (!result[country]) {
result[country] = {};
}
if (!result[country][state]) {
result[country][state] = [name];
}
else {
result[country] = {
...result[country],
[state]: [
...result[country][state],
name
]
};
}
});
console.log(result);
Output
{
US: { Florida: [ 'Place 1', 'Place 2' ], Kansas: [ 'Place 3' ] },
EE: { Harju: [ 'Place 4' ] }
}
I'm sure the if-else part can be removed by using spread operator and operator chaining, but I wasn't able to figure that out.
If your environment supports operator chaining, here's a smaller solution
const data = [];
let result = {};
data.forEach(({ name, state, country }) => {
result[country] = {
...result[country],
[state]: [
...(result?.[country]?.[state] || []),
name
]
};
});
console.log(result);
Related
my data :
const DATA = [
{
id: "1",
from: "Canada",
name: "person1",
},
{
id: "2",
from: "Canada",
name: "person2",
},
{
id: "3",
from: "France",
name: "person3",
}];
i need to filter this flatList and only display people from Canada
I think you are looking for something like:
const result = DATA.filter(element=> element.from === "Canada")
Then you can use your result to display it in the format that you want.
I have a search bar where you type employee name and it should return the name based on a filter. I have a nested JSON object (as shown below) where I need to drill into the object to access the employee's names in the array.
My problem is the code is not filtering the names and returning all the names not the names searched for. I get this error TypeError: Cannot read property 'filter' of undefined
Question: I need to map the element twice, how would I do this?
UPDATE: No answers helped me. The 2nd answer has to do with querying and is very complicated for the simple answer I am looking for, and the 1st answer has a different data structure than mine.
SECOND UPDATE: Click here for the soultion
I also added this to the thread below
added the below for more clarity
WHAT i have tried:
const results = company.filter((comp) =>
comp.details.employee.toLowerCase().includes(searchField.toLowerCase())
);
const results = company.map((el) => {
return {...el, dets: el.dets.filter((details) =>
details.employee.toLowerCase().includes(searchField.toLowerCase()))}
})
const results = company.filter((comp) => r.details.map((innerArr) => {
return innerArr.employee.toLowerCase().includes(searchField.toLowerCase());
})
);
const results = company.filter((comp) => {
return res.details.map((inner) =>
inner.employee.toLowerCase().includes(searchField.toLowerCase())
);
});
added the below for more clarity
The following code works to access the employee names in another component: how can I implement the above in the below code
{test.map((result) => (result.details.map((innerArr) =>
<h5>{innerArr.employee}</h5>
)))}
const SearchByEmpComp = () => {
const [company, setCompany] = useState([
{
"company": "HIJ",
"_id": "1",
"details":
[
{
"employee": "Lesley Peden",
"notes": "Lesley's note",
"_id": "2"
}
],
},
{
"company": "ABC",
"_id": "3",
"details":
[
{
"employee": "David Barton",
"notes": "some note!!",
"_id": "4"
}
],
}
]);
//below code I need to edit with nested map
const test = company.filter((r) =>
r.details.map((innerArr) => {
return innerArr.employee.toLowerCase().includes
(searchField.toLowerCase());
})
);
const deatils = () => {
if (searchShow)
return <EmpDetailsByName test={test} />
}
};
return (
<>
<FormControl
type="search"
/>
<div>
<Button
onClick={handleClick}
>
Enter
</Button>
<div>{deatils()}</div>
</div
);
};
Something like this should work:
const searchField = 'Les';
const company = [
{
company: 'ABC',
details: [
{
employee: 'Lesley Peden',
_id: 2
}
]
},
{
company: 'EFG',
details: [
{
employee: 'Wayne Smith',
_id: 2
}
]
},
];
const results = company.find(comp =>
comp.details.filter(inner =>
inner.employee.toLowerCase().includes(searchField.toLowerCase())
));
console.log(results);
And print it out in a similar fashion in HTML.
all credit due to Zachary Haber:
This is the CORRECT solution to my question
const test = company.map((element) => {
return {
...element,
details: element.details.filter((details) =>
details.employee.toLowerCase().includes(searchField.toLowerCase())
),
};
});
The OP's company array syntax definitely is broken, especially the details array.
I suppose the latter is going to contain many employee items; anything else does not really make sense.
Also, a Jane Smith or a John Doe have to exist each exactly once per company but of cause can occur each in more than just one company at the same time. This too is a real world use case.
Thus an assumed correct data source format would look more like ...
[{
company: "ABC",
details: [{
employee: "Lesley Peden",
_id: 1,
}, {
employee: "John Doe",
_id: 2,
}, {
employee: "Jane Smith",
_id: 3,
}],
}, {
company: "EFG",
details: [{
employee: "John Doe",
_id: 1,
}, {
employee: "Jane Smith",
_id: 2,
}, {
employee: "Wayne Smith",
_id: 3,
}],
}];
Thus an approach has to collect and normalize any employee item from each company item's details array. In order to not loose the relation to its company item any employee item does get assigned its related company.
This happens during an initial mapping and standardized item creation process. An employee item like { employee: "Lesley Peden", _id: 1 } which belongs to the company item company: "ABC" gets mapped temporarily into a standardized company employee item ... { "companyName": "ABC", "employeeId": 1, "employee": "Lesley Peden" }.
In a second aggregation/reducer step this temporary list of standardized company employee items gets transformed into a list of employee name based items, where each item does feature an additional companies array which reflects the one name to many companies relationship by containing not just one, but maybe more company items which for a name like John Doe will most probably look like this ...
{
name: "John Doe",
companies: [{
companyName: "ABC",
employeeId: 2,
}, {
companyName: "EFG",
employeeId: 1,
}],
}
An array of such items of cause can be easily filtered by each item's name property via (fragmented) name search queries which the beneath approach will demonstrate by some executable example code ...
function createEmployeeWithBoundCompanyData({ _id:employeeId, ...employeeValue }) {
return { ...this, employeeId, ...employeeValue };
}
function aggregateEmployeeItems(collector, companyEmployee) {
const { employee:name, ...employeeValue } = companyEmployee;
const { index, list } = collector;
const employeeItem = index[name] ??= { name, companies: [] };
if (employeeItem.companies.length === 0) {
list.push(employeeItem);
}
employeeItem.companies.push(employeeValue);
return collector;
}
function collectCompanyEmployees(employeeList, companyItem) {
const { company:companyName, _id:companyId, details } = companyItem;
return employeeList.concat(
details.map(
createEmployeeWithBoundCompanyData,
{ companyName, companyId },
)
);
}
function createListOfCompanyEmployees(companyListsOfEployees) {
return companyListsOfEployees
.reduce(collectCompanyEmployees, [])
.reduce(aggregateEmployeeItems, { index: {}, list: [] }).list;
}
function queryCompanyEmployeesByName(companyListsOfEployees, query) {
return createListOfCompanyEmployees(companyListsOfEployees)
.filter(({ name }) =>
name.trim().toLowerCase().includes(
query.trim().toLowerCase()
)
);
}
console.log(
'createListOfCompanyEmployees(companyList) ...',
createListOfCompanyEmployees(companyList)
);
console.log(
"queryCompanyEmployeesByName(companyList, 'Smith') ...",
queryCompanyEmployeesByName(companyList, 'Smith')
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
const companyList = [{
company: "ABC",
_id: "aaaa-bbbb-cccc-0000",
details: [{
employee: "Lesley Peden",
notes: "some notes about Lesley Peden",
_id: "aaaa-bbbb-cccc-0001",
}, {
employee: "John Doe",
notes: "some notes about John Doe",
_id: "aaaa-bbbb-cccc-0002",
}, {
employee: "Jane Smith",
notes: "some notes about Jane Smith",
_id: "aaaa-bbbb-cccc-0003",
}],
}, {
company: "EFG",
_id: "dddd-eeee-ffff-0000",
details: [{
employee: "John Doe",
notes: "some notes about John Doe",
_id: "dddd-eeee-ffff-0001",
}, {
employee: "Jane Smith",
notes: "some notes about Jane Smith",
_id: "dddd-eeee-ffff-0002",
}, {
employee: "Wayne Smith",
notes: "some notes about Wayne Smith",
_id: "dddd-eeee-ffff-0003",
}],
}];
</script>
Let's say I have a MongoDB collection "people" that has the form
[
{
_id: [OBJECT_ID_1],
name: "Paul",
hobby: "rowing",
fixed: 1
},
{
_id: [OBJECT_ID_2],
name: "Selena",
hobby: "drawing",
fixed: 2
},
{
_id: [OBJECT_ID_3],
name: "Emily",
hobby: "jogging",
fixed: 3
}
]
And new data to be inserted of the form
var data = [
{
name: "Paul", // name exists, so keep "fixed" at 1
hobby: "archery",
fixed: 4
},
{
name: "Peter",
hobby: "knitting",
fixed: 5
}
]
I would like to insert/update the collection with the new data. However, if a document with the same "name" already exists, I do not want to update "fixed". The result after inserting the above data should be
[
{
_id: [OBJECT_ID_1],
name: "Paul",
hobby: "archery", // updated
fixed: 1 // not updated, because name existed
},
{
_id: [OBJECT_ID_2],
name: "Selena",
hobby: "drawing",
fixed: 2
},
{
_id: [OBJECT_ID_3],
name: "Emily",
hobby: "jogging",
fixed: 3
},
{ // newly inserted document
_id: [OBJECT_ID_4],
name: "Peter",
hobby: "knitting",
fixed: 5
}
]
The data includes a large number of documents, so I would like to achieve this in one query if possible. What would be the best way to accomplish this? Many thanks!
bulkWrite with updateOne's with $upsert:true seems to work best for you...
bulkWrite not perform a Find operation.
Its (in my case) is necessary to control if it will create a another document.
In my case I just use find check before insert/create method.
if (collection.find({"descr":descr}).limit(1).length === 1) {
//...create method
console.log('Exists')
}
I made the following filter in hopes that I would be combining the results from all 3 $and arrays but it is only matching one of those blocks.
How can I combine the results of what would be returned from each $and array if conditions are met. Hopefully that's clear. I don't know what to call the $and array.
const filter = {
$or: [
{
$and: [
{ category: req.query.category },
{ tags: req.query.subCategory },
{contentType: req.query.contentType},
req.query.searchTerm !== ""
? {
name: {
$regex: "(?i)" + req.query.searchTerm + "(?-i)",
$options: "i",
},
}
: {},
],
$and: [
{ category: req.query.category },
{ tags: req.query.subCategory },
{contentType: req.query.contentType},
req.query.searchTerm !== ""
? {
description: {
$regex: "(?i)" + req.query.searchTerm + "(?-i)",
$options: "i",
},
}
: {},
],
$and: [
{ category: req.query.category },
{ tags: req.query.subCategory },
{contentType: req.query.contentType},
req.query.searchTerm !== ""
? {
tags: {
$regex: "(?i)" + req.query.searchTerm + "(?-i)",
$options: "i",
},
}
: {},
],
},
],
};
await Content.paginate(filter, options, (err, result) => {
if (err) {
res.status(500).send(err);
} else {
res.json(result);
}
});
EDIT: Below is an example of two entries that would be found in the database. The way it should work is it should use category, subCategory, and contentType to filter out the entries in the database so that what I have now are only the entries which have the same category, subCategory, and contentType as specified in req.query, I'll call this the firstFilterResult. From there, I am trying to search within firstFilterResult to see if I have entries that have name, tag, or description matches. So basically catgeory, subCategory and contentType are just used to narrow down the results so that I can find matches for name, tag, and description. My code above doesn't do exactly this but this is the idea behind it and I thought that what I have would do similar, but I guess I'm wrong.
contents: [
{
tags: [
'food',
'drinks',
'card',
'account'
],
_id: '1d13ff7m6db4d5417cd608f4',
name: 'THE NAME FOR THIS PIECE OF CONTENT',
description: 'In here I will begin to talk about...',
content_id: '5dbcb998ad4144390c244093',
contentType: 'quiz',
date: '2019-06-03T04:00:00.000Z',
category: 'food',
image: 'https://IMAGE.PNG',
__v: 0
},
{
tags: [
'computer',
'laptop'
],
_id: '7d1b940b1c9d44000025db8c',
name: 'THE NAME FOR THIS PIECE OF CONTENT',
description: 'This is another description',
content_id: '5f1b963d1c9d44000055db8d',
contentType: 'tool',
date: '2019-06-03T04:00:00.000Z',
category: 'money',
image: 'https://IMAGE.PNG',
__v: 0
}
]
I finally got it to work with this
const catFilter =
req.query.category !== "" ? { category: req.query.category } : {};
const subCatFilter =
req.query.subCategory !== "" ? { tags: req.query.subCategory } : {};
const typeFilter =
req.query.contentType !== ""
? { contentType: req.query.contentType }
: {};
const filter = {
$and: [
{
$or: [
{
name: {
$regex: req.query.searchTerm,
$options: "i",
},
},
{
description: {
$regex: req.query.searchTerm,
$options: "i",
},
},
{
tags: {
$regex: req.query.searchTerm,
$options: "i",
},
},
],
},
catFilter,
subCatFilter,
typeFilter,
],
};
Since each element of the $or contains the same 3 checks with a single one that varies, these can be separated out, and the $or is then only needed if a search term is specified.
Passing options:"i" makes the entire regex match case insensitive, so it is not necessary to surround the search string with (?i) and (?-i)
The following should build the filter that you are attempting, without using empty objects:
// base query that checks the common fields
var filter = {
category: req.query.category,
tags: req.query.subCategory,
contentType: req.query.contentType
};
// if a search term is provided, add in the additional critera
if (req.query.searchTerm !== "") {
var regex = {
$regex: req.query.searchTerm,
options:"i"
};
filter['$or'] = [
{ name: regex },
{ description: regex },
{ tags: regex }
]
}
If this doesn't obtain the results you're after, please edit the question and add in some sample documents so we can see the problem.
Lets say that I have the following document in the books collection:
{
_id:0 ,
item: "TBD",
stock: 0,
info: { publisher: "1111", pages: 430 },
tags: [ "technology", "computer" ],
ratings: [ { _id:id1, by: "ijk", rating: 4 }, {_id:id2 by: "lmn", rating: 5 } ],
reorder: false
}
I would like to update the value of ratings[k].rating and all I know is the id of the collection and the _id of the objects existing in the array ratings.
The tutorial of mongoDB has the following example that uses the position of the object inside the array but I suppose that if the update can only be done by knowing the position, this means that I firstly have to find the position and then proceed with the update? Can I do the update with only one call and if so how I can do that?
db.books.update(
{ _id: 1 },
{
$inc: { stock: 5 },
$set: {
item: "ABC123",
"info.publisher": "2222",
tags: [ "software" ],
"ratings.1": { by: "xyz", rating: 3 }
}
}
)
Sorry for late answer; I think this is what you want to do with mongoose.
Books.findOneAndUpdate({
_id: 1,
'ratings._id': id1
},
{
$set: {
'ratings.$.rating' : 3
}
}, function(err, book){
// Response
});
Positional operator may help you:
db.books.update(
// find book by `book_id` with `rating_id` specified
{ "_id": book_id, "ratings._id": rating_id },
// set new `value` for that rating
{ $set: { 'ratings.$.rating': value }}
);
$ will save position of matched document.