I am attempting to build a new object from an existing deep nested object. I can't seem to get my mind in recurive mode but I am running into a bit of trouble:
oldObjArr = [{
id:1,
name:"Record1"
},{
id:2,
name:"Record2"
},{
id:3,
name:"Record3",
kids:[{
id: 4,
name: "Child 3-1"
},{
id: 5,
name: "Child 3-2"
}]
}]
buildTreeNodes = (node) => {
let data = []
node.map(record=>{
record["icon"] = "..."
record["color"] = "..."
data.push(record)
record.kids && buildTreeNodes(record.kids)
})
}
let newObjArr = buildTreeNodes(oldObjArr)
This OBVIOUSLY does not work, but I can't figure out what will. The resulting object should look like this:
[{
id:1,
name:"Record1",
icon:"...",
color: "...",
},{
id:2,
name:"Record2",
icon:"...",
color: "...",
},{
id:3,
name:"Record3",
icon:"...",
color: "...",
kids:[{
id: 4,
name: "Child 3-1",
icon:"...",
color: "...",
},{
id: 5,
name: "Child 3-2",
icon:"...",
color: "...",
}]
}]
Thanks for any help.
Robert's answer is correct.
If by chance you also want to not mutate the original object, then you can do something like this.
Also using ES6 features coz why not.
const oldObjArr = [{
id: 1,
name: "Record1"
}, {
id: 2,
name: "Record2"
}, {
id: 3,
name: "Record3",
kids: [{
id: 4,
name: "Child 3-1"
}, {
id: 5,
name: "Child 3-2"
}]
}];
function transformObject(item) {
if (Array.isArray(item.kids))
return {
...item, icon: '...', color: '...',
kids: item.kids.map(transformObject)
};
else
return {...item, icon: '...', color: '...' };
}
const newArray = oldObjArr.map(transformObject);
console.log(newArray);
So you iterate over you array and take each object and then add your props to it. Then you check if kids exist and some check if is array. i use instanceof but like #Heretic Monkey point it can be Array.isArray. What more you can setup type guard on front of function check that array argument is array then this you don't have to check that if kids is type of array.
const oldObjArr = [{
id:1,
name:"Record1"
},{
id:2,
name:"Record2"
},{
id:3,
name:"Record3",
kids:[{
id: 4,
name: "Child 3-1"
},{
id: 5,
name: "Child 3-2"
}]
}]
const addKeys = arr => {
for(const obj of arr){
obj['icon'] = "test"
obj['color'] = "test"
if("kids" in obj && obj.kids instanceof Array){
addKeys(obj.kids);
}
}
}
addKeys(oldObjArr)
console.log(oldObjArr)
V2
const addKeys = arr => {
if(!Array.isArray(arr))
return;
for(const obj of arr){
if(typeof obj !== "object")
continue;
obj['icon'] = "test"
obj['color'] = "test"
if("kids" in obj){
addKeys(obj.kids);
}
}
}
Ok check this out:
buildTreeNodes = (node) => {
let data = node.map(record=>{
record["icon"] = "..."
record["color"] = "..."
if (record.kids) record.kids = buildTreeNodes(record.kids);
return record;
})
return data;
}
let newObjArr = buildTreeNodes(oldObjArr)
console.log(newObjArr)
I think this is what you were after. You have to return record with each iteration of map, and it will add it directly to data array. The recursion within works the same.
All details are commented in demo below
let objArr = [{
id: 1,
name: "Record 1"
}, {
id: 2,
name: "Record 2"
}, {
id: 3,
name: "Record 3",
kids: [{
id: 4,
name: "Child 3-1"
}, {
id: 5,
name: "Child 3-2"
}]
},
/*
An object with a nested object not in an array
*/
{
id: 6,
name: 'Record 6',
kid: {
id: 7,
name: 'Child 6-1'
}
},
/*
An object that's filtered out because it doesn't have 'id' key/property
*/
{
no: 0,
name: null
},
/*
An object that's filtered out because it doesn't have 'id' key/property BUT has a nested object that has 'id'
*/
{
no: 99,
name: 'Member 99',
kid: {
id: 8,
name: 'Scion 99-1'
}
}
];
/*
Pass an object that has the key/value pairs that you want added to other objects
*/
const props = {
icon: '...',
color: '...'
};
/*
Pass...
a single object: {obj} of objArr[]
a single key/property: 'id'
an object that contains the key/value pairs to be added to each object that has key/property of id: {props}
*/
const addProp = (obj, prop, keyVal) => {
/*
Convert {props} object into a 2D array
props = {icon: '...', color: '...'}
~TO~
kvArr = [['icon', '...'], ['color', '...']]
*/
let kvArr = Object.entries(keyVal);
/*
for Each key/value pair of kvArr[][]
assign them to the (obj} if it has ['prop']
as one of it's key/properties
(in this demo it's 'id')
*/
kvArr.forEach(([key, val]) => {
if (obj[prop]) {
obj[key] = val;
}
});
/*
Convert {obj} into a 2D array
obj = {id: 3, name: "Record 3", kids: [{ id: 4, name: "Child 3-1"}, {id: 5, name: "Child 3-2"}]}
~TO~
subArr = [['id', 3], ['name', "Record 3"], ['kids', [{id: 4, name: "Child 3-1"}, {id: 5, name: "Child 3-2"}]]
*/
let subArr = Object.entries(obj);
/*
for Each value of subArr[][] (ie ['v'])
if it's an [Array] call addProp and pass
the {obj} of subArr[][]
*/
/*
if it's an {obj} do the same as above
*/
subArr.forEach(([k, v]) => {
if (Array.isArray(v)) {
v.forEach(subObj => {
addProp(subObj, prop, keyVal);
});
} else if (v instanceof Object) {
addProp(v, prop, keyVal);
}
});
};
// Run addProp() on each {obj} of objArr[]
for (let object of objArr) {
addProp(object, 'id', props);
}
console.log(JSON.stringify(objArr, null, 2));
Related
I have an SQLite database table
+---------------------------------------------------+
| id | Cat_Name | Parent_ID |
|---------------------------------------------------+
| 1 | Asset | NULL |
+---------------------------------------------------+
| 2 | Bank | 1 |
+---------------------------------------------------+
| 3 | Cash | 1 |
+---------------------------------------------------+
| 4 | Petty Cash | 3 |
+---------------------------------------------------+
| 5 | ABC Bank | 2 |
+---------------------------------------------------+
| 6 | Dollar Account | 2 |
+---------------------------------------------------+
i can fetch the data as below
[{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty_Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC_Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar_Account", Parent_ID: 2}]
In this table, category and subcategory created by the user, we can't assume how many parent and child categories will be in the table
Now I want pass the data as a nested javascript object to the front end
example
{Asset: {Bank: {ABC Bank: 5}, {Dollar Account: 6}
},
{Cash:{PettyCash: 4}, if any...}
}
Could anybody can help to get this result in the best way...
Thanks in advance
I suggest you change the design of the output object. I think the array approach would be better for the frontend.
const rawData = [
{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar Account", Parent_ID: 2},
{ id: 7, Category_Name: "Another Wallet", Parent_ID: 4},
];
const getParentDeep = (arr, targetId) => arr.find(({ id }) => id === targetId)
?? arr.flatMap(({ children }) => getParentDeep(children, targetId))
.filter(e => e)
.at(0);
const result = rawData
.sort(({ Parent_ID: a }, { Parent_ID: b }) => a - b)
.reduce((acc, { id, Category_Name, Parent_ID }) => {
const obj = { id, name: Category_Name, children: [] };
const parentObj = getParentDeep(acc, Parent_ID);
if (parentObj) parentObj.children.push(obj)
else acc.push(obj);
return acc;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0 }
The result will look like this:
[{
id: 1,
name: "Asset",
children: [{
id: 2,
name: "Bank",
children: [{
id: 5,
name: "ABC Bank",
children: []
}, {
id: 6,
name: "Dollar Account",
children: []
}]
}, {
id: 3,
name: "Cash",
children: [{
id: 4,
name: "Petty Cash",
children: [{
id: 7,
name: "Another Wallet",
children: []
}]
}]
}]
}]
Presented below is one possible way to achieve the desired objective. Admittedly, it is not very elegant (& possibly not the most-efficient).
Code Snippet
// helper method to recursively-add to object
const recurAdd = (arr, idx, res) => {
// when "idx" exceeds length of array "arr",
// simply return existing result "res" object
if (idx >= arr.length) return res;
// de-structure to access parent-id & id for current elt
const { Parent_ID, id } = arr[idx];
if (Parent_ID in res) {
// parent-id exists at current object,
// so, add "id" to same object (mutate)
res[Parent_ID][id] = {};
// make recursive call for "next" elt in "arr"
return recurAdd(arr, idx+1, res);
} else {
// find next-level object where current elt will fit
const foundIt = Object.values(res).map(obj => recurAdd(arr, idx, obj));
// NOTE: "obj" is part of "res" and it gets mutated
// if found, make recursive call
if (foundIt.some(x => x !== false)) return recurAdd(arr, idx+1, res);
};
// in case parent-id is not found, simply return false
return false;
};
// helper method to substitute "id" with "category names"
const recurNamify = (obj, myMap) => (
// reconstruct object from key-value pairs of intermediate result
Object.fromEntries(
// generate intermediate result of key-value pairs
Object.entries(obj)
.map(([k, v]) => (
// substitute key (ie, "id") with category-name
Object.keys(v).length === 0
? [myMap[k], k]
: [myMap[k], recurNamify(v, myMap)]
))
// when "v" is not an empty object, make recursive call
)
);
// transform the array into nested object
const myTransform = arr => {
// first transform "Number" to "string" for id and parent-id
// because JS-object keys are string type
const myArr = arr.map(ob => ({
...ob,
id: ob.id.toString(),
Parent_ID: ob.Parent_ID.toString()
}));
// generate a dictionary/map for "id" to category-name
const myMap = myArr.reduce(
(acc, itm) => {
acc[itm.id] = itm.Category_Name
return acc;
},
{}
);
// find the index of root (ie, parent id is zero)
const rIdx = myArr.findIndex(({ Parent_ID }) => Parent_ID === '0');
// obtain the root & mutate "arr" by removing the root
const [root] = myArr.splice(rIdx, 1);
// use the helper methods to transform
return recurNamify(recurAdd(myArr, 0, {[root.id]: {}}), myMap);
};
const rawData = [
{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty_Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC_Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar_Account", Parent_ID: 2}
];
console.log('transformed: ', myTransform(rawData));
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Inline comments added to the snippet above.
PS: If you'd like to add value to stackoverflow community,
Please consider reading: What to do when my question is answered
Thank you !
Here's another linked list variation, but with bi-directional object references and JSON de-/serialization in acknowledgement of the client/server relationship:
The Stack Overflow code snippet virtual console doesn't show interactive object relationships like your browser's JS console, so copy and paste this into your JS console to see the relational references in the final linked list value.
/** Conceptually similar to CSV when stringified, but preserves JSON types */
function compact (keysOrMappedKeys, array) {
const inputKeys = [];
let outputKeys = [];
const keysAreMapped = Array.isArray(keysOrMappedKeys[0]);
if (keysAreMapped) {
for (const [keyIn, keyOut] of keysOrMappedKeys) {
inputKeys.push(keyIn);
outputKeys.push(keyOut);
}
}
else {
for (const key of keysOrMappedKeys) inputKeys.push(key);
outputKeys = inputKeys;
}
const rows = [];
for (const obj of array) {
const row = [];
for (const key of inputKeys) row.push(obj[key]);
rows.push(row);
}
return [outputKeys, rows];
}
// Not actually needed for this answer:
/** The reverse of the `compact` function */
function expand ([keys, rows]) {
return rows.map(array => {
const obj = {};
for (const [index, key] of keys.entries()) obj[key] = array[index];
return obj;
});
}
/** Expects keys in the order `[ownId, parentId, ...others]` */
function createLinkedObjectList ([keys, rows]) {
const map = new Map(rows.map(row => {
const obj = {};
const iter = keys.entries();
const [ownIdIndex] = iter.next().value;
const ownId = row[ownIdIndex];
const [parentIdIndex] = iter.next().value;
const parentId = row[parentIdIndex];
for (const [index, key] of iter) obj[key] = row[index];
return [ownId, {id: ownId, parentId, value: obj}];
}));
for (const obj of map.values()) {
const parent = map.get(obj.parentId);
if (typeof parent !== 'undefined') {
obj.parent = parent;
(parent.children ??= []).push(obj);
}
delete obj.parentId;
}
return [...map.values()];
}
// Use: On the server:
// From the SQLite db:
const input = [
{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty_Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC_Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar_Account", Parent_ID: 2},
];
// Optionally, rename the keys when compacting the data structure:
const mappedKeys = [
['id', 'id'], // The ID key needs to be first
['Parent_ID', 'parent'], // The parent ID key needs to be second
// The order of the remaining keys is simply preference:
['Category_Name', 'name'],
];
const compacted = compact(mappedKeys, input);
/*
Or, just use the original key names:
const keys = [
'id', // The ID key needs to be first
'Category_Name', // The parent ID key needs to be second
// The order of the remaining keys is simply preference:
'Parent_ID',
];
const compacted = compact(keys, input);
*/
// You can send this JSON string to the client
const json = JSON.stringify(compacted);
console.log(json); // [["id","parent","name"],[[1,0,"Asset"],[2,1,"Bank"],[3,1,"Cash"],[4,3,"Petty_Cash"],[5,2,"ABC_Bank"],[6,2,"Dollar_Account"]]]
// Use: On the client:
/* After receiving the json from the server:
const json = await getDataFromServer();
Expand it into a linked list with bi-directional references
between actual parent and children objects.
This is where the order of the keys matters: */
const list = createLinkedObjectList(compacted);
console.log(list); /* Looks like this:
[
{
id: 1,
value: { name: 'Asset' },
children: [
{ id: 2, ... },
{ id: 3, ... },
],
},
{
id: 2,
value: { name: 'Bank' },
parent: { id: 1, ... },
children: [
{ id: 5, ... },
{ id: 6, ... },
],
},
{
id: 3,
value: { name: 'Cash' },
parent: { id: 1, ... },
children: [
{ id: 4, ... },
],
},
{
id: 4,
value: { name: 'Petty_Cash' },
parent: { id: 3, ... },
},
{
id: 5,
value: { name: 'ABC_Bank' },
parent: { id: 2, ... },
},
{
id: 6,
value: { name: 'Dollar_Account' },
parent: { id: 2, ... },
},
]
*/
I have a data tree structure with children:
{ id: 1,
name: "Dog",
parent_id: null,
children: [
{
id: 2,
name: "Food",
parent_id: 1,
children: []
},
{
id: 3,
name: "Water",
parent_id: 1,
children: [
{
id: 4,
name: "Bowl",
parent_id: 3,
children: []
},
{
id: 5,
name: "Oxygen",
parent_id: 3,
children: []
},
{
id: 6,
name: "Hydrogen",
parent_id: 3,
children: []
}
]
}
]
}
This represents a DOM structure that a user could select an item from to delete by clicking the corresponding button in the DOM.
I have a known text title of the selected item for deletion from the DOM set as the variable clickedTitle. I am having trouble finding an algorithm that will allow me to delete the correct object data from the deeply nested tree.
Here is my code:
function askUserForDeleteConfirmation(e) {
const okToDelete = confirm( 'Are you sure you want to delete the item and all of its sub items?' );
if(!okToDelete) {
return;
}
const tree = getTree(); // returns the above data structure
const clickedTitle = getClickedTitle(e); // returns string title of clicked on item from DOM - for example "Dog" or "Bowl"
const updatedTree = removeFromTree(tree, tree, clickedTitle);
return updatedTree;
}
function removeFromTree(curNode, newTree, clickedTitle) {
if(curNode.name === clickedTitle) {
// this correctly finds the matched data item to delete but the next lines don't properly delete it... what to do?
const index = curNode.children.findIndex(child => child.name === clickedTitle);
newTree = curNode.children.slice(index, index + 1);
// TODO - what to do here?
}
for(const node of curNode.children) {
removeFromTree(node, newTree, clickedTitle);
}
return newTree;
}
I have tried to use the info from Removing matched object from array of objects using javascript without success.
If you don't mind modifying the parameter tree in-place, this should do the job. Note that it'll return null if you attempt to remove the root.
const tree = { id: 1, name: "Dog", parent_id: null, children: [ { id: 2, name: "Food", parent_id: 1, children: [] }, { id: 3, name: "Water", parent_id: 1, children: [ { id: 4, name: "Bowl", parent_id: 3, children: [] }, { id: 5, name: "Oxygen", parent_id: 3, children: [] }, { id: 6, name: "Hydrogen", parent_id: 3, children: [] } ] } ] };
const removeFromTree = (root, nameToDelete, parent, idx) => {
if (root.name === nameToDelete) {
if (parent) {
parent.children.splice(idx, 1);
}
else return null;
}
for (const [i, e] of root.children.entries()) {
removeFromTree(e, nameToDelete, root, i);
}
return tree;
};
console.log(removeFromTree(tree, "Oxygen"));
Your current code is very much on the right track. However:
newTree = curNode.children.slice(index, index + 1);
highlights a few issues: we need to manipulate the parent's children array to remove curNode instead of curNode's own children array. I pass parent objects and the child index recursively through the calls, saving the trouble of the linear operation findIndex.
Additionally, slicing from index to index + 1 only extracts one element and doesn't modify curNode.children. It's not obvious how to go about using newArray or returning it through the call stack. splice seems like a more appropriate tool for the task at hand: extracting one element in-place.
Note that this function will delete multiple entries matching nameToDelete.
I like #VictorNascimento's answer, but by applying map then filter, each children list would be iterated twice. Here is an alternative with reduce to avoid that:
function removeFromTree(node, name) {
return node.name == name
? undefined
: {
...node,
children: node.children.reduce(
(children, child) => children.concat(removeFromTree (child, name) || []), [])
}
}
In the case you want a way to remove the items in-place, as #ggorlen proposed, I'd recommend the following solution, that is simpler in my opinion:
function removeFromTree(node, name) {
if (node.name == name) {
node = undefined
} else {
node.children.forEach((child, id) => {
if (!removeFromTree(child, name)) node.children.splice(id, 1)
})
}
return node
}
I've built the algorithm as follows:
function omitNodeWithName(tree, name) {
if (tree.name === name) return undefined;
const children = tree.children.map(child => omitNodeWithName(child, name))
.filter(node => !!node);
return {
...tree,
children
}
}
You can use it to return a new tree without the item:
noHydrogen = omitNodeWithName(tree, "Hydrogen")
If it's ok to use Lodash+Deepdash, then:
let cleaned = _.filterDeep([tree],(item)=>item.name!='Hydrogen',{tree:true});
Here is a Codepen
We use object-scan for many data processing tasks. It's powerful once you wrap your head around it. Here is how you could answer your question
// const objectScan = require('object-scan');
const prune = (name, input) => objectScan(['**[*]'], {
rtn: 'bool',
abort: true,
filterFn: ({ value, parent, property }) => {
if (value.name === name) {
parent.splice(property, 1);
return true;
}
return false;
}
})(input);
const obj = { id: 1, name: 'Dog', parent_id: null, children: [{ id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [{ id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 5, name: 'Oxygen', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] }] }] };
console.log(prune('Oxygen', obj)); // return true iff pruned
// => true
console.log(obj);
// => { id: 1, name: 'Dog', parent_id: null, children: [ { id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [ { id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] } ] } ] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
I have a nested array of objects like this:
let data = [
{
id: 1,
title: "Abc",
children: [
{
id: 2,
title: "Type 2",
children: [
{
id: 23,
title: "Number 3",
children:[] /* This key needs to be deleted */
}
]
},
]
},
{
id: 167,
title: "Cde",
children:[] /* This key needs to be deleted */
}
]
All I want is to recursively find leaves with no children (currently an empty array) and remove the children property from them.
Here's my code:
normalizeData(data, arr = []) {
return data.map((x) => {
if (Array.isArray(x))
return this.normalizeData(x, arr)
return {
...x,
title: x.name,
children: x.children.length ? [...x.children] : null
}
})
}
You need to use recursion for that:
let data = [{
id: 1,
title: "Abc",
children: [{
id: 2,
title: "Type 2",
children: [{
id: 23,
title: "Number 3",
children: [] /* This key needs to be deleted */
}]
}]
},
{
id: 167,
title: "Cde",
children: [] /* This key needs to be deleted */
}
]
function traverse(obj) {
for (const k in obj) {
if (typeof obj[k] == 'object' && obj[k] !== null) {
if (k === 'children' && !obj[k].length) {
delete obj[k]
} else {
traverse(obj[k])
}
}
}
}
traverse(data)
console.log(data)
Nik's answer is fine (though I don't see the point of accessing the children key like that), but here's a shorter alternative if it can help:
let data = [
{id: 1, title: "Abc", children: [
{id: 2, title: "Type 2", children: [
{id: 23, title: "Number 3", children: []}
]}
]},
{id: 167, title: "Cde", children: []}
];
data.forEach(deleteEmptyChildren = o =>
o.children.length ? o.children.forEach(deleteEmptyChildren) : delete o.children);
console.log(data);
If children is not always there, you can change the main part of the code to:
data.forEach(deleteEmptyChildren = o =>
o.children && o.children.length
? o.children.forEach(deleteEmptyChildren)
: delete o.children);
Simple recursion with forEach is all that is needed.
let data = [{
id: 1,
title: "Abc",
children: [{
id: 2,
title: "Type 2",
children: [{
id: 23,
title: "Number 3",
children: [] /* This key needs to be deleted */
}]
}, ]
},
{
id: 167,
title: "Cde",
children: [] /* This key needs to be deleted */
}
]
const cleanUp = data =>
data.forEach(n =>
n.children.length
? cleanUp(n.children)
: (delete n.children))
cleanUp(data)
console.log(data)
This assumes children is there. If it could be missing than just needs a minor change to the check so it does not error out on the length check. n.children && n.children.length
You can do it like this using recursion.
So here the basic idea is in removeEmptyChild function we check if the children length is non zero or not. so if it is we loop through each element in children array and pass them function again as parameter, if the children length is zero we delete the children key.
let data=[{id:1,title:"Abc",children:[{id:2,title:"Type2",children:[{id:23,title:"Number3",children:[]}]},]},{id:167,title:"Cde",children:[]},{id:1}]
function removeEmptyChild(input){
if( input.children && input.children.length ){
input.children.forEach(e => removeEmptyChild(e) )
} else {
delete input.children
}
return input
}
data.forEach(e=> removeEmptyChild(e))
console.log(data)
I have nested tree object I would like filter through without losing structure
var items = [
{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3
}]
}]
}
];
so for example if id == 2 remove object with id 2 and his children
if id == 3 only remove object with id 3
this's just apiece of object to make question clean but the object it self contains more and more :)
using vanilla javascript, _lodash or Angular2 it's okay
thank you
You can create recursive function using filter() and also continue filtering children if value is Array.
var items = [{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3
}, ]
}]
}];
function filterData(data, id) {
var r = data.filter(function(o) {
Object.keys(o).forEach(function(e) {
if (Array.isArray(o[e])) o[e] = filterData(o[e], id);
})
return o.id != id
})
return r;
}
console.log(filterData(items, 3))
console.log(filterData(items, 2))
Update: As Nina said if you know that children is property with array you don't need to loop keys you can directly target children property.
var items = [{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3
}, ]
}]
}];
const filterData = (data, id) => data.filter(o => {
if (o.children) o.children = filterData(o.children, id);
return o.id != id
})
console.log(JSON.stringify(filterData(items, 3), 0, 2))
console.log(JSON.stringify(filterData(items, 2), 0, 2))
If it's ok for your case to use Lodash+Deepdash, then:
let filtered = _.filterDeep(items,(i)=>i.id!=3,{tree:true});
Here is a demo Codepen
You could use an iterative approach with Array#some and call the callback iter recursive for the children. I found, splice.
function deleteItem(id) {
items.some(function iter(a, i, aa) {
if (a.id === id) {
aa.splice(i, 1);
return true;
}
return a.children.some(iter);
});
}
var items = [{ name: "a1", id: 1, children: [{ name: "a2", id: 2, children: [{ name: "a3", id: 3 }] }] }];
console.log(items);
deleteItem(3);
console.log(items);
deleteItem(2);
console.log(items);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use recursive function:
var items = [
{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3,
children: [{
name: "a4",
id: 4,
}]
}]
}]
}
];
function filterId(items, id) {
var len = items.length;
while (len--) {
if (items[len].id === id) {
items.splice(len, 1);
break;
} else {
filterId(items[len].children, id);
}
}
return items;
}
// filtering out the item with 'id' = 4
console.log(filterId(items, 4));
// filtering out the item with 'id' = 2
console.log(filterId(items, 2));
I want to dinamically replace object inside parent object.
Object:
var obj = {
address: { id: 2, type: { id: 1, label: 'Test1' } },
id: 1,
name: 'test'
}
Selector:
var selector = "address.type";
New inner object:
var type = { id:2, label: 'Test2' }
Now, what is the best way to replace "obj.address.type" with "type"?
My attemtp
tmp = selector.split('.'), obj1 = obj;
for(prop in tmp) {
obj1 = obj1[tmp[prop]];
}
Selectors are given as strings, I presume.
function set(obj, selector, value) {
selector = selector.split(".");
selector.slice(0, -1).reduce(function(obj, s) {
return obj[s]
}, obj)[selector.pop()] = value;
}
set(obj, 'address.type.id', 'foobar');
If selectors are real pointers to objects, you can also replace them directly:
function replaceObject(obj, newObj) {
Object.keys(obj).forEach(function(k) {
delete obj[k];
});
Object.keys(newObj).forEach(function(k) {
obj[k] = newObj[k];
});
}
replaceObject(obj.address.type, {'foo':'bar'});
James Donnelly answered correctly but here is some quick code doing what I think you wanted to achieve... I apologise if I have missed your goal. I don't know what you want to do with the selector variable.
var objArray = [{
address: { id: 2, type: { id: 1, label: 'Test1' } },
id: 1,
name: 'test'
},{
address: { id: 4, type: { id: 3, label: 'Test2' } },
id: 1,
name: 'test'
},{
address: { id: 6, type: { id: 5, label: 'Test3' } },
id: 1,
name: 'test'
}]
var lastId=6;for (var i=0,l=objArray.length;i<l; i++){objArray[i].type={id:++lastId, label:'Test'+lastId}};
JSON.stringify(objArray);