JS Reduce with parent parameter - javascript

I am trying to solve my last issue with my reduce function to turn a nested JSON object into a flat list to enable easier searching.
Taking the JSON Below
{
"MovementPatterns": [
{
"Id": "",
"Name": "Warm-up",
"Exercises": [
{
"Id": "",
"Name": "Lizard Stretch",
"Level": 1,
"EquipmentRequired": ""
},
{
"Id": "",
"Name": "Pigeon Stretch",
"Level": 1,
"EquipmentRequired": ""
},
{
"Id": "",
"Name": "Core Hold",
"Level": 1,
"EquipmentRequired": ""
},
{
"Id": "",
"Name": "Superman",
"Level": 1,
"EquipmentRequired": ""
}
]
},
{
"Name": "Horizontal Push",
"Id": "",
"Exercises": [
{
"Id": "",
"Name": "Wall Push-up",
"Level": 0,
"VideoUrl": "",
"EquipmentRequired": ""
},
{
"Id": "",
"Name": "Push-up",
"Level": 1,
"EquipmentRequired": ""
},
{
"Id": "",
"Name": "Tap Push-up",
"Level": 2,
"EquipmentRequired": ""
},
{
"Id": "",
"Name": "Explosive Push-up",
"Level": 3,
"EquipmentRequired": ""
}
]
}
]
}
I have used the following code:
const exercises = data.MovementPatterns.reduce(
(a, {Exercises}) => [...a, ...Exercises, ...a],
[],
);
To flattern all the exercises from each movement pattern into a pure list of exercises...This is great, but I now need to INCLUDE in that JSON for each exercise the PARENT Movement Pattern ID e.g.
[
{
"Id": "",
"Name": "Lizard Stretch",
"Level": 1,
"MovementPatternId": 1,
"EquipmentRequired": ""
},
....
{
"Id": "",
"Name": "Wall Push-up",
"Level": 1,
"MovementPatternId": 2,
"EquipmentRequired": ""
},
]
Can someone please help me figure out how to do this with my reduce function :)
Thanks

You're almost close. Just append parent's Id as MovementPatternId to the each element of Exercises.
const exercises = ab.MovementPatterns.reduce(
(a, { Exercises, Id }) => [
...a,
...Exercises.map(e => ({ ...e, MovementPatternId: Id }))
],
[]
);

Related

Finding ID in nested array of objects

