Group array object by object property and add new key - javascript

I got an array and want to convert my array into new shape, something like what on html section, so I did it successfully, but there is a problem, and a question:
Question: can I use iteration once? I mean already used map with filter, is there a better way to do this? like use single function like filter or reduce without map ?
Problem: I want to change id key name to value and title to label, but looks like can't do this in filter() Also I used forEach and map and reduce and etc.. but didn't work.
let data = [{
id: 1,
group: 1,
parent: 1,
title: 'group a'
}, {
id: 2,
group: 1,
parent: null,
title: 'a1'
}, {
id: 3,
group: 1,
parent: null,
title: 'a2'
}, {
id: 4,
group: 2,
parent: null,
title: 'b1'
},
{
id: 5,
group: 2,
parent: 2,
title: 'group b'
}
];
let array = [];
data.map(function(item) {
if (item.parent) {
let obj = {};
obj.label = item.title
//obj.options = data.filter(x => !x.parent && x.group === item.parent);
obj.options = data.map(x => !x.parent && x.group === item.parent ? {value: x.id, label: x.title} : {});
array.push(obj)
}
})
console.log(array);
<script>
[{
label: "group a",
options: [{
label: "a1",
value: 1
},
{
label: "a2",
value: 2
}
]
},
{
label: "group b",
options: [{
label: "b1",
value: 4
}
]
}];
</script>
I tried this, and used short if condition but it will add empty object if condition is false:
obj.options = data.map(x => !x.parent && x.group === item.parent ? {value: x.id, label: x.title} : {});

Your solution is not the ideal way to do this but if tweaked a little it can still be made to work. Just put a filter after your map condition to remove all the empty objects.
let data = [{
id: 1,
group: 1,
parent: 1,
title: 'group a'
}, {
id: 2,
group: 1,
parent: null,
title: 'a1'
}, {
id: 3,
group: 1,
parent: null,
title: 'a2'
}, {
id: 4,
group: 2,
parent: null,
title: 'b1'
},
{
id: 5,
group: 2,
parent: 2,
title: 'group b'
}
];
let array = [];
data.forEach(function(item) {
if (item.parent) {
let obj = {};
obj.label = item.title
obj.options = data.map(x => !x.parent && x.group === item.parent ? {value: x.id, label: x.title} : {}).filter(x => Object.keys(x).length > 0);
array.push(obj)
}
})
console.log(array);
<script>
[{
label: "group a",
options: [{
label: "a1",
value: 1
},
{
label: "a2",
value: 2
}
]
},
{
label: "group b",
options: [{
label: "b1",
value: 4
}
]
}];
</script>
I have used Object.keys.
Also map is when you want to transform the array into a new one. map returns an array, if you are not making use of the returned array, then map is not the ideal choice. That is why I used forEach.
Note:. The way to do this properly would be .reduce(). It is for computing a result from an array, where at each iteration you can refer to the accumulated object uptil that iteration.

Related

How to change value in array with objects in JS

I have an array and want to change name in object { id: 4, name: 'name4' } to 'name6'
const example = [
{
id: '1234',
desc: 'sample1',
items: [
{ id: 1, name: 'name1' },
{ id: 2, name: 'testItem2' }
]
},
{
id: '3456',
desc: 'sample2',
items: [
{ id: 4, name: 'name4' },
{ id: 5, name: 'testItem5' }
]
},
I try in this way but it isn't working
const name = 'name4';
const result = example?.forEach((group) =>
group.items.forEach((item) =>
if (item.name === name) {
return item.name === 'name6';
}
return null;
})
);
The for...of statement is my recommendation for readability and loop optimisation.
const example = [
{
id: '1234',
desc: 'sample1',
items: [
{ id: 1, name: 'name1' },
{ id: 2, name: 'testItem2' },
],
},
{
id: '3456',
desc: 'sample2',
items: [
{ id: 4, name: 'name4' },
{ id: 5, name: 'testItem5' },
],
},
];
const oldName = 'name4';
const newName = 'name6';
for (const group of example) {
for (const item of group.items) {
if (item.name === oldName) {
item.name === newName;
break
}
}
}
You could even go a step further and terminate the outer loop with a label if you only need to change the name in a single group.
outerLoop: for (const group of example) {
for (const item of group.items) {
if (item.name === oldName) {
item.name === newName;
break outerLoop;
}
}
}
Hope this helps.
You could either change the value by simply assigning a new value.
example[1].items[0].name = 'name6'
But you can also iterate through all items and search for the name you want to change. I created a function that goes through an array and loops over its nested items arrays searching for any given name (targetName) and replacing it with a new one (newName):
function changeName(array, targetName, newName) {
// Loop through the elements of array
array.forEach((element) => {
// Check each item: change the name if it matches the target
element.items.forEach((item) => {
if (item.name === targetName) item.name = newName;
});
});
}
// This function will check example array and change
// every name that has a value 'name4' into 'name6'
changeName(example, "name4", "name6");
forEach doesn't return any value.
Instead of return item.name === 'name6' you can simply set new value to item.name.
Why not like this?
const example = [{
id: '1234',
desc: 'sample1',
items: [{
id: 1,
name: 'name1'
},
{
id: 2,
name: 'testItem2'
}
]
},
{
id: '3456',
desc: 'sample2',
items: [{
id: 4,
name: 'name4'
},
{
id: 5,
name: 'testItem5'
}
]
},
]
example[1].items[0].name = 'name6'
console.log(example)

