JS: slice a nested array in array of objects by index - javascript

The task is to slice a nested array by data property.
I have the following array structure:
const mockData = [
{
text: 'Text1',
data: [
{ field: '1' },
{ field: '2' },
{ field: '3' },
{ field: '4' },
{ field: '5' },
{ field: '6' }
]
},
{
text: 'Text2',
data: [{ field: '1' }, { field: '2' }, { field: '3' }, { field: '4' }]
}
];
Here's the method I use:
const sliceArray = mockData => mockData.map(d => ({...d, data: d.data.slice(0, 3)}))
It goes through all nested objects and slice array by data property, but how can I do it for a specific nested object instead of all of them?
I'd like to use text property as a key.
So, if I pass Text1 to a method - data property in the first object only should be sliced and the output should be:
const mockData = [
{
text: 'Text1',
data: [{ field: '1' }, { field: '2' }, { field: '3' }]
},
{
text: 'Text2',
data: [{ field: '1' }, { field: '2' }, { field: '3' }, { field: '4' }]
}
];
If I pass 'Text2':
const mockData = [
{
text: 'Text1',
data: [
{ field: '1' },
{ field: '2' },
{ field: '3' },
{ field: '4' },
{ field: '5' },
{ field: '6' }
]
},
{
text: 'Text2',
data: [{ field: '1' }, { field: '2' }, { field: '3' }]
}
];
What can be the solution? Thank you!

You can try to add a condition like this:
const sliceArray = (mockData, text) =>
mockData.map(d => d.text === text
? {...d, data: d.data.slice(0, 3)}
: d)

You can just add another parameter to your function and check if the text matches that parameter.
const mockData = [{"text":"Text1","data":[{"field":"1"},{"field":"2"},{"field":"3"},{"field":"4"},{"field":"5"},{"field":"6"}]},{"text":"Text2","data":[{"field":"1"},{"field":"2"},{"field":"3"},{"field":"4"}]}]
const sliceArray = (data, target, len = 3) =>
data.map(({ text, data, ...rest}) => ({
data: text == target ? data.slice(0, len) : data,
text,
...rest
}))
console.log(sliceArray(mockData, 'Text1'))
You could also pass an array of text values that you want to match and use includes method for checking.
const mockData = [{"text":"Text1","data":[{"field":"1"},{"field":"2"},{"field":"3"},{"field":"4"},{"field":"5"},{"field":"6"}]},{"text":"Text2","data":[{"field":"1"},{"field":"2"},{"field":"3"},{"field":"4"}]}, {"text":"Text3","data":[{"field":"1"},{"field":"2"},{"field":"3"},{"field":"4"},{"field":"5"},{"field":"6"}]}]
const sliceArray = (data, targets, len = 3) =>
data.map(({ text, data, ...rest}) => ({
data: targets.includes(text) ? data.slice(0, len) : data,
text,
...rest
}))
console.log(sliceArray(mockData, ['Text1', 'Text3']))

If you dont wanna to modify existing data, Since you are mocking. use reduce
const mockData = [
{
text: "Text1",
data: [
{ field: "1" },
{ field: "2" },
{ field: "3" },
{ field: "4" },
{ field: "5" },
{ field: "6" }
]
},
{
text: "Text2",
data: [{ field: "1" }, { field: "2" }, { field: "3" }, { field: "4" }]
}
];
function update(data, text, count = 3) {
return data.reduce((arr, item) => {
let updatedItem = { ...item };
if (item.text === text) {
updatedItem.data = (updatedItem.data || []).slice(0, count);
}
arr.push(updatedItem);
return arr;
}, []);
}
console.log("%j", update(mockData, "Text1"));
.as-console-row {color: blue!important}

Related

How to construct an array of object to unique array of object