I'm still new to recursive functions but I'm having trouble returning the object once found to a variable (currently searching based on ID). I've included a dataset below and what I have so far. The recursive function finds the correct matching item, but when it returns it, it just returns undefined to the variable. I have tried the solution here and also get the same problem I have where it just returns undefined instead of the object. Any help/pointers would be great!
const data = {
"navItems": [
{
"type": "directory",
"id" : 1,
"name": "Nav Title 1",
"children": [
{
"downloadUrl": "",
"content": "",
"id" : 2,
"type": "file",
"name": "File 1.pdf"
},
{
"downloadUrl": "",
"content": "",
"type": "file",
"id" : 3,
"name": "File 2.pdf"
},
{
"type": "directory",
"name": "Sub Title 1",
"id" : 4,
"children": [
{
"downloadUrl": "",
"content": "",
"type": "file",
"id" : 5,
"name": "Sub File 1.pdf"
},
{
"downloadUrl": "",
"content": "",
"type": "file",
"id" : 6,
"name": "Sub File 2.docx"
}
]
},
{
"type": "directory",
"name": "Sub Title 2",
"id" : 7,
"children": [
{
"type": "directory",
"id" : 8,
"name": "Sub Sub Title 1",
"children": [
{
"downloadUrl": "",
"id" : 9,
"content": "",
"type": "file",
"name": "Sub Sub File 1.pdf"
},
{
"downloadUrl": "",
"content": "",
"type": "file",
"id" : 10,
"name": "Sub Sub File 2.pdf"
}
]
},
{
"type": "directory",
"name": "Sub Sub Title 2",
"id" : 11,
"children": [
{
"downloadUrl": "",
"content": "",
"id" : 12,
"type": "file",
"name": "Sub Sub File 1.pdf"
},
{
"downloadUrl": "",
"content": "",
"id" : 13,
"type": "file",
"name": "Sub Sub File 2.pdf"
}
]
}
]
}
]
}
]
}
/* console.log(navigationConfig);*/
const searchNavItems = (navItem) => {
if (navItem.id == 10) {
console.log(navItem);
return navItem;
} else {
if (navItem.hasOwnProperty("children") && navItem.children.length > 0 && navItem.type == "directory") {
navItem.children.map(child => searchNavItems(child))
} else {
return false;
}
}
};
let dataItem = data.navItems.forEach((item => {
let nav = searchNavItems(item);
console.log(nav);
}))
console.log(dataItem)
in your fn there are couple of problems
when you are maping over children you are not returning the array of children
forEach does not return anything () => void, so you will need to create a new variable to hold that value
let dataItem;
data.navItems.forEach((item) => {
console.log(searchNavItems(item));
dataItem = searchNavItems(item);
});
hope this helps
Presented below is one possible way to achieve the desired objective.
Code Snippet
const mySearch = (needle, hayStack) => (
hayStack.some(({ id }) => id === needle)
? (
{children, ...rest} = hayStack.find(({ id }) => id === needle),
[{...rest}]
)
: hayStack.flatMap(
({ children = [] }) => mySearch(needle, children)
)
);
/* explanation of the above method
// method to search for element/s with given "id"
// finding "needle" (ie "id") in hayStack (ie, "array" of objects)
const mySearch = (needle, hayStack) => (
// check if needle exists in current array
hayStack.some(({ id }) => id === needle)
? ( // find the matching array elt, destructure to access
// "children" and "rest" props.
// send the props other than "children"
{children, ...rest} = hayStack.find(({ id }) => id === needle),
[{...rest}]
) // if needle is not present in current array
// try searching in the inner/nested "children" array
: hayStack.flatMap( // use ".flatMap()" to avoid nested return
// recursive call to "mySearch" with "children" as the hayStack
({ children = [] }) => mySearch(needle, children)
)
);
*/
const data = {
"navItems": [{
"type": "directory",
"id": 1,
"name": "Nav Title 1",
"children": [{
"downloadUrl": "",
"content": "",
"id": 2,
"type": "file",
"name": "File 1.pdf"
},
{
"downloadUrl": "",
"content": "",
"type": "file",
"id": 3,
"name": "File 2.pdf"
},
{
"type": "directory",
"name": "Sub Title 1",
"id": 4,
"children": [{
"downloadUrl": "",
"content": "",
"type": "file",
"id": 5,
"name": "Sub File 1.pdf"
},
{
"downloadUrl": "",
"content": "",
"type": "file",
"id": 6,
"name": "Sub File 2.docx"
}
]
},
{
"type": "directory",
"name": "Sub Title 2",
"id": 7,
"children": [{
"type": "directory",
"id": 8,
"name": "Sub Sub Title 1",
"children": [{
"downloadUrl": "",
"id": 9,
"content": "",
"type": "file",
"name": "Sub Sub File 1.pdf"
},
{
"downloadUrl": "",
"content": "",
"type": "file",
"id": 10,
"name": "Sub Sub File 2.pdf"
}
]
},
{
"type": "directory",
"name": "Sub Sub Title 2",
"id": 11,
"children": [{
"downloadUrl": "",
"content": "",
"id": 12,
"type": "file",
"name": "Sub Sub File 1.pdf"
},
{
"downloadUrl": "",
"content": "",
"id": 13,
"type": "file",
"name": "Sub Sub File 2.pdf"
}
]
}
]
}
]
}]
};
console.log(
'data for id: 10\n',
mySearch(10, data.navItems)?.[0]
);
console.log(
'data for id: 13\n',
mySearch(13, data.navItems)?.[0]
);
console.log(
'data for id: 9\n',
mySearch(9, data.navItems)?.[0]
);
console.log(
'data for id: 3\n',
mySearch(3, data.navItems)?.[0]
);
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Inline comments added to the snippet above.

How to add value to object and remove current value?