Need change an array to add a comma at the end of each 'text', but a dot in the end of last 'text' in object

const arr = [
{ id: 1, text: "123" },
{ id: 2, text: "456" },
{ id: 3, text: "789" },
];
For example now its looks like that ^^^, but need to make it smth like that:
const arr = [
{ id: 1, text: "123," },
{ id: 2, text: "456," },
{ id: 3, text: "789." },
];
How can i do it?
Here you go:
const arr = [{
id: 1,
text: "123"
},
{
id: 2,
text: "456"
},
{
id: 3,
text: "789"
},
]
const newArr = arr.map(({
id,
text
}, idx) => ({
id,
text: `${text}${idx < arr.length -1 ? "," : "."}`
}))
console.log(newArr)

How to filter nested arrays by searching

I have an array of objects that I want to filter by comparing a nested property to a search term.
For example:
let array = [
{
category: 15,
label: "Components",
value: "a614741f-7d4b-4b33-91b7-89a0ef96a099",
children: [
{
category: 1,
label: "Carousel1",
diId: 55946,
// as you can see there are many children nested array of object
children: [{ label: "nodatafoundmessage", value: "47d18fb2-3e63-4542-ad0e-e5e09acb5016", children: [] }],
value: "be5e027b-9163-4cfb-8816-0c8e3b816086"
},
{
category: 2,
label: "Checkbox1",
diId: 193909,
children: [{ label: "datafound", value: "47d18sb2-3e63-4542-ad0e-e5e09acb5016", children: [] }],
value: "045e8786-2165-4e1e-a839-99b1b0ceef57"
}
]
},
{
value: "4be22726-850c-4905-ab3b-039fcf607d55",
label: "Default",
children: [
{
category: 5,
defaultValueType: 1,
label: "Empty",
toType: "String",
value: "ebedb43f-4c53-491f-8954-d030321845cd"
},
{
category: 5,
defaultValueType: 2,
label: "Space",
toType: "String",
value: "2d0e1429-572b-4f21-9f83-3340bafff95a"
},
{
category: 5,
defaultValueType: 8,
label: "Current Username",
toType: "String",
value: "25f6b40a-33c7-4f17-b29d-99e8d1e4e33c"
},
{
category: 5,
defaultValueType: 9,
label: "Current Location",
toType: "Location",
value: "ed59da2f-318d-4599-9085-4d9d769a27d7"
}
]
},
{
category: 4,
label: "Fixed Value",
isFixed: true,
value: "28e90e3e-a20b-4499-9593-061a7d1e7bd6"
// as you can see there is no children in this object
}
]};
What I'm trying to achieve is if I search for 'nodata' for example my result should be
let array = [
{
category: 15,
label: "Components",
value: "a614741f-7d4b-4b33-91b7-89a0ef96a099",
children: [
{
category: 1,
label: "Carousel1",
diId: 55946,
// as you can see there are many children nested array of object
children: [{ label: "nodatafoundmessage", value: "47d18fb2-3e63-4542-ad0e-e5e09acb5016", children: [] }],
value: "be5e027b-9163-4cfb-8816-0c8e3b816086"
}
]
}
];
Another option if I search for 'spa' my result should be
let array = [
{
value: "4be22726-850c-4905-ab3b-039fcf607d55",
label: "Default",
children: [
{
category: 5,
defaultValueType: 2,
label: "Space",
toType: "String",
value: "2d0e1429-572b-4f21-9f83-3340bafff95a"
}
]
}
];
I have been super confused and I decided to get some help. Thank you for your helps guys!
The following function should do the trick for you:
function searchData(dataArray, searchTerm) {
return dataArray.flatMap(obj => {
const objHasSearchTerm = Object.entries(obj)
.some(([key, value]) => key !== 'children' && String(value).toLowerCase().includes(searchTerm.toLowerCase()));
if (objHasSearchTerm && !obj.children) return [obj];
const matchedChildren = searchData(obj.children ?? [], searchTerm);
return objHasSearchTerm || matchedChildren.length > 0
? [{
...obj,
children: matchedChildren,
}]
: [];
})
}
It recursively goes through the data array, looks for any entries that have the specified search term, and if so, places it into the newly constructed object. It will preserve the nested shape of the object, which may or may not be what is needed. Feel free to tweak the algorithm to your own needs.
let allData = [
{
category: 15,
label: "Components",
value: "a614741f-7d4b-4b33-91b7-89a0ef96a099",
children: [
{
category: 1,
label: "Carousel1",
diId: 55946,
// as you can see there are many children nested array of object
children: [{ label: "nodatafoundmessage", value: "47d18fb2-3e63-4542-ad0e-e5e09acb5016", children: [] }],
value: "be5e027b-9163-4cfb-8816-0c8e3b816086"
},
{
category: 2,
label: "Checkbox1",
diId: 193909,
children: [{ label: "datafound", value: "47d18sb2-3e63-4542-ad0e-e5e09acb5016", children: [] }],
value: "045e8786-2165-4e1e-a839-99b1b0ceef57"
}
]
},
{
value: "4be22726-850c-4905-ab3b-039fcf607d55",
label: "Default",
children: [
{
category: 5,
defaultValueType: 1,
label: "Empty",
toType: "String",
value: "ebedb43f-4c53-491f-8954-d030321845cd"
},
{
category: 5,
defaultValueType: 2,
label: "Space",
toType: "String",
value: "2d0e1429-572b-4f21-9f83-3340bafff95a"
},
{
category: 5,
defaultValueType: 8,
label: "Current Username",
toType: "String",
value: "25f6b40a-33c7-4f17-b29d-99e8d1e4e33c"
},
{
category: 5,
defaultValueType: 9,
label: "Current Location",
toType: "Location",
value: "ed59da2f-318d-4599-9085-4d9d769a27d7"
}
]
},
{
category: 4,
label: "Fixed Value",
isFixed: true,
value: "28e90e3e-a20b-4499-9593-061a7d1e7bd6"
// as you can see there is no children in this object
}
];
function searchData(dataArray, searchTerm) {
return dataArray.flatMap(obj => {
const objHasSearchTerm = Object.entries(obj)
.some(([key, value]) => key !== 'children' && String(value).toLowerCase().includes(searchTerm.toLowerCase()));
if (objHasSearchTerm && !obj.children) return [obj];
const matchedChildren = searchData(obj.children ?? [], searchTerm);
return objHasSearchTerm || matchedChildren.length > 0
? [{
...obj,
children: matchedChildren,
}]
: [];
})
}
console.log('----- Search: nodata')
console.log(JSON.stringify(searchData(allData, 'nodata'), null, 2))
console.log('----- Search: spa')
console.log(JSON.stringify(searchData(allData, 'spa'), null, 2))