I have an array of object like this
let data =
[
{
text: 'label'
},
{
text: 'username'
},
{
text: 'category'
},
{
text: 'book'
},
{
text: 'john'
},
{
text: 'education'
},
{
text: 'car'
},
{
text: 'doe'
},
{
text: 'automotive'
},
{
text: 'shoes'
},
{
text: 'cena'
},
{
text: 'fashion'
},
]
and my expect array of objects
let result =
[
{
label: 'book',
username: 'john',
category: 'education'
},
{
label: 'car',
username: 'doe',
category: 'automotive'
},
{
label: 'shoes',
username: 'cena',
category: 'fashion'
},
]
Just a simple for loop is probably the clearest. Here storing each object in a temp variable to avoid having to access the end of the result array every iteration, and abstracting the size into a variable.
let data = [{ text: 'label' }, { text: 'username' }, { text: 'category' }, { text: 'book' }, { text: 'john' }, { text: 'education' }, { text: 'car' }, { text: 'doe' }, { text: 'automotive' }, { text: 'shoes' }, { text: 'cena' }, { text: 'fashion' },];
const size = 3;
const result = [];
for (let temp, i = size; i < data.length; i++) {
if (i % size === 0) {
result.push(temp = {});
}
temp[data[i % size].text] = data[i].text;
}
console.log(result)
How about a switch-case with a modulo % operator to check for the current key:
const transformData = (data) => {
let result = [];
let tmpObj = {};
data.forEach((element, idx) => {
switch (idx % 3) {
case 0:
tmpObj["label"] = element.text;
break;
case 1:
tmpObj["username"] = element.text;
break;
case 2:
result.push({ ...tmpObj,
category: element.text
});
tmpObj = {};
break;
default:
break;
}
});
return result;
};
console.log(transformData(getSampleData()));
function getSampleData() {
return [{
text: 'label'
},
{
text: 'username'
},
{
text: 'category'
},
{
text: 'book'
},
{
text: 'john'
},
{
text: 'education'
},
{
text: 'car'
},
{
text: 'doe'
},
{
text: 'automotive'
},
{
text: 'shoes'
},
{
text: 'cena'
},
{
text: 'fashion'
},
];
}
According to your data,the top 3 records are property name,others are data,so we can use Array.slice() to get the property names
Then we can use Array.reduce() to convert the left data
let keys = data.slice(0,3).map(v => v.text)
let result = data.slice(3).reduce((a,c,i) =>{
let key = keys[i%3]
if(i%keys.length ==0){
let obj = {}
obj[key] = c.text
a.push(obj)
}else{
a.at(-1)[key]=c.text
}
return a
},[])
console.log(result)
let data =
[
{
text: 'label'
},
{
text: 'username'
},
{
text: 'category'
},
{
text: 'book'
},
{
text: 'john'
},
{
text: 'education'
},
{
text: 'car'
},
{
text: 'doe'
},
{
text: 'automotive'
},
{
text: 'shoes'
},
{
text: 'cena'
},
{
text: 'fashion'
},
]
let keys = Object.values(data.slice(0,3)).map(v => v.text)
let result = data.slice(3).reduce((a,c,i) =>{
let key = keys[i%3]
if(i%keys.length ==0){
let obj = {}
obj[key] = c.text
a.push(obj)
}else{
a.at(-1)[key]=c.text
}
return a
},[])
console.log(result)

Get values from arrays inside nested objects, compare and return the object