I have data like this :
users = [{
"emp_id": 1,
"user": {
"emp_full_name": "Test",
"emp_email": "test#gmail.com",
"emp_phone_no": null,
"preferred_work_type": null
},
"hashtag": {
"id": 1,
"name": "NodeJs",
"hashtag_group_id": 1
},
"difficulty": "HARD"
}, {
"emp_id": 2,
"user": {
"emp_full_name": "test2",
"emp_email": "test2#gmail.com",
"emp_phone_no": null,
"preferred_work_type": null
},
"hashtag": {
"id": 1,
"name": "NodeJs",
"hashtag_group_id": 1
},
"difficulty": "EASY"
}, {
"emp_id": 1,
"user": {
"emp_full_name": "Test",
"emp_email": "test#gmail.com",
"emp_phone_no": null,
"preferred_work_type": null
},
"hashtag": {
"id": 4,
"name": "Javascript",
"hashtag_group_id": 1
},
"difficulty": "HARD"
}]
I want to add hashtag to same the object that has the same emp_id. If emp_id has more than one data then the data that has the emp_id with the single hashtag data should be removed.
So basically this is what I expected:
[{
"emp_id": 1,
"user": {
"emp_full_name": "Test",
"emp_email": "test#gmail.com",
"emp_phone_no": null,
"preferred_work_type": null
},
"hashtag": [{
"id": 1,
"name": "NodeJs",
"hashtag_group_id": 1
}, {
"id": 4,
"name": "Javascript",
"hashtag_group_id": 1
}],
"difficulty": "HARD"
}, {
"emp_id": 2,
"user": {
"emp_full_name": "test2",
"emp_email": "test2#gmail.com",
"emp_phone_no": null,
"preferred_work_type": null
},
"hashtag": {
"id": 1,
"name": "NodeJs",
"hashtag_group_id": 1
},
"difficulty": "EASY"
}]
How to transform the data like that?
I have no idea how to solve that, I tried using filter(), and map() with some validation condition, but couldn't get it to work.
You can use .reduce() with .findIndex(). Try this
let users = '[{"emp_id": 1, "user": {"emp_full_name": "Test", "emp_email": "test#gmail.com", "emp_phone_no": null, "preferred_work_type": null }, "hashtag": {"id": 1, "name": "NodeJs", "hashtag_group_id": 1 }, "difficulty": "HARD"}, {"emp_id": 2, "user": {"emp_full_name": "test2", "emp_email": "test2#gmail.com", "emp_phone_no": null, "preferred_work_type": null }, "hashtag": {"id": 1, "name": "NodeJs", "hashtag_group_id": 1 }, "difficulty": "EASY"}, {"emp_id": 1, "user": {"emp_full_name": "Test", "emp_email": "test#gmail.com", "emp_phone_no": null, "preferred_work_type": null }, "hashtag": {"id": 4, "name": "Javascript", "hashtag_group_id": 1 }, "difficulty": "HARD"} ]';
users = JSON.parse(users)
users = users.reduce((arr, o) => {
let idx = arr.findIndex(({emp_id}) => emp_id === o.emp_id);
if( idx !== -1 ) arr[idx].hashtag = [].concat(arr[idx].hashtag, o.hashtag)
else arr.push(o)
return arr;
}, []);
console.log(users)
You could create a Map keyed by emp_id and collect the users by that key. When there is already an entry, extend the hashtag using [].concat. This will create an array if it wasn't an array yet.
const users = [{"emp_id": 1,"user": {"emp_full_name": "Test","emp_email": "test#gmail.com","emp_phone_no": null,"preferred_work_type": null},"hashtag": {"id": 1,"name": "NodeJs","hashtag_group_id": 1},"difficulty": "HARD"},{"emp_id": 2,"user": {"emp_full_name": "test2","emp_email": "test2#gmail.com","emp_phone_no": null,"preferred_work_type": null},"hashtag": {"id": 1,"name": "NodeJs","hashtag_group_id": 1},"difficulty": "EASY"},{"emp_id": 1,"user": {"emp_full_name": "Test","emp_email": "test#gmail.com","emp_phone_no": null,"preferred_work_type": null},"hashtag": {"id": 4,"name": "Javascript","hashtag_group_id": 1},"difficulty": "HARD"}];
const map = new Map;
for (const user of users) {
const match = map.get(user.emp_id);
if (match) match.hashtag = [].concat(match.hashtag, user.hashtag);
else map.set(user.emp_id, {...user});
}
const result = [...map.values()];
console.log(result);