How best to restructure JSON child data object arrays into parent array

We have some JSON data where each parent array object can have a property that is an array of "children" along the lines of the following :
data: [
{
value: 1,
parent_id: null,
label: "1.0 - TT One",
children: [
{
value: 3,
label: "1.1 - TT One-One",
parent_id: 1,
},
{
value: 4,
label: "1.2 - TT One-Two",
parent_id: 1,
}
]
},
{
value: 2,
parent_id: null,
label: "2.0 - TT Two",
children: [
{
value: 5,
label: "2.1 - TT Two-One",
parent_id: 2,
}
]
}
]
We'd like to "flatten" the children so that we end up with one array that is all the parents and children as follows (it does not have to stay named data if not efficient):
data: [
{
value: 1,
parent_id: null,
label: "1.0 - TT One"
},
{ <-- FORMER CHILD
value: 3,
label: "1.1 - TT One-One",
parent_id: 1
},
{ <-- FORMER CHILD
value: 4,
label: "1.2 - TT One-Two",
parent_id: 1,
},
{
value: 2,
parent_id: null,
label: "2.0 - TT Two"
},
{ <-- FORMER CHILD
value: 5,
label: "2.1 - TT Two-One",
parent_id: 2,
}
]
Thoughts on how best to accomplish this in an efficient manor? We have underscore if that will help.
Found a buttload of array to array flatten and some object to array flattens, but nothing combining the two. If we had this, we wouldn't have needed to post. If it's not obvious from exactly what we have to exactly what we need, what is the point?
the point is to understand what is your data structure and how to visit every element of that data structure.
Since there are only 2 levels you do not even need to find a general solution to transform your initial data into an array. Do you know how to traverse an array? then you know how to traverse an element that has an array as property as well.
Anyway here is both a recursive and a non recursive generalized solution.
var d = [{
value: 1,
parent_id: null,
label: "1.0 - TT One",
children: [{
value: 3,
label: "1.1 - TT One-One",
parent_id: 1,
},
{
value: 4,
label: "1.2 - TT One-Two",
parent_id: 1,
}
]
},
{
value: 2,
parent_id: null,
label: "2.0 - TT Two",
children: [{
value: 5,
label: "2.1 - TT Two-One",
parent_id: 2,
}]
}
];
function walkRecursive(data) {
let result = []
data.forEach(element => {
let e = { ...element}
result.push(e);
if (e.children) {
let children = walkRecursive(e.children)
result = result.concat(children)
delete e.children
}
});
return result;
}
function walk(data) {
data = data.slice()
let result = []
let d, oldData;
while (d = data.shift()) {
let el = { ...d}
result.push(el)
if (el.children) {
oldData = data
data = el.children.slice();
delete el.children;
} else {
if (oldData && data.length == 0) {
data = oldData
oldData = null;
}
}
}
return result;
}
console.log(walkRecursive(d))
console.log(walk(d))
https://codeburst.io/learn-and-understand-recursion-in-javascript-b588218e87ea