I am trying to loop through a nested object that looks like this:
let obj = {
cols: [
{ name: 'name', type: 'String' },
{ name: 'dob', type: 'Number' },
{ name: 'address', type: 'String' },
{ name: 'income', type: 'String' },
{ name: 'vehicleNumber', type: 'Number' },
{ name: 'assets', type: 'Number' }
],
row: [
{
name: 'randomInfo',
columns: ['name', 'address', 'assets'],
}
]
}
I am using the logic below to loop through the object's arrays, compare if they are equal, and if they are, I am returning them in an array. I am trying to return the entire object inside the cols key though. For e.g, if there are matching elements inside cols array' name value with the row array's columns key's value, (cols.name === row.columns[element], if there is a match return cols object)
//loop through the obj and get the keys before this
let cols = cols.map(col => col.name);
let row = row.map(ind => ind.columns);
let rowNamesFlattened = [].concat.apply([], row);
let matchingCols = cols.filter(element => row.includes(element));
The matchingCols object now has the matching names, but I want to ultimately return their type as well. Any idea how this can be done?
you can use filter directly on the cols array. However here I assumed that row array has only 1 element
let obj = {
cols: [
{ name: 'name', type: 'String' },
{ name: 'dob', type: 'Number' },
{ name: 'address', type: 'String' },
{ name: 'income', type: 'String' },
{ name: 'vehicleNumber', type: 'Number' },
{ name: 'assets', type: 'Number' }
],
row: [
{
name: 'randomInfo',
columns: ['name', 'address', 'assets'],
}
]
}
let matchingCols = obj.cols.filter(({name}) => obj.row[0].columns.includes(name))
console.log(matchingCols)
In case multiple elements present inside row array. can use flatMap to get flattened list of columns and then the same procedure as above
let obj = {
cols: [
{ name: 'name', type: 'String' },
{ name: 'dob', type: 'Number' },
{ name: 'address', type: 'String' },
{ name: 'income', type: 'String' },
{ name: 'vehicleNumber', type: 'Number' },
{ name: 'assets', type: 'Number' }
],
row: [
{
name: 'randomInfo',
columns: ['name', 'address', 'assets'],
},
{
name: 'randomInfo2',
columns: ['dob','name'],
}
]
}
let filtered = obj.cols.filter(({name}) => obj.row.flatMap(ind => ind.columns).includes(name))
console.log(filtered)
Another solution to get both matched and unmatched in one go using reduce. so no need 2 filter calls. referenced this
let obj = {
cols: [
{ name: 'name', type: 'String' },
{ name: 'dob', type: 'Number' },
{ name: 'address', type: 'String' },
{ name: 'income', type: 'String' },
{ name: 'vehicleNumber', type: 'Number' },
{ name: 'assets', type: 'Number' }
],
row: [
{
name: 'randomInfo',
columns: ['name', 'address', 'assets'],
},
{
name: 'randomInfo2',
columns: ['dob','name'],
}
]
}
let flatted = obj.row.flatMap(ind => ind.columns);
const result = obj.cols.reduce((acc, curr) => {
acc[flatted.includes(curr.name) ? 'match' : 'unmatch'].push(curr);
return acc;
}, { match: [], unmatch: [] });
console.log(result)

Filter does not return the correct result

I have this array and I created this function that return me the filtered array:
const result = [{
key: 'A',
title: 'titleA',
data: [{
name: 'miael',
id: 'id4',
},
{
name: 'top',
id: 'id2',
}
]
},
{
key: 'B',
title: 'titleB',
data: [{
name: 'mich1',
id: 'id12',
},
{
name: 'tomato',
id: 'id123',
}
]
},
]
const doSearch = (data) => result.filter(entry =>
entry.data.some(item =>
item.name
.toString()
.toLowerCase()
.includes(data.toString().toLowerCase().trim()),
),
);
console.log(doSearch('mich'));
This works, but it also returns results that do not contain the searched word 'mic'
if I search for mic, I expect this result:
[{
key: 'B',
title: 'titleB',
data: [{
name: 'mich1',
id: 'id12',
},
]
}],
what am I doing wrong?
A couple of changes should make this work the way you wish.
Turning doSearch into a function.
Adding a searchFor parameter to the doSearch() function and passing to the .includes() call.
Using Array.reduce() to create the output array. Items are only added if they include the searchFor value.
const input = [{ key: 'A', title: 'titleA', data: [{ name: 'miael', id: 'id4', }, { name: 'top', id: 'id2', } ] }, { key: 'B', title: 'titleB', data: [{ name: 'mich1', id: 'id12', }, { name: 'tomato', id: 'id123', } ] }, ]
const doSearch = (searchFor, arr) => arr.reduce((acc, { key, title, data }) => {
const filteredData = data.filter(({ name }) => {
return name.toLowerCase().includes(searchFor.toLowerCase())
});
if (filteredData.length > 0) {
acc.push({ key, title, data: filteredData });
}
return acc;
}, []);
console.log(doSearch('mic', input ));
You can keep your current logic and add a map with the same filter for entry.data:
const result = [{
key: 'A',
title: 'titleA',
data: [{
name: 'miael',
id: 'id4',
},
{
name: 'top',
id: 'id2',
}
]
},
{
key: 'B',
title: 'titleB',
data: [{
name: 'mich1',
id: 'id12',
},
{
name: 'tomato',
id: 'id123',
}
]
},
]
function nameFilter(item, data) {
return item.name
.toString()
.toLowerCase()
.includes(data.toString().toLowerCase().trim())
}
const doSearch = (data) => result.filter(entry =>
entry.data.some(item =>
nameFilter(item, data)
),
).map(entry => ({
...entry,
data: entry.data.filter(item => nameFilter(item, data))
}));
console.log(doSearch('mich'));