How to check for duplicate data sets in JSON payload using JavaScript?

I would like to find duplicate data sets from below payload using combination of 'NAME', 'ID'. If a set exist more than 3 times, I need to return NAME, ID of duplicated data set.
{
"Test": "1",
"value": [
{
"NAME": "ABCD",
"ID": "1234",
"ACTIVE": "true"
},
{
"NAME": "EFGH",
"ID": "5678",
"ACTIVE": "true"
},
{
"NAME": "EFGH",
"ID": "5678",
"ACTIVE": "true"
},
{
"NAME": "EFGH",
"ID": "5678",
"ACTIVE": "true"
},
{
"NAME": "ABCD",
"ID": "1234",
"ACTIVE": "true"
},
{
"NAME": "ABCD",
"ID": "1234",
"ACTIVE": "true"
},
{
"NAME": "IJKL",
"ID": "91011",
"ACTIVE": "true"
}
]
}
Expected output:
["ABCD:1234", "EFGH:5678"]
Try this. You can improve this further by performance wise, if you work around a bit.
var data = {
"Test": "1",
"value": [
{
"NAME": "ABCD",
"ID": "1234",
"ACTIVE": "true"
},
{
"NAME": "ABCD",
"ID": "1234",
"ACTIVE": "true"
},
{
"NAME": "ABCD",
"ID": "1234",
"ACTIVE": "true"
},
{
"NAME": "EFGH",
"ID": "5678",
"ACTIVE": "true"
},
{
"NAME": "IJKL",
"ID": "91011",
"ACTIVE": "true"
}
]
};
var objArray = data.value;
var duplicates = []; // duplicates will be stored here.
for(var i=0, iLen = objArray.length; i<iLen;i++){
var obj = objArray[i];
var filtered = objArray.filter(function(arrVal) {
return arrVal.NAME === obj.NAME && arrVal.ID === obj.ID ;
});
var dup = obj.NAME + ":" + obj.ID;
if(filtered.length>=3 && duplicates.indexOf(dup) < 0) {
duplicates.push(dup);
}
}
this questuon was answered multiple times on SO. You create array from the json and compare the elements of the array.
How to remove all duplicates from an array of objects?
According to your sample output I believe it's "at least 3 times" rather than "more than 3 times".
Below snippet can product the expected output with the sample data.
const data = {
"Test": "1",
"value": [
{
"NAME": "ABCD",
"ID": "1234",
"ACTIVE": "true"
},
{
"NAME": "EFGH",
"ID": "5678",
"ACTIVE": "true"
},
{
"NAME": "EFGH",
"ID": "5678",
"ACTIVE": "true"
},
{
"NAME": "EFGH",
"ID": "5678",
"ACTIVE": "true"
},
{
"NAME": "ABCD",
"ID": "1234",
"ACTIVE": "true"
},
{
"NAME": "ABCD",
"ID": "1234",
"ACTIVE": "true"
},
{
"NAME": "IJKL",
"ID": "91011",
"ACTIVE": "true"
}
]
};
const occurrence = {}; // key: count
const result = [];
for (const item of data.value) {
const key = `${item.NAME}:${item.ID}`;
occurrence[key] = (occurrence[key] || 0) + 1;
if (occurrence[key] >= 3) {
result.push(key);
}
}
console.log(result);

How can I .filter an object by elements within an array inside an object?