Get property of first objects in two arrays

I have two array of objects:
var books = [
{ id: 1, name: 'Book A' },
{ id: 2, name: 'Book B' }
];
var cars = [
{ id: 1, name: 'Car A' },
{ id: 2, name: 'Car B' },
{ id: 3, name: 'Car C' },
];
I need to create an array of strings that contains:
1. The Name of the first Book in books (if there are any)
2. The Names of the first 2 Cars in cars (if there are any)
I can do:
if (books.length > 0)
var bookA = books[0].name;
or:
if (cars.length > 1) {
var carA = cars[0].name;
var carB = cars[1].name;
}
Then build the string array but I believe there might be a better way to do this.
Can use filter() and map()
var books = [
{ id: 1, name: 'Book A' },
{ id: 2, name: 'Book B' }
];
var cars = [
{ id: 1, name: 'Car A' },
{ id: 2, name: 'Car B' },
{ id: 3, name: 'Car C' }
];
var res = [books[0], cars[0], cars[1]]
.filter(e => e)// remove undefined's
.map(({name:n}) => n)
console.log(res)
If you are using ES6. You can use [...array1,...array2] to merge them. So I slice the first item in book and use map to get a new array with only string name, and map it to result array.
For the cars array I slice the first two cars and do the same
var resultArray =[];
var books = [
{ id: 1, name: 'Book A' },
{ id: 2, name: 'Book B' }
];
var cars = [
{ id: 1, name: 'Car A' },
{ id: 2, name: 'Car B' },
{ id: 3, name: 'Car C' }
];
resultArray = [...resultArray, ...books.slice(0,1).map(v => v.name)]
resultArray = [...resultArray, ...cars.slice(0,2).map(v => v.name)]
console.log(resultArray)
One of a million ways to do it. This one would allow you to easily create a data structure (arrayDefinition) that configures what property to get from which array and at which index, which you could e.g. retrieve from a RESTful webservice.
var books = [
{ id: 1, name: 'Book A' },
{ id: 2, name: 'Book B' }
];
var cars = [
{ id: 1, name: 'Car A' },
{ id: 2, name: 'Car B' },
{ id: 3, name: 'Car C' },
];
const arrayDefinition = [
{
source: 'books',
index: 0,
prop: 'name'
},
{
source: 'cars',
index: 0,
prop: 'name'
},
{
source: 'cars',
index: 1,
prop: 'name'
}
];
let resultArr = []
arrayDefinition.forEach(function(def) {
if (Array.isArray(window[def.source]) && window[def.source].length >= def.index) {
resultArr.push(window[def.source][def.index][def.prop])
}
})
console.log(resultArr)

Categories