I am trying to split up an array of components into individual arrays depending on if there is a list breaking the pattern of heading, paragraph or hr. Here is what i have tried:
const components = [
{ name: 'heading' },
{ name: 'paragraph' },
{ name: 'hr' },
{ name: 'heading' },
{ name: 'list' },
{ name: 'hr' },
{ name: 'paragraph' },
{ name: 'list' }
];
const richText = [];
const list = [];
components.forEach((component, index) => {
switch (component.name) {
case 'heading': case 'hr': case 'paragraph':
richText.push(component.name)
break
case 'list':
list.push(`${component.name}-index`)
break
}
})
console.log(richText)
Current output is:
[
{ name: 'heading' },
{ name: 'paragraph' },
{ name: 'hr' },
{ name: 'heading' },
{ name: ‘hr’ },
{ name: 'paragraph' }
]
Desired output is:
[
{ name: 'heading' },
{ name: 'paragraph' },
{ name: 'hr' },
{ name: 'heading' }
],
[
{ name: ‘hr’ },
{ name: 'paragraph' }
]
Considering list as a break, you can make initialization for richText as [[]]. You can keep pushing to last index of richText until a list is found. If a list is found push an empty array [] to your richText array.
Working Fiddle
const components = [
{ name: 'heading' },
{ name: 'paragraph' },
{ name: 'hr' },
{ name: 'heading' },
{ name: 'list' },
{ name: 'hr' },
{ name: 'paragraph' },
{ name: 'list' }
];
const richText = [[]];
const list = [];
components.forEach((component, index) => {
switch (component.name) {
case 'heading': case 'hr': case 'paragraph':
richText[richText.length - 1].push(component);
break
case 'list':
list.push({name: `${component.name}-${richText.length}`})
index < components.length - 1 ? richText.push([]) : {};
break
}
})
console.log(richText);
console.log(list);
Same logic Array.reduce implementation
const components = [
{ name: 'heading' },
{ name: 'paragraph' },
{ name: 'hr' },
{ name: 'heading' },
{ name: 'list' },
{ name: 'hr' },
{ name: 'paragraph' },
{ name: 'list' }
];
const richText = components.reduce((acc, curr, index) => {
if(curr.name === 'list' && index < components.length - 1) {
acc.push([]);
} else {
acc[acc.length - 1].push(curr);
}
return acc;
}, [[]]);
console.log(richText);
How about
const components = [
{ name: 'heading' },
{ name: 'paragraph' },
{ name: 'hr' },
{ name: 'heading' },
{ name: 'list' },
{ name: 'hr' },
{ name: 'paragraph' },
{ name: 'list' }
];
let richText = [];
let list = [];
components.forEach((component, index) => {
switch (component.name) {
case 'heading': case 'hr': case 'paragraph':
richText.push({'name': component.name})
break
case 'list':
list.push(richText);
richText = [];
break
}
})
console.log(list);
Here's a generic function akin to python's groupby. Given an iterable and a key function, it yields pairs [key, chunk] where each chunk contains sequential items that have the same key:
function* groupBy(iter, keyFn) {
let last = null, buf = []
for (let item of iter) {
let key = keyFn(item)
if (key !== last) {
if (buf.length)
yield [last, buf]
buf = []
}
buf.push(item)
last = key
}
yield [last, buf]
}
Applied to your problem:
for (let [isList, tags] of groupBy(components, c => c.name === 'list'))
if (isList) ... else ...
Related
How do I remove an element from a JavaScript object by ID?
For instance I have to remove 004 or 007:
const obj = {
id: '001',
children: [
{
id: '002',
children: [
{
id: '003',
children: [],
},
{
id: '004',
children: [
{
id: '005',
children: [],
}
],
}
],
},
{
id: '006',
children: [
{
id: '007',
children: [],
}
],
},
]
}
i am trying to like this, find id but what should be next. It is expected to remove id from the object.
const removeById = (obj = {}, id = '') => {
console.log('obj: ', obj)
const search = obj.children.find(o => o.id === id)
console.log('##search: ', search)
if(search) {
console.log('## parent id: ', obj.id)
...
}
if (obj.children && obj.children.length > 0) {
obj.children.forEach(el => removeById(el, id));
}
}
removeById(obj, '007')
You can use findIndex to get the location in the array.
To remove an element from an array you need to use splice.
You can then loop over the children with some and check the children's children. Using some, you can exit out when you find the id so you do not have to keep looping.
let obj = {
id: '001',
children: [
{
id: '002',
children: [
{
id: '003',
children: [],
},
{
id: '004',
children: [
{
id: '005',
children: [],
}
],
}
],
},
{
id: '006',
children: [
{
id: '007',
children: [],
}
],
},
]
}
const removeById = (parentData, removeId) => {
// This is only the parent level!
// If you will never delete the first level parent, this is not needed
if (parentData.id === removeId){
Object.keys(data).forEach(key => delete parentData[key]);
return true;
}
function recursiveFind (children) {
// check if any of the children have the id
const index = children.findIndex(({id}) => id === removeId);
// if we have an index
if (index != -1) {
// remove it
children.splice(index, 1);
// say we found it
return true;
}
// Loop over the chldren check their children
return children.some(child => recursiveFind(child.children));
}
return recursiveFind(parentData.children);
}
removeById(obj,'004');
removeById(obj,'007');
console.log(obj)
We can use a recursive function to do it
let obj = {
id: '001',
children: [
{
id: '002',
children: [
{
id: '003',
children: [],
},
{
id: '004',
children: [
{
id: '005',
children: [],
}
],
}
],
},
{
id: '006',
children: [
{
id: '007',
children: [],
}
],
},
]
}
const removeById = (data,id) =>{
if(data.id === id){
delete data.id
delete data.children
return
}
data.children.forEach(d =>{
removeById(d,id)
})
}
removeById(obj,'006')
console.log(obj)
Update: not leave an empty object after removing
let obj = {
id: '001',
children: [
{
id: '002',
children: [
{
id: '003',
children: [],
},
{
id: '004',
children: [
{
id: '005',
children: [],
}
],
}
],
},
{
id: '006',
children: [
{
id: '007',
children: [],
}
],
},
]
}
let match = false
const removeById = (data,id) =>{
match = data.some(d => d.id == id)
if(match){
data = data.filter(d => d.id !== id)
}else{
data.forEach(d =>{
d.children = removeById(d.children,id)
})
}
return data
}
let data = [obj]
console.log(removeById(data,'004'))
I am trying to add a new object using the map, but the new object is not inserted, i think there is an issue with my logic,
What I am trying to is I have some results data, I am trying to structure it, the English 1st part will be an object and English 2nd part will be another object, these 2 objects will be inside children, like the example
[
{
title: "English",
children: [
0:{
title: "1ST",
children: [
{
title: "CQ",
dataIndex: "CQ",
},
{
title: "MCQ",
dataIndex: "MCQ",
},
],
},
1:{
title: "2ND",
children: [
{
title: "CQ",
dataIndex: "CQ",
},
{
title: "MCQ",
dataIndex: "MCQ",
},
],
}
]
Here is my code, the English 2nd part is not inserting
const results = [
{ Field: "english_1st_CQ" },
{ Field: "english_1st_MCQ" },
{ Field: "english_2nd_CQ" },
{ Field: "english_2nd_MCQ" },
];
const structureData = (results) => {
let arr = [];
results.map((value) => {
if (value.Field.includes("_")) {
const [subjectName, subjectPart, suffix] = value.Field.split("_");
const isSubjectExist = arr.find((el) => el.title == subjectName);
if (isSubjectExist !== undefined) {
isSubjectExist.children.map((element, i) => {
if (element.title == subjectPart) {
element = {
title: subjectPart,
children: [
{
title: suffix,
dataIndex: suffix,
},
],
};
} else {
element.children["2"] = {
title: suffix,
dataIndex: suffix,
};
}
});
} else {
const subject = {
title: subjectName,
children: [
{
title: subjectPart,
children: [
{
title: suffix,
dataIndex: suffix,
},
],
},
],
};
arr.push(subject);
}
}
});
return arr;
};
console.log(structureData(results));
The issue is here:
if (element.title == subjectPart) {
element = {
title: subjectPart,
children: [
{
title: suffix,
dataIndex: suffix,
},
],
};
I think that using a functional approach helps to better keep track of and reason about what is happening during the iterations because it keeps things DRY:
function resolveNode (arr, title) {
const node = arr.find(node => node.title === title) ?? {title, children: []};
if (!arr.includes(node)) arr.push(node);
return node;
}
function parse (input) {
const result = [];
for (const {Field} of input) {
const [subject, part, suffix] = Field.split('_');
const s = resolveNode(result, subject);
const p = resolveNode(s.children, part);
// Don't create duplicates:
if (!p.children.find(node => node.title === suffix)) {
p.children.push({title: suffix, dataIndex: suffix});
}
}
return result;
}
const input = [
{ Field: "english_1st_CQ" },
{ Field: "english_1st_MCQ" },
{ Field: "english_2nd_CQ" },
{ Field: "english_2nd_MCQ" },
];
const result = parse(input);
const formattedJson = JSON.stringify(result, null, 2);
console.log(formattedJson);
I have an array of data which is a string of folders:
var data = [{ name: "/X" }, { name: "/X/Y" }, { name: "/X2" }, { name: "/X2/Z" }, { name: "/X/k" }]
For a component to display this items I need them sorted nested like these:
var data = [{ name: "/X", sub: [{ name: "/Y" }, { name: "/k" }]}, { name: "/X2" }, sub: [{ name: "/Z" }] }]
These items are just examples, the item count is 1000+ and the nested items can be unlimited too.
Any ideas how to do that?
You could do this with forEach and reduce methods and use one object to keep track of level based on the current part of the name property value.
const data = [{ name: "/X" }, { name: "/X/Y" }, { name: "/X2" }, { name: "/X2/Z" }, {name: '/X/K/1'}, {name: '/X/K/2'}]
const result = []
const level = {result}
data.forEach(({ name, ...rest }) => {
name.split('/').filter(Boolean).reduce((r, k) => {
if (!r[k]) {
r[k] = { result: [] }
r.result.push({
name: `/${k}`,
sub: r[k].result
})
}
return r[k]
}, level)
})
console.log(result)
Using reduce() and Map()
var data = [{ name: "/X" }, { name: "/X/Y" }, { name: "/X2" }, { name: "/X2/Z" }, { name: "/X/k" }]
var res = data.reduce((a, i) => {
let s = i.name.match(/\/\w+/g) || []
if (a.has(s[0])) {
let path = a.get(s[0])
i.name = s[1]
path.sub = path.sub || []
path.sub.push(i)
} else {
a.set(i.name, i)
}
return a
}, new Map())
console.log([...res.values()])
I'm attempting to convert an array that I get in this format:
data = [
{ name: 'Buttons/Large/Primary', id: '1:23' },
{ name: 'Buttons/Large/Secondary', id: '1:24' },
{ name: 'Buttons/Medium/Primary', id: '1:25' },
{ name: 'Buttons/Medium/Secondary', id: '1:26' },
{ name: 'Forms/Text', id: '2:1' },
{ name: 'Forms/Checkbox', id: '2:2' },
];
to an array in this format:
data = [
{
name: "Buttons",
id: '1:23',
components: [{
name: "Large",
id: '1:23',
components: [{
name: "Primary",
id: '1:23'
}, {
name: "Secondary",
id: '1:24'
}]
},{
name: "Medium",
id: '1:25',
components: [{
name: "Primary",
id: '1:25'
}, {
name: "Secondary",
id: '1:26'
}]
}]
}, {
name: "Forms",
id: '2:1',
components: [{
name: "Text",
id: '2:1'
},{
name: "Checkbox",
id: '2:2'
}]
}
];
My approach was to create arrays from each object in the original dataset by splitting the name property at '/', then nest them inside each other. This is what I have so far, which nests each item in the original array, but lacks grouping them together like my target format shows. Suggestions?
function nestItems(obj, path, value) {
let component = {};
let temp = component;
for (let i = 0; i < path.length; i++) {
let component = temp;
component.name = path[i];
component.id = value;
if (path.length - 1 === i) {
} else {
component.components = {};
temp = component.components;
}
}
obj.push(component)
}
let obj = [];
for (let i = 0; i < data.length; i++) {
let path = data[i].name.split('/');
nestItems(obj, path, data[i].id);
}
console.log(obj)
I agree with your approach for splitting with /.
Here's my approach for using reduce to create a map and generating the final array:
const data = [
{ name: 'Buttons/Large/Primary', id: '1:23' },
{ name: 'Buttons/Large/Secondary', id: '1:24' },
{ name: 'Buttons/Medium/Primary', id: '1:25' },
{ name: 'Buttons/Medium/Secondary', id: '1:26' },
{ name: 'Forms/Text', id: '2:1' },
{ name: 'Forms/Checkbox', id: '2:2' },
];
const map = data.reduce((acc, curr) => {
const { id } = curr;
const [parent, sub, subSub] = curr.name.split('/');
if (acc[parent]) {
if (acc[parent][sub]) {
acc[parent][sub][subSub] = { id };
} else {
acc[parent][sub] = { id };
if (subSub) {
acc[parent][sub][subSub] = { id };
}
}
} else {
acc[parent] = { id };
if (sub && subSub) {
acc[parent][sub] = {
id,
[subSub]: { id }
};
} else if (sub) {
acc[parent][sub] = { id };
};
}
return acc;
}, {});
const result = Object.keys(map).map(parentName => {
const { id: parentId, ...subs } = map[parentName];
const parentObj = { name: parentName, id: parentId };
parentObj.components = Object.keys(subs).map(subName => {
const { id: subId, ...subSubs } = subs[subName];
const subObj = { name: subName, id: subId };
if (Object.keys(subSubs).length) {
subObj.components = Object.keys(subSubs).map(subSubName => ({ name: subSubName, id: subSubs[subSubName].id }));
}
return subObj;
});
return parentObj;
});
console.log(result);
i want to push my retrieved results which is an inline string result to my array object item
Here is my code :
arrayObject.push({ type: "type", item: itemArray });
arrayObject.forEach((elementItem) => {
global.forEach((element) => {
const { items } = element;
for (const item in items) {
const title = items[item].title;
elementItem.item.push({ title });
}
});
});
And here is my json file that i retrieve from global, items and title
global: [
{
items: {
xxx: {
title: 'result1',
}
},
}
]
The result i want is like this :
[ { type: 'xxx', item: [ {name: result1 } ] } ]
Here i've used reduce and object.values to produce your expected outcome.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
const global = [{
way: 'type1',
items: {
get: {
title: 'result1',
},
post: {
title: 'result2',
},
put: {
title: 'result3',
},
},
},
{
way: 'type2',
items: {
get: {
title: 'test1',
},
post: {
title: 'test2',
},
put: {
title: 'test3',
},
},
},
]
function mapJsonToTypes(arr) {
const typeAndTitles = (acc, {items, way: type}) => {
return [...acc, {type, item: getTitlesFromItems(items)}]
}
return arr.reduce(typeAndTitles, []);
}
function getTitlesFromItems(items = {}) {
return Object.values(items).map(({ title }) => title)
}
console.log(mapJsonToTypes(global));