I've been playing around trying to learn in an API project using Postman and conducting tests using JavaScript. So far, I have succeeded with the help of reading on websites and watching YouTube videos. Of course, previous tests and playing around have been fairly easy but now I came to a stop. I really tried to figure this out for several weeks but I need further guidance, a push in the right direction or direct help.
What I'm trying to do is to filter out some of the response to only view objects that contain specific data.
To do that, I'm using a filter where I want all products containing a specific value inside an array "product_option_values".
My first approach was to see if I could sort products having any values from the first array, and it worked. It filters just fine.
var filterSmall = jsonData.products.filter(fs => fs.associations.product_option_values);
My next approach was to get to my goal of filtering out products according to specific values inside this array. I tried many simple .(dot) combinations and pointing to [index] to access it without any luck. (I must add that I know how to access this from a specific product, but that way doesn't work when filtering).
I've also tried other approaches such as:
var filterSmall = jsonData.products.filter(fs => fs.associations["product_option_values", 0, "name"] === "S");
and other similar combinations.
This is a very shortened sample of the structure of "products" which in its full form consists of 20 products and far more values inside of it:
{
"products": [
{
"id": 16,
"manufacturer_name": "Graphic Corner",
"quantity": "0",
"price": "12.900000",
"indexed": "1",
"name": "Mountain fox notebook",
"associations": {
"categories": [
{
"id": "2"
},
{
"id": "6"
}
],
"product_option_values": [
{
"id": "22"
},
{
"id": "23"
}
]
}
},
{
"id": 17,
"manufacturer_name": "Graphic Corner",
"quantity": "0",
"price": "12.900000",
"indexed": "1",
"name": "Brown bear notebook",
"associations": {
"categories": [
{
"id": "2"
},
{
"id": "6"
}
],
"product_option_values": [
{
"id": "23"
},
{
"id": "24"
}
]
}
}
]
}
and here is a small and expanded sample from product_option_values:
{
"product_option_values": [
{
"id": 1,
"id_attribute_group": "1",
"color": "",
"position": "0",
"name": "S"
},
{
"id": 2,
"id_attribute_group": "1",
"color": "",
"position": "1",
"name": "M"
},
{
"id": 3,
"id_attribute_group": "1",
"color": "",
"position": "2",
"name": "L"
}
]
}
How do I proceed? Did I do anything correct or even close to it?
Perhaps I've been staring at this for too long.
Thanks in advance.
If you want to compare nested attributes you have to transform the objects (e.g. by using a map operation), so that the relevant attributes are easily accessible for a comparison. If you want to filter by product_option_value id, you could do something like this:
const jsonData = {
"products": [
{
"id": 16,
"manufacturer_name": "Graphic Corner",
"quantity": "0",
"price": "12.900000",
"indexed": "1",
"name": "Mountain fox notebook",
"associations": {
"categories": [
{
"id": "2"
},
{
"id": "6"
}
],
"product_option_values": [
{
"id": "22"
},
{
"id": "23"
}
]
}
},
{
"id": 17,
"manufacturer_name": "Graphic Corner",
"quantity": "0",
"price": "12.900000",
"indexed": "1",
"name": "Brown bear notebook",
"associations": {
"categories": [
{
"id": "2"
},
{
"id": "6"
}
],
"product_option_values": [
{
"id": "23"
},
{
"id": "24"
}
]
}
}
]
};
const sample = {
"product_option_values": [
{
"id": 22,
"id_attribute_group": "1",
"color": "",
"position": "0",
"name": "S"
},
{
"id": 2,
"id_attribute_group": "1",
"color": "",
"position": "1",
"name": "M"
},
{
"id": 3,
"id_attribute_group": "1",
"color": "",
"position": "2",
"name": "L"
}
]
};
const ids = sample.product_option_values.map((el) => String(el.id));
console.log(ids);
const filtered = jsonData.products.filter((fs) => fs.associations.product_option_values.map((e) => e.id).some((f) => ids.includes(f)));
console.log(filtered);

flattening nested json object

[
{
"children": [
{
"children": [
{
"dateAdded": 1493033302670,
"id": "1534",
"index": 0,
"parentId": "1",
"title": "data1",
"url": "data2"
},
{
"children": [
{
"dateAdded": 1489571506844,
"id": "1451",
"index": 0,
"parentId": "1401",
"title": "data3",
"url": "data4"
}
],
"dateAdded": 1490363326576,
"dateGroupModified": 1490363326576,
"id": "1401",
"index": 1,
"parentId": "1",
"title": "daily"
},
{
"children": [
{
"dateAdded": 1481787664555,
"id": "1429",
"index": 0,
"parentId": "1407",
"title": "data56",
"url": "data"
},
{
"dateAdded": 1483365608504,
"id": "1430",
"index": 1,
"parentId": "1407",
"title": "data34",
"url": "data55"
}
]
}
]
}
]
}
]
This is a representation of Chrome bookmarks data.
If the object has url property it means that is a bookmark. If it does not have url property it is a folder.
It is a tree structure.
I would like to create flatten object with additional property named type. Like:
[
{
"dateAdded": 1489571506844,
"id": "1451",
"index": 0,
"parentId": "1401",
"title": "title",
"url": "some url",
"type": "bookmark"
},
{
"dateAdded": 1489571506844,
"id": "1451",
"index": 0,
"parentId": "1402",
"title": "title2",
"url": "some url2"
"type": "folder"
}
]
Thanks in advance.
You could use an iterative and recursive approach for getting flat data.
function flatten(array) {
var result = [];
array.forEach(function iter(o) {
var temp = {},
keys = Object.keys(o);
if (keys.length > 1) {
keys.forEach(function (k) {
if (k !== 'children') {
temp[k] = o[k];
}
});
temp.type = 'url' in o ? 'bookmark' : 'folder';
result.push(temp);
}
Array.isArray(o.children) && o.children.forEach(iter);
});
return result;
}
var data = [{ children: [{ children: [{ dateAdded: 1493033302670, id: "1534", index: 0, parentId: "1", title: "data1", url: "data2" }, { children: [{ dateAdded: 1489571506844, id: "1451", index: 0, parentId: "1401", title: "data3", url: "data4" }], dateAdded: 1490363326576, dateGroupModified: 1490363326576, id: "1401", index: 1, parentId: "1", title: "daily" }, { children: [{ dateAdded: 1481787664555, id: "1429", index: 0, parentId: "1407", title: "data56", url: "data" }, { dateAdded: 1483365608504, id: "1430", index: 1, parentId: "1407", title: "data34", url: "data55" }] }] }] }];
console.log(flatten(data));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I've made a function that iterates through an array containing objects. If a given object has a property called children, the function calls itself. If it doesn't, then it gets pushed to a new array flattenedBookmarks.
The Solution
var flattenedBookmarks = [];
flattenBookmarks(bookmarks);
function flattenBookmarks(bookmarks) {
for (var i = 0; i < bookmarks.length; i++) {
var potentialBookmark = bookmarks[i];
if (potentialBookmark.hasOwnProperty("url")) {
potentialBookmark.type = "bookmark";
} else {
potentialBookmark.type = "folder";
}
if (potentialBookmark.hasOwnProperty("children")) {
flattenBookmarks(potentialBookmark.children);
if (potentialBookmark.hasOwnProperty("dateGroupModified")) {
flattenedBookmarks.push(potentialBookmark);
}
} else {
flattenedBookmarks.push(potentialBookmark);
}
}
}
You should probably be returning the flattened array from the function instead of storing it in a new global array flattenedBookmarks, but at least this will get you started.
https://jsfiddle.net/s9ur35re/
The example shows how to do it
data = [
{
"children": [
{
"children": [
{
"dateAdded": 1493033302670,
"id": "1534",
"index": 0,
"parentId": "1",
"title": "data1",
"url": "data2"
},
{
"children": [
{
"dateAdded": 1489571506844,
"id": "1451",
"index": 0,
"parentId": "1401",
"title": "data3",
"url": "data4"
}
],
"dateAdded": 1490363326576,
"dateGroupModified": 1490363326576,
"id": "1401",
"index": 1,
"parentId": "1",
"title": "daily"
},
{
"children": [
{
"dateAdded": 1481787664555,
"id": "1429",
"index": 0,
"parentId": "1407",
"title": "data56",
"url": "data"
},
{
"dateAdded": 1483365608504,
"id": "1430",
"index": 1,
"parentId": "1407",
"title": "data34",
"url": "data55"
}
]
}
]
}
]
}
];
data2 = [];
function search(data) {
for (n in data) {
if (typeof data[n] == 'object') {
if (data[n].id != undefined) {
if (data[n].url != undefined) {
data[n].type="folder";
} else {
data[n].type="bookmark";
}
data2.push(data[n]);
}
search(data[n]);
}
}
}
search(data);
console.log(data2);

Categories

Resources