I have this Mongoose Object:
recipe = {
"ingredients": [
{
"_id": "5fc8b729c47c6f38b472078a",
"ingredient": {
"unit": "ml",
"name": "olive oil",
"createdAt": "2020-10-09T10:53:35.567Z",
"updatedAt": "2020-12-03T09:25:43.282Z",
"unitConversion": [
{
"_id": "5fc0a688c1799719d03aea3f",
"value": 20,
"convertedValue": 1,
"unitMeasure": {
"name": "spoon",
"createdAt": "2020-11-19T07:31:11.353Z",
"updatedAt": "2020-11-19T07:31:11.353Z",
"id": "5fb61f3f0a62dc27fc052271"
}
}
],
"id": "5f80412f583af20c8c0aeea1"
},
"quantity": 2,
"unitConv": "5fc0a688c1799719d03aea3f"
}
]
}
What I want to do, populate the unitConv with the object from unitConversion array that has a matching id.
Here is my code:
const newRecipe = recipe.ingredients.map((ingredient) => {
let unit
if (ingredient.unitConv && ingredient.unitConv !== null) {
unit = ingredient.ingredient.unitConversion.reduce((unit) => {
if (unit.id === ingredient.unitConv) {
return unit
}
})
ingredient.unitConv = unit
}
return ingredient
})
It works. ingredient.unitConv gets populated with the object with the matching id from the unitConversion array, but is not an object. It's being added as a string.
This is how ingredient.unitConv is being populated:
"unitConv": "{\n _id: 5fc0a688c1799719d03aea3f,\n value: 20,\n convertedValue: 1,\n unitMeasure: {\n _id: 5fb61f3f0a62dc27fc052271,\n name: 'spoon',\n createdAt: 2020-11-19T07:31:11.353Z,\n updatedAt: 2020-11-19T07:31:11.353Z,\n }\n}"
I tried using JSON.parse() on it, but it won't work, I will get several errors of SyntaxError type (Unexpected token in JSON at position...). I tried JSON.parse(JSON.stringify('the object')), but this won't populate at all.
In your environment it is probably a string, but when passing the code here, it remains as an object. Try to check the data type like this:
recipe = {
ingredients: [
{
_id: '5fc8b729c47c6f38b472078a',
ingredient: {
unit: 'ml',
name: 'olive oil',
createdAt: '2020-10-09T10:53:35.567Z',
updatedAt: '2020-12-03T09:25:43.282Z',
unitConversion: [
{
_id: '5fc0a688c1799719d03aea3f',
value: 20,
convertedValue: 1,
unitMeasure: {
name: 'spoon',
createdAt: '2020-11-19T07:31:11.353Z',
updatedAt: '2020-11-19T07:31:11.353Z',
id: '5fb61f3f0a62dc27fc052271',
},
},
],
id: '5f80412f583af20c8c0aeea1',
},
quantity: 2,
unitConv: '5fc0a688c1799719d03aea3f',
},
],
};
const newRecipe = recipe.ingredients.map((ingredient) => {
let unit;
if (ingredient.unitConv && ingredient.unitConv !== null) {
unit = ingredient.ingredient.unitConversion.reduce((unit) => {
if (unit.id === ingredient.unitConv) {
return unit;
}
});
ingredient.unitConv = unit;
}
return ingredient;
});
console.log(`unitConv is an ${typeof newRecipe[0].unitConv}`);
I would suggest you to user filter function instead of reduce function in your code. In this case you will get a array of objects. Also please check the type of unitConv in your schema.
Related
For one of my e-commerce application requirement, I have a nested array of the form (Sample):
const data = [
{
"id": 1,
"group": "upper-wear",
"labels": [
{
"type": "shirts",
"quantity": "20",
},
],
popular: true
},
{
"id": 2,
"group": "bottom-wear",
"lables": [
{
"type": "trousers",
"quantity": "31",
},
],
popular: true
},
]
To this array, I need to insert new objects to the array 'labels' if the group value equals 'upper-wear'.
const newDataToInsert = [
{
"type": 'blazers',
"quantity": 19
},
]
This is what I tried so far, considering that for now I only need to insert to single label (i.e. 'upper-wear') (in future, there can be multiple labels category 'upper-wear', 'bottom-wear', to be inserted into):
const updatedArray = data.map((datum) => {
if (datum.group === 'upper-wear') {
return {
...datum,
labels: [...datum.labels, ...newDataToInsert]
};
}
});
console.log(updatedArray);
But there seems to be a silly issue that I am missing as the result returns like this:
[
{
id: 1,
group: 'upper-wear',
labels: [ [Object], [Object] ],
popular: true
},
undefined
]
I know there may be better approaches available, but this is what I can think of as the minimum solution for now.
any help to resolve the current or any better solution will be highly appreciated.
Try with this
updatedArray = data.map((d) => {
if (d.group && d.group === 'upper-wear') {
return { ...d, labels: d.labels.concat(newDataToInsert) }
} else {
return d;
}
})
const data = [
{
"id": 1,
"group": "upper-wear",
"labels": [
{
"type": "shirts",
"quantity": "20",
},
],
popular: true
},
{
"id": 2,
"group": "bottom-wear",
"lables": [
{
"type": "trousers",
"quantity": "31",
},
],
popular: true
},
];
const newDataToInsert = [
{
"type": 'blazers',
"quantity": 19
},
];
const updatedArray = data.map((d) => {
if (d.group && d.group === 'upper-wear') {
return { ...d, labels: d.labels.concat(newDataToInsert) }
} else {
return d;
}
});
console.log(updatedArray)
Explaination
Here while mapping the data, we check for the condition
IF
If it matches then we will first copy the whole object from the variable b return { ...b }
after that we take another variable with the same name lables return { ...d, labels: d.labels.concat(newDataToInsert) },As per the JSON default nature the new variable with the same name will hold the latest value
Here in labels we first take a copy of old data and then merge it with newDataToInsert array labels: d.labels.concat(newDataToInsert), It will merge 2 arrays and store them in JSON with the name labels
Else
In else we just return the current values else { return d; }
You don't actually need to iterate with map over the array. Just find an object in the array and change what you want.
const data=[{id:1,group:"upper-wear",labels:[{type:"shirts",quantity:"20"}],popular:true},{id:2,group:"bottom-wear",lables:[{type:"trousers",quantity:"31"}],popular:true}];
const newDataToInsert=[{type:"blazers",quantity:19}];
data.find(({ group }) => group === 'upper-wear')?.labels.push(...newDataToInsert);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You're not returning all objects from your map. you're only returning a result when your criteria is met. This is resulting in your undefined objects...
const data = [
{ "id": 1, "group": "upper-wear", "labels": [ { "type": "shirts", "quantity": "20", }, ], popular: true },
{ "id": 2, "group": "bottom-wear", "lables": [ { "type": "trousers", "quantity": "31", }, ], popular: true },
]
const newDataToInsert = [ { "type": 'blazers',"quantity": 19 }, ]
const updatedArray = data.map(datum => {
if (datum.group === 'upper-wear') datum.labels = [...datum.labels, ...newDataToInsert]
return datum
});
console.log(updatedArray);
You can use Array#find to locate the desired group and then change labels for the group found. There are two options depending on how many items you would like to insert. Use Array#push to add the desired item; use forEach for more than one item:
const searchgroup = "upper-wear";
const target = data.find(({group}) => group === searchgroup);
target.labels.push(...newDataToInsert); //For one item to insert
//newDataToInsert.forEach(label => target.labels.push( label )); //For more than one item
const data = [{"id": 1, "group": "upper-wear", "labels": [{"type": "shirts", "quantity": "20"},],popular: true }, {"id": 2, "group": "bottom-wear", "lables": [{"type": "trousers", "quantity": "31", },],popular: true}];
const newDataToInsert = [{"type": 'blazers', "quantity": 19}];
//group to find
const searchgroup = "upper-wear";
//target element in data
const target = data.find(({group}) => group === searchgroup);
//check if group was found
if( target ) {
//if there's only one product in newDataToInsert us this:
//target.labels.push(...newDataToInsert);
//if you have more than one product to be inserted use this; also works for one
newDataToInsert.forEach(label => target.labels.push( label ));
} else {
console.log( `No such group found: ${searchgroup}!` );
}
console.log( data );
This question already has answers here:
Filter array of objects by multiple properties and values
(4 answers)
Closed 1 year ago.
First of all, my apologies if my question is too obvious but I my knowledge is limited and I don't get how to achieve what I am trying. I have a JSON file as source of the data (songs) and I am trying to filter that data based on several fields (level, name, artist, etc.).
Example of some data from the JSON:
[
{"artist": "Black",
"categories": "Arreglos",
"date": 1639127185000,
"level": "Fácil",
"musicStyle": "Pop/Rock",
"name": "Wonderful Life",
"practice": "n/a",
"preloadID": "Wonderful_Life",
"subtitle": "Fácil",
},
{"artist": "",
"categories": "Arreglos",
"date": 1587948049309,
"image": "./images/arreglos/a_million_dreams.jpeg",
"level": "Fácil",
"musicStyle": "Film/TV",
"name": "A million dreams",
"preloadID": "AMillionDreams_Facil",
"subtitle": "Fácil",
},
{"artist": "",
"categories": "Arreglos",
"date": 1587948046688,
"image": "./images/arreglos/a_million_dreams.jpeg",
"level": "Principiante",
"musicStyle": "Film/TV",
"name": "A million dreams",
"preloadID": "AMillionDreams_Principiante",
"subtitle": "Principiante",
},
{"artist": "Vanessa Carlton",
"categories": "Arreglos",
"date": 1602939064030,
"level": "Fácil",
"musicStyle": "Pop/Rock",
"name": "A thousand miles",
"preloadID": "AThousandMiles_Facil",
"subtitle": "Fácil",
},
{"artist": "Vanessa Carlton",
"categories": "Arreglos",
"date": 1602939064033,
"level": "Muy fácil",
"musicStyle": "Pop/Rock",
"name": "A thousand miles",
"preloadID": "AThousandMiles_MuyFacil",
"subtitle": "Muy fácil",
"tonality": ""
},
]
And this is the script I have to try to filter the data.
let filteredItems = [];
let filterLevel=this.context.appActions.dataSlots['ds_LevelFilter'];
let filterStr=this.context.appActions.dataSlots['ds_SearchFilter'];
filterStr=filterStr.toLowerCase();
items.forEach(item => {
if (item["artist"].toLowerCase().includes(filterStr) || item["name"].toLowerCase().includes(filterStr) ) {
filteredItems.push(item);
}
});
items.forEach(item => {
if (item["level"] == filterLevel) {
filteredItems.push(item);
}
});
items = filteredItems.sort((a, b) => {
return new Date(b.date) - new Date(a.date);
}).slice(0,this.context.appActions.dataSlots['ds_limitQuery']);
return items;
For filterStr, I have a text field where the user would write a search and if that is included in name or artist, it should return the resulted documents.
In filterLevel I have a picker with several values (Easy, Medium, etc. in Spanish) and it should be equal to the field "level" from the data.
I am not sure if the code shows what I am trying but if I use just the first if block (name and artist) it works perfectly. But if I add the second, it gives me an error of duplicated keys (it is a React project). I am guessing the script is not correct.
Use an Object filter: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
const songs = [
{
id: 1,
name: 'Golden Hour',
artist: 'Kacey Musgraves',
level: "Fácil",
},
{
id: 2,
name: 'King Size Manger',
artist: 'Josh Turner',
level: "Fácil",
},
{
id: 3,
name: 'Legend',
artist: 'Bob Marley',
level: "Muy fácil",
},
{
id: 4,
name: 'Catch A Fire',
artist: 'Bob Marley',
level: "Muy fácil",
},
{
id: 5,
name: 'Fine Line',
artist: 'Harry Styles',
level: "Fácil",
},
]
function filterSongs(filterStr = '', filterLevel = '') {
return songs.filter(item => {
const context = filterStr.toLowerCase()
// Filter on level else ignore by always returning true.
let result = filterLevel.length ? (item.level === filterLevel) : true
// If result is false because level was set and did not match then skip filterStr check.
// If result is true because level was ignored or matched then search if filterStr has value.
if(result && filterStr.length) {
result = item.artist.toLowerCase().includes(context) || item.name.toLowerCase().includes(context)
}
return result
})
}
console.log('Search for Harry', filterSongs('Harry'))
console.log('Search for level Fácil', filterSongs('', 'Fácil'))
console.log('Search for Golden with level Fácil', filterSongs('Golden', 'Fácil'))
console.log('Search for Bob', filterSongs('Bob'))
How to implement the above code with your example:
let filterLevel = this.context.appActions.dataSlots['ds_LevelFilter'] ?? '';
let filterStr = this.context.appActions.dataSlots['ds_SearchFilter'] ?? '';
let filterLimit = this.context.appActions.dataSlots['ds_limitQuery'] ?? 15;
function filterSongs(filterStr = '', filterLevel = '') {
return songs.filter(item => {
const context = filterStr.toLowerCase()
// Filter on level else ignore by always returning true.
let result = filterLevel.length ? (item.level === filterLevel) : true
// If result is false because level was set and did not match then skip filterStr check.
// If result is true because level was ignored or matched then search if filterStr has value.
if(result && filterStr.length) {
result = item.artist.toLowerCase().includes(context) || item.name.toLowerCase().includes(context)
}
return result
})
}
let filteredItems = filterSongs(filterStr, filterLevel)
return filteredItems.sort((a, b) => {
return new Date(b.date) - new Date(a.date);
}).slice(0,filterLimit);
How would I use the filter() on nested items ? I am trying to retrieve all the data that has a projex.HeightPay === '1'. I would like to get back the Id, Name,System etc and the projex items if the HeightPay is 1.
For example:
const fakeData = [
{
Id: "022173333101",
Name: "Blue",
System: "DESIGN",
Squares: 0,
Attributes: {
projex: [
{
Project: "50",
HeightPay: "1"
},
{
Project: "50",
HeightPay: "0"
}
]
},
Customer: {
Addr1: "Somewhere",
Addr2: ""
}
}
];
// returns nothing
const found = fakeData.filter(data => data.Attributes.projex.HeightPay === "1");
Desired output:
{
Id: "022173333101",
Name: "Blue",
System: "DESIGN",
Squares: 0,
Attributes: {
projex: [
{
Project: "50",
HeightPay: "1"
}
]
},
Customer: {
Addr1: "Somewhere",
Addr2: ""
}
}
You can use Array.prototype.map to go through each element of the fakeData array and then filter on the child array Attributes.projex using Array.prototype.filter and return a new object from the map call for every iteration
The new object in the Array.prototype.map call is formed by taking all the properties of each element of fakeData using the object spread operator ... except the Attributes.projex property and then assigning the new Attributes.projex array from the Array.prototype.filter to each new object:
const fakeData = [ { Id: "022173333101", Name: "Blue", System: "DESIGN", Squares: 0, Attributes: { projex: [ { Project: "50", HeightPay: "1" }, { Project: "50", HeightPay: "0" } ] }, Customer: { Addr1: "Somewhere", Addr2: "" } } ];
const found = fakeData.map(data => ({
...data,
Attributes: {
projex: data.Attributes.projex.filter(({
HeightPay
}) => HeightPay === "1")
}
}));
console.log(found);
const result = fakeData.map(item => ({
...item,
Attributes: {
projex: item.Attributes.projex.filter(e => e.HeightPay === "1")
}
}))
I'm having an issue returning the element which has been found in this hierarchical tree.
For example, if my selected item is:
{
"UID": 49,
"GUID": "",
"LocationName": "Doctor Smith's Office",
"LocationType": {
"UID": 2,
"LocationTypeName": "Practice",
"Description": "other location"
}
}
I will match up the UID to the below array of objects.
{
UID: 2,
GUID: "",
LocationName: "USA",
ParentLocation: null,
subs: [{
UID: 42,
GUID: "",
LocationName: "New Jersey",
Description: "",
subs: [{
UID: 3,
GUID: "",
LocationName: "Essex County",
ParentLocation: null,
"subs":[
UID: 4,
LocationName: "Newark",
ParentLocation: 3,
"subs": [
{
"UID": 49,
"GUID": "",
"LocationName": "Doctor Smith's Office",
"LocationType": {
"UID": 2,
"LocationTypeName": "Practice",
"Description": "other location"
},
"subs": [
{
"HostID": 38,
"HostName": "Ocean Host",
}
]
}
]
]
}
]
}]
};
let foundItem = this.findInTreeView(this.treeviewData[0], node.selectedNode);
// find selected node in treeview nav
// param: data - the treeview dataset
// param: selected - the selected node to be searched for in param 'data'
findInTreeView(data: any, selected: any ) {
let found;
if (this.foundInTree(data, selected)) {
return data;
}
let elem;
let ary = data.subs;
for (var i=0; i < ary.length; i++) {
elem = ary[i];
if (this.foundInTree(elem, selected)) {
// *** PROBLEM: If func has return true, I want to return the 'elem' object.
return elem;
}
}
for (var i=0; i < ary.length; i++) {
elem = ary[i];
if (elem.subs !== undefined) {
// recurse subs array
let found = this.findInTreeView(elem, selected);
if (found) {
return elem;
}
}
}
//return elem;
}
foundInTree(treeItem, node) {
if (treeItem.UID === node.UID) {
return true;
}
else {
return false;
}
}
It would be far easier to use a recursive reduce function, like this:
const input={UID:2,GUID:"",LocationName:"USA",ParentLocation:null,subs:[{UID:42,GUID:"",LocationName:"New Jersey",Description:"",subs:[{UID:3,GUID:"",LocationName:"Essex County",ParentLocation:null,"subs":[{UID:4,LocationName:"Newark",ParentLocation:3,"subs":[{"UID":49,"GUID":"","LocationName":"Doctor Smith's Office","LocationType":{"UID":2,"LocationTypeName":"Practice","Description":"other location"},"subs":[{"HostID":38,"HostName":"Ocean Host",}]}]}]}]}]};
const findUIDObj = (uid, parent) => {
const { UID, subs } = parent;
if (UID === uid) {
const { subs, ...rest } = parent;
return rest;
}
if (subs) return subs.reduce((found, child) => found || findUIDObj(uid, child), null);
};
console.log(findUIDObj(49, input))
You could use an explicit function which searches for the wanted UID.
function find(array, UID) {
var object;
array.some(o => {
if (o.UID === UID) {
return object = o;
}
return object = find(o.subs, UID);
});
return object;
}
var object = { UID: 2, GUID: "", LocationName: "USA", ParentLocation: null, subs: [{ UID: 42, GUID: "", LocationName: "New Jersey", Description: "", subs: [{ UID: 3, GUID: "", LocationName: "Essex County", ParentLocation: null, subs: [{ UID: 4, LocationName: "Newark", ParentLocation: 3, subs: [{ UID: 49, GUID: "", LocationName: "Doctor Smith's Office", LocationType: { UID: 2, LocationTypeName: "Practice", Description: "other location" }, subs: [{ HostID: 38, HostName: "Ocean Host", }] }] }] }] }] };
console.log(find([object], 49));
.as-console-wrapper { max-height: 100% !important; top: 0; }
One way to do this is to write a fairly generic version of a tree-finding function, and then configure it for your specific problem. Here we choose to test by matching on a supplied UID, we descend into children by looking at the subs property, and we convert the result by stripping out the subs property:
const searchTreeDF = (kids, test, convert, node) => test(node) // depth-first search
? convert(node)
: (kids(node) || []).reduce(
(found, child) => found || searchTreeDF(kids, test, convert, child),
false
)
const subs = node => node.subs
const matchId = (uid) => (item) => item.UID === uid
const convert = ({subs, ...rest}) => ({...rest})
const findUid = (uid, tree) => searchTreeDF(subs, matchId(uid), convert, tree)
// ...
const tree = {"GUID": "", "LocationName": "USA", "ParentLocation": null, "UID": 2, "subs": [{"Description": "", "GUID": "", "LocationName": "New Jersey", "UID": 42, "subs": [{"GUID": "", "LocationName": "Essex County", "ParentLocation": null, "UID": 3, "subs": [{"LocationName": "Newark", "ParentLocation": 3, "UID": 4, "subs": [{"GUID": "", "LocationName": "Doctor Smith's Office", "LocationType": {"Description": "other location", "LocationTypeName": "Practice", "UID": 2}, "UID": 49, "subs": [{"HostID": 38, "HostName": "Ocean Host"}]}]}]}]}]}
console.log(findUid(49, tree))
But if we didn't want to pass in the UID directly, but instead wanted to pass in an element that has its own UID property, we could write
const matchElem = (elem) => (item) => elem.UID === item.UID
and then do this:
const findUid2 = (elem, tree) => searchTreeDF(subs, matchElem(elem), convert, tree)
// ...
findUid2({UID: 49}, tree)
Or if we wanted to not convert the result, and keep the subs property, we could just supply an identity function for convert:
const findUid = (uid, tree) => searchTreeDF(subs, matchId(uid), x => x, tree)
Or we could mix and match as we please. Also note that the configuration does not have to use named functions. We could just as easily write
const findUid = (uid, tree) => searchTreeDF(
node => node.subs || [],
(item) => item.UID === uid,
({subs, ...rest}) => ({...rest}),
tree
)
Generic functions are not always the right answer. But they can help separate out those things that change from the more basic algorithm we're writing. I think in this case it helps make things more maintainable.
I want to merge item and purchases array of json into one by matching their property value.
Here's the source :
{
"item": [
{
"invoiceId": 1
},
{
"invoiceId": 2
},
{
"invoiceId": 3
}
],
"purchase": [
{
"id": "1",
"date": "12/1/2014"
},
{
"id": "2",
"date": "12/1/2014"
},
{
"id": "3",
"date": "12/1/2014"
}
]
}
I want to produce something like this :
{
"combined": [
{
"invoiceId": 1,
"id": "1",
"date": "12/1/2014"
},
{
"invoiceId": 2,
"id": "2",
"date": "12/1/2014"
},
{
"invoiceId": 3,
"id": "3",
"date": "12/1/2014"
}
]
}
How can I match the item.invoiceId with purchase.id?
Solution
assuming obj is your object
var new_obj = {combined:[]};
obj["purchase"].forEach(function(a) {
obj["item"].forEach(function(b){
if (+b["invoiceId"]===(+a["id"])) {
a["invoiceId"] = b["invoiceId"] || 0;//WILL MAKE INVOICEID 0 IF IT IS NOT DEFINE. CHANGE 0 TO YOUR NEEDS
new_obj.combined.push(a);
}
});
});
How it works
The first .forEach() loops through obj.purchase. Then we loop through obj.item To check if their is a matching invoiceId (if you don't need to make sure their is a matching invoiceId, use the alternate code). Then, we simply add a new value to the new_obj
The result (copied from console) is:
{
"combined":[
{
"id":"1",
"date":"12/1/2014",
"invoiceId":1
},
{
"id":"2",
"date":"12/1/2014",
"invoiceId":2
},
{
"id":"3",
"date":"12/1/2014",
"invoiceId":3
}
]
}
Alternative Code
Use this if you don't need to make sure, invoiceId is there
var new_obj = {combined:[]};
obj["purchase"].forEach(function(a){a["invoiceId"]=a["id"];new_obj.combined.push(a);});
One way of achieving what you want will be
var result = {};
var getMatchingPurchase = function(invoiceId) {
return data.purchase.filter(function(purchase) {
return invoiceId == purchase.id;
})[0];
};
result.combined = data.item.map(function(invoice) {
var purchase = getMatchingPurchase(invoice.invoiceId);
return {
invoiceId: invoice.invoiceId,
id: purchase.id,
date: purchase.date
};
});
console.log(result);
It will print like bellow
{ combined:
[ { invoiceId: 1, id: '1', date: '12/1/2014' },
{ invoiceId: 2, id: '2', date: '12/1/2014' },
{ invoiceId: 3, id: '3', date: '12/1/2014' } ] }
Note:- I'm using map and filter functions which are not supported in IE8. If you want to use in IE8 you have to use for loop.
If you have to support old browsers like IE8 (poor guy...), note that the native forEach might not be supported, in this case you can use lodash for cross-browser compatibility:
function getCombinedResult(source){
var combinedList = [];
_.each(source.item, function(item){
_.each(source.purchase, function(purchase){
if (item['invoiceId'].toString() != purchase['id'].toString()) return;
var combinedItem = _.extend(item, purchase)
combinedList.push(combinedItem);
});
})
return {"combined": combinedList};
}