Filter array of objects from nested array and nested array of objects

I have the following array of object
const skus = [
{
id: 1,
features: ["Slim"],
fields: [
{ label: "Material", value: "Material1" },
{ label: "Type", value: "Type1" }
]
},
{
id: 2,
features: ["Cotton"],
fields: [
{ label: "Material", value: "Material2" },
{ label: "Type", value: "Type2" }
]
},
{
id: 3,
features: ["Slim"],
fields: [
{ label: "Material", value: "Material3" },
{ label: "Type", value: "Type1" }
]
}
]
And i want the expected output to be
const output = [
{ label: "features", value: ["Slim", "Cotton"] },
{ label: "Material", value: ["Material1", "Material2", "Material3"] },
{ label: "Type", value: ["Type1", "Type2"] }
]
I tried the following way
const output = [];
let featureArr = [];
let fieldsArr = []
skus.forEach(e => {
e.features.forEach(f => {
featureArr.push(f);
});
e.fields.forEach(f => {
fieldsArr.push({ label: f.label, value: f.value });
});
});
featureArr = _.uniq(featureArr);
fieldsArr = _.uniqBy(fieldsArr, 'value')
fieldsArr = _.groupBy(fieldsArr, 'label');
output.push({ label: 'Features', value: featureArr })
for (const k in fieldsArr) {
let valArr = []
valArr = fieldsArr[k].map(v => v.value)
output.push({ label: k, value: valArr });
}
I'm getting the expected output, but here multiple loops are present. Is there a way on how can i write the solution in more optimized way.
You could take a grouping function for nested properties, where a map, an array for iterating, group and value keys are handed over. The result is a map with all collected values for each group.
Later get all unique values from the map and build a new array of objects.
const
skus = [{ id: 1, features: ["Slim"], fields: [{ label: "Material", value: "Material1" }, { label: "Type", value: "Type1" }] }, { id: 2, features: ["Cotton"], fields: [{ label: "Material", value: "Material2" }, { label: "Type", value: "Type2" }] }, { id: 3, features: ["Slim"], fields: [{ label: "Material", value: "Material3" }, { label: "Type", value: "Type1" }] }],
getGrouped = (map, array, key, value) => array.reduce((m, o) =>
m.set(o[key], [...(m.get(o[key]) || []), o[value]]), map),
result = Array.from(
skus.reduce((m, o) =>
getGrouped(
m.set('features', [...(m.get('features') || []), ...o.features]),
o.fields,
'label',
'value'
),
new Map
),
([label, value]) => ({ label, value: [...new Set(value)] })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
First Build an object with values as Sets. Then convert the object of sets into array of array.
const skus = [
{
id: 1,
features: ["Slim"],
fields: [
{ label: "Material", value: "Material1" },
{ label: "Type", value: "Type1" }
]
},
{
id: 2,
features: ["Cotton"],
fields: [
{ label: "Material", value: "Material2" },
{ label: "Type", value: "Type2" }
]
},
{
id: 3,
features: ["Slim"],
fields: [
{ label: "Material", value: "Material3" },
{ label: "Type", value: "Type1" }
]
}
];
const update = data => {
const res = {};
data.forEach(item => {
const features = res["features"] || new Set();
item.features.forEach(fea => features.add(fea));
res["features"] = features;
item.fields.forEach(field => {
const labels = res[field.label] || new Set();
labels.add(field.value);
res[field.label] = labels;
});
});
return Object.keys(res).map(key => ({ label: key, value: [...res[key]] }));
};
console.log(update(skus));
If you can use them, Sets will be your friend here:
//data
const skus = [{id: 1,features: ["Slim"],fields: [{ label: "Material", value: "Material1" },{ label: "Type", value: "Type1" }]},{id: 2,features: ["Cotton"],fields: [{ label: "Material", value: "Material2" },{ label: "Type", value: "Type2" }]},{id: 3,features: ["Slim"],fields: [{ label: "Material", value: "Material3" },{ label: "Type", value: "Type1" }]}];
//solution
const output = Object.entries(skus.reduce((map,sku) => {
sku.features.forEach(feat => map.features.add(feat));
sku.fields.forEach(field => (map[field.label] = (map[field.label] || new Set()).add(field.value)));
return map;
}, {features: new Set()})).map(([label, set]) => ({label, value: Array.from(set)}));
//display
console.log(output);
Each feature array and field array only get iterated exactly once using this approach.
If you can't use Sets, you can emulate their behavior using js objects. The goal is to use some structure that doesn't need to be iterated again to find unique values.
The following function will do the job
const fn = (array) => {
return array.reduce((result, element) => {
const features = result[0].value
const feature = element.features[0]
if (!features.includes(feature)) {
features.push(feature)
}
const materials = result[1].value
const material = element.fields[0].value
if (!materials.includes(material)) {
materials.push(material)
}
const types = result[2].value
const type = element.fields[1].value
if (!types.includes(type)) {
types.push(type)
}
return result
}, [
{ label: 'features', value: [] },
{ label: 'Material', value: [] },
{ label: 'Type', value: [] }
])
}
BUT, your object structure is quite messy, you should likely build accessor functions that extract information from your initial elements, and use some helper functions to populate your result object.
Anyway, read more about the 'reduce' function used here ;)
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce

