How to delete particular nodes within a nested object tree in JavaScript - javascript

Here is where my algorithm skills ends. I can traverse through the object and find a certain object but I'm not able to delete the object in the same time.
Here is the object
const obj = {
children: [{
children: [
{
children: [
{
key: 'a1',
type: 'a1_type'
},
{
key: 'a2',
type: 'a2_type'
}
],
key: 'root',
type: 'root_type'
},
{
key: 'error',
type: 'error_type'
}
]
}]
}
The object with the key === 'error' object can be in any children array. I want to find it and delete the object that contains the key.
The output should be like that:
let output = findAndDeleteObjByKeyAndType('error', 'error_type')
output = {
children: [{
children: [
{
children: [
{
key: 'a1',
type: 'a1_type'
},
{
key: 'a2',
type: 'a2_type'
}
],
key: 'root',
type: 'root_type'
}
]
}]
}
Can someone help here?

Array methods like filter and every can come in handy here:
const object = {
children: [{
children: [{
children: [{
key: 'a1',
type: 'a1_type'
},
{
key: 'a2',
type: 'a2_type'
},
{
key: 'error',
type: 'error_type'
}
],
key: 'root',
type: 'root_type'
},
{
key: 'error',
type: 'error_type'
}
]
}]
}
function purgeAll (object, target) {
if (object.children) {
const keys = Object.keys(target)
object.children = object.children.filter(o =>
!keys.every(k => target[k] === o[k]) && purgeAll(o, target)
)
}
return object
}
let output = purgeAll(object, {
key: 'error',
type: 'error_type'
})
console.log(output)
.as-console-wrapper { min-height: 100%; }

Related

How to make a function as pure function using javascript and react?