How do I get the length of nested object inside a Javascript array? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I have this array of objects:
const mockData = [
{
text: 'Text1',
data: [
{ field: '1' },
{ field: '2' },
{ field: '3' },
{ field: '4' },
{ field: '5' },
{ field: '6' }
]
},
{
text: 'Text2',
data: [{ field: '1' }, { field: '2' }, { field: '3' }, { field: '4' }]
}
];
I would like to write a program that calculates the length of the data field for every entry in the array. The expected output should be:
6
4
Here's what I've tried so far, but can't get it out:
const res = mockData.reduce((sum, item) => item.data.length, 0);
How can this be achieved?
You need to loop through the array to get desired.
I think you just need value like 6 4 so use this line.
mockData.map(({data}) => data.length).join(' '); // 6 4
if you want to show count in new line replace .join(' '); with .join('\n');
const mockData = [
{
text: 'Text1',
data: [
{ field: '1' },
{ field: '2' },
{ field: '3' },
{ field: '4' },
{ field: '5' },
{ field: '6' }
]
},
{
text: 'Text2',
data: [{ field: '1' }, { field: '2' }, { field: '3' }, { field: '4' }]
}
];
// log it
mockData.forEach((it) => console.log(it.data.length));
// or print it on web page
mockData.forEach((it) => document.write(it.data.length + '\n'));
// So enjoy one number here
mockData.map(({data}) => data.length).join(' ');
const mockData = [
{
text: 'Text1',
data: [`enter code here`
{ field: '1' },
{ field: '2' },
{ field: '3' },
{ field: '4' },
{ field: '5' },
{ field: '6' }
]
},
{
text: 'Text2',
data: [{ field: '1' }, { field: '2' }, { field: '3' }, { field: '4' }]
}
];
var dataLengthArr = function(){
return mockData.map((obj)=>{
return obj["data"].length;
});
}
console.log(dataLengthArr());

Categories