i have data like below,
const arr_obj = [
{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [
{
id: '1',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '2',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '3',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
},
{
id: '3',
children: [
{
id: '4',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '5',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '6',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
}
]
I have to find out the count of type: 'MAIN'. these 'MAIN' will be within type: "type2"
So the expected count is 6.
below is the code,
const ParentComponent = () => {
const findCount = (arr_obj) => {
let count = 0;
const expectedCount = 2;
const loop = (children) => {
for (const obj of children) {
const { type, children } = obj;
if (type === 'TYPE2') {
loop(children);
} else if (type === 'MAIN') {
++count;
if (count > expectedCount) return;
}
}
};
loop(children);
return count > expectedCount;
};
const output = findCount(arr_obj);
return (
//some jsx rendering
);
}
the above code works fine. but i want to make loop(children) function a pure function. I am not sure how to do it.
the problem now is i define variables outside the loop method.
how can i define everything as arguments to the function, you could move the function outside the component.
could someone help me with this. thanks.
You could take an array of the wanted type order and iterate only one level and han over the rest of wanted type. If no type are left over, return one otherwise the result of nested count.
const
getCount = (array, types) => {
let count = 0;
for (const { type, children } of array) {
if (types[0] === type) {
count += types.length === 1
? 1
: getCount(children, types.slice(1));
}
}
return count;
}
data = [{ id: '1', children: [], type: 'TYPE1' }, { id: '2', children: [{ id: '1', children: [{}], type: 'MAIN' }, { id: '2', children: [{}], type: 'MAIN' }, { id: '3', children: [{} ], type: 'MAIN' }], type: 'TYPE2' }, { id: '3', children: [{ id: '4', children: [{}], type: 'MAIN' }, { id: '5', children: [{}], type: 'MAIN' }, { id: '6', children: [{}], type: 'MAIN' }], type: 'TYPE2' }],
order = ['TYPE2', 'MAIN'],
count = getCount(data, order);
console.log(count);
Pure Function is a function (a block of code ) that always returns the same result if the same arguments are passed. It does not depend on any state, or data change during a program’s execution rather it only depends on its input arguments.
Reference
In the above shared code I could see expectedCount as the shared variable which is not the PURE function.
As I could see your comments the desired type is in Children then its just the 2 levels. Then the following code would work.
function count(data, condition) {
let count = 0;
data.forEach((value, index) => {
if(value.type === condition[0]){
value.children.forEach((val, idx) => {
if(val.type === condition[1]) {
count++;
}
})
}
});
return count;
}
const condition = ['TYPE2', 'MAIN'];
console.log(count(arr_obj, condition));
Nina's answer is more to the point but you could also do it by filtering the input array.
const data = [{ id: '1', children: [], type: 'TYPE1' }, { id: '2', children: [{ id: '1', children: [{}], type: 'MAIN' }, { id: '2', children: [{}], type: 'MAIN' }, { id: '3', children: [{} ], type: 'MAIN' }], type: 'TYPE2' }, { id: '3', children: [{ id: '4', children: [{}], type: 'MAIN' }, { id: '5', children: [{}], type: 'MAIN' }, { id: '6', children: [{}], type: 'MAIN' }], type: 'TYPE2' }];
const count = data
.filter(v=>v.type==='TYPE2')
.flatMap(v=>v.children)
.filter(v=>v.type==='MAIN')
.length
console.log(count);

How to find the length of particular type within object using javascript?

i have object like below,
Example 1
input = {
item_type: {
id: ‘1’,
name: ‘name1’,
},
children: [
{
type_1: {
id: ‘12’,
},
type: 'item1-type',
},
{
children: [
{
id: '1',
type: 'item2',
},
{
id: '2',
type:'item2',
},
{
id:'3',
type: 'item2',
},
]
type: 'item2-type',
},
{
children: [
{
id: '4',
type: 'item2',
},
{
id: '5',
type:'item2',
},
{
id:'6',
type: 'item2',
},
]
type: 'item2-type',
},
]
}
now i want to find the count of "item2" type within children array within children array again.
note that the outer children array can be empty array and the children array within children array may not be present. so the input can be of types like below
input = {
item_type: {
id: ‘1’,
name: ‘name1’,
},
children: [] //empty children array
}
input = {
item_type: {
id: ‘1’,
name: ‘name1’,
},
children:
[ //no children array within
{
type_1: {
id: ‘12’,
},
type: “item1-type”,
},
]
}
how can i find the count of type: "item2" within children array considering example1 input.
so the expected count is 6.
could someone help me with this. thanks. new to programming.
const findAllChildrenOfType = (obj, type) => {
let count = 0;
if (obj.type === type) count++;
if (obj.children) {
obj.children.forEach(child => {
const childCount = findAllChildrenOfType(child, type);
count += childCount;
})
}
return count;
}
console.log(findAllChildrenOfType(input, "item2"))

looping into nested array and modify data

As per the above structure, All the types are having text-1.
I wanted only the first occurrence to be in text-1 rest all should be of text-2 in the data structure.
And the order of the block should not be changed. Also block can have different types which should not get affected.
I tried to loop each block, but dono the logic to modify only first occurrence. Please help
Below is what i tried so far,
let newData = data.forEach(data => {
return data.block.forEach(block => {
return block.child.forEach((type, i) => {
if (i === 0) { return }
type.type = 'text-2'
})
});
});
Current Data
const data = [
{
block: [
{ child: [{ type: 'text-1' }] },
{ child: [{ type: 'any type' }] },
{ child: [{ type: 'text-1' }] }
]
},
{
block: [
{ child: [{ type: 'text-1' }] },
{ child: [{ type: 'text-1' }] }
]
}
]
Expected Data,
const data = [
{
block: [
{ child: [{ type: 'text-1' }] },
{ child: [{ type: 'any type' }] },
{ child: [{ type: 'text-2' }] }
]
},
{
block: [
{ child: [{ type: 'text-2' }] },
{ child: [{ type: 'text-2' }] }
]
}
]
You could map the items and the nested ones as well by taking a function for getting the first string once and for all other calls the other string.
const
getOnce = (first, other, used) => () => used ? other : (used = true, first);
data = [{ block: [{ child: [{ type: 'text-1' }] }, { child: [{ type: 'any type' }] }, { child: [{ type: 'text-1' }] }] }, { block: [{ child: [{ type: 'text-1' }] }, { child: [{ type: 'text-1' }] }] }],
type1 = 'text-1',
type2 = 'text-2',
getType = getOnce(type1, type2),
result = data.map(
({ block }) => ({
block: block.map(
({ child }) => ({
child: child.map(o => Object.assign({}, o, o.type === type1 && { type: getType() }))
})
)
})
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Generic alternative using the JSON.parse revival parameter :
var found = 0, data = [{ block: [{ child: [{ type: 'text-1' }] }, { child: [{ type: 'any type' }] }, { child: [{ type: 'text-1' }] }] }, { block: [{ child: [{ type: 'text-1' }] }, { child: [{ type: 'text-1' }] }] }]
data = JSON.parse(JSON.stringify(data),
(key, val) => key == 'type' && val == 'text-1' && found++ ? 'text-2' : val)
console.log( data )
Mutating alternative with for...of :
const data = [{ block: [{ child: [{ type: 'text-1' }] }, { child: [{ type: 'any type' }] }, { child: [{ type: 'text-1' }] }] }, { block: [{ child: [{ type: 'text-1' }] }, { child: [{ type: 'text-1' }] }] }];
let found = 0;
for (const item of data)
for (const block of item.block)
for (const child of block.child)
if (child.type === 'text-1' && found++)
child.type = 'text-2';
console.log( data );
Assuming that:
The array structure is the one shown in the example.
every data element is an object.
every object has a block property and it's an array.
every block element might have a type property.
you want to alter the original array without making a new copy of it (that's what I've understood from the question, but I can be wrong, of course).
Here is an (ugly) solution to alter the values as required.
As a side note, this kind of alteration is weird and doesn't really seems to make much sense, I highly recommend you to consider using alternative data structures.
Code explanation is directly in the code below.
const data = [
{
block: [
{ child: [{ type: 'text-1' }] },
{ child: [{ type: 'any type' }] },
{ child: [{ type: 'text-1' }] }
]
},
{
block: [
{ child: [{ type: 'text-1' }] },
{ child: [{ type: 'text-1' }] }
]
}
];
// That's unreasonable.. anyway.
data.forEach((o, i) => {
// loop each element, keep track of the index.
if (i > 0) {
// if it's NOT the first element, get all the elements who actually have a child, loop the child and for each type property, replace 1 with 2.
o.block.forEach(c => c.child && c.child.forEach(child => child.type = child.type.replace("1","2")));
}
else {
// otherwise, if that's the first element, just find the last block that has a child and whose child equals 'text-1'.
const elems = o.block.filter(c => c.child && c.child.length && c.child.some(child => child.type === 'text-1'));
const found = elems && elems.length && elems[elems.length - 1];
// if found, replace the value.
if (found) found.child[0].type = found.child[0].type.replace("1","2");
}
});
console.log(data);

How to find the first property that is an array in an object?

I'm creating a function that loops through an array like this:
schema: [{
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}, {
And returns a callback with the values of the objects:
eachDeep (array, callback) {
array.forEach(item => {
item.fields.forEach(field => {
callback(field)
})
})
},
As you can see the item.fields.forEach part is harcoded. How can I modify the function so it detects the first property that it's an array and loop through it? (e.g. in this case that property is fields).
To find whether a property of an object is an array or not you can also use this one:
//let item be your object's property
if(typeof item == "object" && item.length > 0){
//do whatever if it is an array
}
You can check if the field is not an array or not, if so loop it, otherwise do something else with it.
var data = [{
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}, {
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}];
eachDeep (array, callback) {
array.forEach(item => {
// loop through each property again
item.forEach(prop => {
// if property is an array
if (prop instanceof Array) {
prop.forEach(field => callback(field));
} else {
// property is not an array
// do something else
}
})
})
},
var big_array =
[
{
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}
];
for (let item of big_array)
{
for (let key in item)
{
if (Array.isArray(item[key]) )
{
console.log('this is an array do something:', key);
}
}
}
You could check using Array.isArray()
If the goal is to find the first array property you can do the following. Using ES6.
const schema = [{
name: 'firstRow',
fields: [{
name: 'name',
text: 'Name',
type: 'text',
col: 12,
value: ''
}]
}]
let firstArr;
schema.forEach(item => {
firstArr = Object.keys(item).filter(k => Array.isArray(item[k]))[0];
})

Concat array from Object from Array

I'm currently trying to retrieve a list of metadata stored as an array, inside an object, inside an array. Here's a better explanatory example:
[
{
name: 'test',
metadata: [
{
name: 'Author',
value: 'foo'
},
{
name: 'Creator',
value: 'foo'
}
]
},
{
name: 'otherTest',
metadata: [
{
name: 'Created',
value: 'foo'
},
{
name: 'Date',
value: 'foo'
}
]
},
{
name: 'finalTest'
}
]
Now, my objective is to retrieve a list of metadata (by their name) without redundancy. I think that .map() is the key to success but I can't find how to do it in a short way, actually my code is composed 2 for and 3 if, and I feel dirty to do that.
The expected input is: ['Author', 'Creator', 'Created', 'Date']
I'm developping in Typescript, if that can help for some function.
You can use reduce() and then map() to return array of names.
var data = [{"name":"test","metadata":[{"name":"Author","value":"foo"},{"name":"Creator","value":"foo"}]},{"name":"otherTest","metadata":[{"name":"Created","value":"foo"},{"name":"Date","value":"foo"}]},{"name":"finalTest"}]
var result = [...new Set(data.reduce(function(r, o) {
if (o.metadata) r = r.concat(o.metadata.map(e => e.name))
return r
}, []))];
console.log(result)
You could use Set for unique names.
var data = [{ name: 'test', metadata: [{ name: 'Author', value: 'foo' }, { name: 'Creator', value: 'foo' }] }, { name: 'otherTest', metadata: [{ name: 'Created', value: 'foo' }, { name: 'Date', value: 'foo' }] }, { name: 'finalTest' }],
names = new Set;
data.forEach(a => (a.metadata || []).forEach(m => names.add(m.name)));
console.log([...names]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var data = [{"name":"test","metadata":[{"name":"Author","value":"foo"},{"name":"Creator","value":"foo"}]},{"name":"otherTest","metadata":[{"name":"Created","value":"foo"},{"name":"Date","value":"foo"}]},{"name":"finalTest"}]
data
.filter(function(obj){return obj.metadata != undefined})
.map(function(obj){return obj.metadata})
.reduce(function(a,b){return a.concat(b)},[])
.map(function(obj){return obj.name})
A hand to hand Array.prototype.reduce() and Array.prototype.map() should do it as follows;
var arr = [
{
name: 'test',
metadata: [
{
name: 'Author',
value: 'foo'
},
{
name: 'Creator',
value: 'foo'
}
]
},
{
name: 'otherTest',
metadata: [
{
name: 'Created',
value: 'foo'
},
{
name: 'Date',
value: 'foo'
}
]
},
{
name: 'finalTest'
}
];
result = arr.reduce((p,c) => c.metadata ? p.concat(c.metadata.map(e => e.name))
: p, []);
console.log(result);

Categories

Resources