I have a Object which looks like the following obj.
var obj = [
{ id: 1, name: "animals" },
{ id: 2, name: "animals_cat" },
{ id: 3, name: "animals_dog" },
{ id: 4, name: "animals_weazle" },
{ id: 5, name: "animals_weazle_sand shadow weazle" },
{ id: 11, name: "fruits" },
{ id: 32, name: "fruits_banana" },
{ id: 10, name: "threes" },
{ id: 15, name: "cars" }
];
The Object should be converted into the following scheme:
var items = [
{ id: 11, name: "fruits", items: [
{ id: 32, name: "banana" }
]},
{ id: 10, name: "threes" },
{ id: 1, name: "animals", items: [
{ id: 2, name: "cat" },
{ id: 3, name: "dog" },
{ id: 4, name: "weazle", items: [
{ id: 5, name: "sand shadow weazle" }
]}
]},
{ id: 15, name: "cars" }
];
I tried a lot but unfortunately without any success. I did $.each on obj, did a split('_') on it and pushed it to items. But how can I do it for unlimited depth and push it into the right category?
I'm happy for any help.
Maybe this helps.
It works with Array.prototype.forEach for processing obj, Array.prototype.reduce for getting the right branch and Array.prototype.some for the right array element for inserting the new object.
This proposal works for sorted and consistent data.
var obj = [
{ id: 1, name: "animals" },
{ id: 2, name: "animals_cat" },
{ id: 3, name: "animals_dog" },
{ id: 4, name: "animals_weazle" },
{ id: 5, name: "animals_weazle_sand shadow weazle" },
{ id: 11, name: "fruits" },
{ id: 32, name: "fruits_banana" },
{ id: 10, name: "threes" },
{ id: 15, name: "cars" }
],
tree = [];
obj.forEach(function (a) {
var path = a.name.split('_'),
o = {};
o.id = a.id;
path.reduce(function (r, b) {
o.name = b;
r.some(function (c) {
if (c.name === b) {
c.items = c.items || [];
r = c.items;
return true;
}
});
return r;
}, tree).push(o);
});
document.write('<pre>' + JSON.stringify(tree, 0, 4) + '</pre>');
Update: Version for independent order of items.
var obj = [
{ id: 5, name: "animals_weazle_sand shadow weazle" },
{ id: 32, name: "fruits_banana" },
{ id: 1, name: "animals" },
{ id: 2, name: "animals_cat" },
{ id: 3, name: "animals_dog" },
{ id: 4, name: "animals_weazle" },
{ id: 11, name: "fruits" },
{ id: 10, name: "threes" },
{ id: 15, name: "cars" },
{ id: 999, name: "music_pop_disco_euro"}
],
tree = [];
obj.forEach(function (item) {
var path = item.name.split('_'),
o = tree;
path.forEach(function (a, i) {
var oo = { name: a, items: [] },
last = path.length - 1 === i,
found = o.some(function (b) {
if (b.name === a) {
if (last) {
b.id = item.id;
return true;
}
b.items = b.items || [];
o = b.items;
return true;
}
});
if (!found) {
if (last) {
o.push({ id: item.id, name: a });
} else {
o.push(oo);
o = oo.items;
}
}
});
});
document.write('<pre>' + JSON.stringify(tree, 0, 4) + '</pre>');
Related
I am having a problem trying to modify the name of a nested object using map function and to return the modified object.
I was trying the approach with double forEach loop but I am also failing with that.
const myObject = [{
id: 1,
childrenList: [{
id: 1,
name: 'foo',
},
{
id: 2,
name: 'foo',
},
],
},
{
id: 2,
childrenList: [{
id: 1,
name: 'foo',
},
{
id: 2,
name: 'foo',
},
],
},
];
const alteredObject = myObject.map((thisChild) => {
if (thisChild.id === 1) {
thisChild.childrenList.map((item) => {
if (item.id === 1) {
return {
...item,
name: 'bar',
};
}
return item;
});
}
return thisChild;
});
console.log(alteredObject);
//trying to get:
alteredObject = [
{
id: 1,
childrenList: [
{
id: 1,
name: 'bar',
},
{
id: 2,
name: 'foo',
},
],
},
{
id: 2,
childrenList: [
{
id: 1,
name: 'foo',
},
{
id: 2,
name: 'foo',
},
],
},
];
This is the first time I am trying to modify a nested object. Normally with an array of objects, I am not having any issue so I am not sure what I am doing wrong
You only need to update the children with your map and it will work. Like this:
const myObject = [
{
id: 1,
childrenList: [
{
id: 1,
name: "foo"
},
{
id: 2,
name: "foo"
}
]
},
{
id: 2,
childrenList: [
{
id: 1,
name: "foo"
},
{
id: 2,
name: "foo"
}
]
}
];
const alteredObject = myObject.map((thisChild) => {
if (thisChild.id === 1) {
thisChild.childrenList = thisChild.childrenList.map((item) => {
if (item.id === 1) {
return {
...item,
name: "bar"
};
}
return item;
});
}
return thisChild;
});
console.log(alteredObject);
And if you want to do it with forEach:
const myObject = [
{
id: 1,
childrenList: [
{
id: 1,
name: "foo"
},
{
id: 2,
name: "foo"
}
]
},
{
id: 2,
childrenList: [
{
id: 1,
name: "foo"
},
{
id: 2,
name: "foo"
}
]
}
];
const alteredObject = myObject.map((thisChild) => {
if (thisChild.id === 1) {
thisChild.childrenList.forEach((item) => {
if (item.id === 1) {
item.name = 'bar';
}
return item;
});
}
return thisChild;
});
console.log(alteredObject);
If you can modify your object then you can do it with two forEach:
const myObject = [
{
id: 1,
childrenList: [
{
id: 1,
name: "foo"
},
{
id: 2,
name: "foo"
}
]
},
{
id: 2,
childrenList: [
{
id: 1,
name: "foo"
},
{
id: 2,
name: "foo"
}
]
}
];
myObject.forEach((thisChild) => {
if (thisChild.id === 1) {
thisChild.childrenList.forEach((item) => {
if (item.id === 1) {
item.name = 'bar';
}
return item;
});
}
});
console.log(myObject);
As you already know, Array.prototype.map() returns a new Array containing the modified version.
In your first map function myObject.map(), you aren't saving the second map function modified result as the childrenList content.
therefore no changes would be stored in the first map function and the result would have no changes.
const alteredObject = myObject.map((thisChild) => {
if (thisChild.id === 1) {
// Here you should save the result of this
// Array.prototype.map() Function as the new 'thisChild.childrenList'
thisChild.childrenList = thisChild.childrenList.map((item) => {
// ...
});
}
return thisChild;
});
const myObject = [{
id: 1,
childrenList: [{
id: 1,
name: 'foo',
},
{
id: 2,
name: 'foo',
},
],
},
{
id: 2,
childrenList: [{
id: 1,
name: 'foo',
},
{
id: 2,
name: 'foo',
},
],
},
];
const alteredObject = myObject.map((thisChild) => {
if (thisChild.id === 1) {
thisChild.childrenList = thisChild.childrenList.map((item) => {
if (item.id === 1) {
return {
...item,
name: 'bar',
};
}
return item;
});
}
return thisChild;
});
console.log(alteredObject);
You can use this code :
const myObject = [
{
id: 1,
childrenList: [
{ id: 1, name: 'foo', },
{ id: 2, name: 'foo', },
],
},
{
id: 2,
childrenList: [
{ id: 1, name: 'foo', },
{ id: 2, name: 'foo', },
],
},
];
let result = myObject.map(
el => el.id === 1 ?
{...el, childrenList: el.childrenList.map(child => child.id === 1 ? {...child, name: 'bar'} : child)}
: el
);
console.log(result);
This can be done with a couple of map calls, we'll alter the name value if the firstChild id is 1 and the leaf object id is also 1:
const myObject = [ { id: 1, childrenList: [ { id: 1, name: 'foo', }, { id: 2, name: 'foo', }, ], }, { id: 2, childrenList: [ { id: 1, name: 'foo', }, { id: 2, name: 'foo', }, ], }, ];
const alteredObject = myObject.map((thisChild) => {
return { ...thisChild, childrenList: thisChild.childrenList.map(({id, name}) => {
return { id, name: (thisChild.id === 1 && id === 1) ? 'bar': name };
})}
});
console.log(alteredObject)
.as-console-wrapper { max-height: 100% !important; }
The array map method creates a new array (mdn), so the parent object alteredObject still has the childrenList key pointing to the original array.
To solve this, you can add assignment of the new array to the key:
thisChild.childrenList = thisChild.childrenList.map(...)
This way, the key will point to the newly created array
You're missing a return; you have to return the modified thisChild as {...thisChild, childrenList:modifiedChildrenList}
const myObject = [{ id: 1, childrenList: [{ id: 1, name: 'foo', }, { id: 2, name: 'foo', }, ], }, { id: 2, childrenList: [{ id: 1, name: 'foo', }, { id: 2, name: 'foo', }, ], }, ];
const alteredObject = myObject.map((thisChild) => {
if (thisChild.id === 1) {
return {...thisChild,childrenList:thisChild.childrenList.map((item) => {
if (item.id === 1) {
return {
...item,
name: 'bar',
};
}
return item;
})
}
}
return thisChild;
});
console.log(alteredObject);
I have a data structure as given below
ds: [
{ id: "0a12", pos: 0, name: "PH1" },
{
id: "8f83",
name: "PH2",
pos: 1,
children: [
{ id: "ll54", pos: 0, name: "L1" },
{
id: "97cs",
name: "L2",
pos: 1,
children: [
{ id: "80s3", pos: 0, name: "LL1" },
{ id: "2dh3", pos: 1, name: "LL2" },
],
},
],
},
{ id: "75fd", pos: 2, name: "PH3" },
{ id: "34jg", pos: 3, name: "PH4" },
],
I am creating a org chart wherein I have option to add a node to the left or right.
On clicking the node, I can get the node obj as an argument (ie) if I clicked on 'LL1' I can get that object, along with second arg called as type which will contain either 'left' or 'right'.
so now the type == 'left' then my resultant data structure should be like
ds: [
{ id: "0a12", pos: 0, name: "PH1" },
{
id: "8f83",
name: "PH2",
pos: 1,
children: [
{ id: "4", pos: 0, name: "L1" },
{
id: "5",
name: "L2",
pos: 1,
children: [
{ id: "36fd", pos: 0, name: "" }, // new node for type === 'left'
{ id: "80s3", pos: 1, name: "LL1" },
{ id: "2dh3", pos: 2, name: "LL2" },
],
},
],
},
{ id: "75fd", pos: 2, name: "PH3" },
{ id: "34jg", pos: 3, name: "PH4" },
],
and if type === 'right'
ds: [
{ id: "0a12", pos: 0, name: "PH1" },
{
id: "8f83",
name: "PH2",
pos: 1,
children: [
{ id: "4", pos: 0, name: "L1" },
{
id: "5",
name: "L2",
pos: 1,
children: [
{ id: "80s3", pos: 0, name: "LL1" },
{ id: "36fd", pos: 1, name: "" }, // new node for type === 'right'
{ id: "2dh3", pos: 2, name: "LL2" },
],
},
],
},
{ id: "75fd", pos: 2, name: "PH3" },
{ id: "34jg", pos: 3, name: "PH4" },
],
Note: for those nodes that don't have children we shouldn't initialize an empty array to children attribute
You can use Array.slice and spread operator to insert the new node into your array.
let data = [
{ id: '0a12', pos: 0, name: 'PH1' },
{
id: "8f83",
name: "PH2",
pos: 1,
children: [
{ id: '4', pos: 0, name: 'L1' },
{
id: '5',
name: 'L2',
pos: 1,
children: [
{ id: '80s3', pos: 0, name: 'LL1' },
{ id: '2dh3', pos: 1, name: 'LL2' },
],
},
],
},
{ id: '75fd', pos: 2, name: 'PH3' },
{ id: '34jg', pos: 3, name: 'PH4' },
];
function addNode(direction, idElement, ptr) {
const elementIndex = ptr.findIndex(x => x.id === idElement);
if (elementIndex === -1) {
// Go deeper
return ptr.map(x => x.children ? {
...x,
children: addNode(direction, idElement, x.children),
}: x);
}
const offset = direction === 'left' ? 0 : 1;
// Insert the new node in the correct position
const mutatedPtr = [
...ptr.slice(0, elementIndex + offset),
{ id: 'XXXX', pos: elementIndex + offset, name: '' },
...ptr.slice(elementIndex + offset),
];
// change the positions
mutatedPtr.forEach((x, xi) => {
x.pos = xi;
});
return mutatedPtr;
}
data = addNode('right', '75fd', data);
data = addNode('left', '75fd', data);
data = addNode('left', '2dh3', data);
data = addNode('right', '2dh3', data);
console.log(data);
This is a tree insertion problem. First you need to find the index to the anchor node and then insert a new node at index if type is "left" or index + 1 if type is "right". Here's a solution that works for your case. Note that you need to set the right "id" using whatever id generator you have.
ds = [
{ id: "0a12", pos: 0, name: "PH1" },
{
id: "8f83",
name: "PH2",
pos: 1,
children: [
{ id: "4", pos: 0, name: "L1" },
{
id: "5",
name: "L2",
pos: 1,
children: [
{ id: "80s3", pos: 0, name: "LL1" },
{ id: "2dh3", pos: 1, name: "LL2" }
]
}
]
},
{ id: "75fd", pos: 2, name: "PH3" },
{ id: "34jg", pos: 3, name: "PH4" }
];
// Returns parent & position
function findNode(childNodes, name) {
for (var ii = 0; ii < childNodes.length; ii++) {
let child = childNodes[ii];
if (child.name == name) {
return {
childNodes: childNodes,
index: ii
};
}
if (child.children) {
let result = findNode(child.children, name);
if (result) {
return result;
}
}
}
return null;
}
function rewritePositions(childNodes) {
for (var ii = 0; ii < childNodes.length; ii++) {
childNodes[ii].pos = ii;
}
}
function insertNode(name, type) {
let searchResult = findNode(ds, name);
if (!searchResult) {
return;
}
let index = searchResult.index;
let newPosition = type === "left" ? index : index + 1;
let newNode = { id: "todo: index", pos: newPosition, name: "" };
searchResult.childNodes.splice(newPosition, 0, newNode);
rewritePositions(searchResult.childNodes);
}
<script> var itemsTemp= [
{ id: 0, text: 'Andy' },
{
id: 1, text: 'Harry',
children: [
{ id: 2, text: 'David' }
]
},
{ id: 3, text: 'Lisa' },
{ id: 4, text: 'Mona' },
{ id: 5, text: 'Ron' },
{ id: 6, text: 'Joe' }
];
var items = itemsTemp;
var filtered = items.filter(function(item) {
return item.id !== 3;
});
console.log(filtered);
</script>
in this way, I can only remove the parent but how can I delete the child object? please help me to fix this
Since you want to filter children, you can use .reduce() to perform a mapping and filtering of your array. When you reach an object which has a children property, you can recursively call your function to then perform the mapping/filtering on the child array .reduce() array like so:
const items = [{ id: 0, text: 'Andy' }, { id: 1, text: 'Harry', children: [{ id: 2, text: 'David' }] }, { id: 3, text: 'Lisa' }, { id: 4, text: 'Mona' }, { id: 5, text: 'Ron' }, { id: 6, text: 'Joe' } ];
const filterItems = (items, fn) => items.reduce((acc, item) => {
if(item.children)
return [...acc, ...filterItems(item.children, fn)];
else if(fn(item))
return [...acc, item];
return acc;
}, []);
const filtered = filterItems(items, item => item.id !== 2);
console.log(filtered);
If you don't want to remove the item from the parent list, and only from the child list, then you push an update object instead:
const items = [{ id: 0, text: 'Andy' }, { id: 1, text: 'Harry', children: [{ id: 2, text: 'David' }] }, { id: 3, text: 'Lisa' }, { id: 4, text: 'Mona' }, { id: 5, text: 'Ron' }, { id: 6, text: 'Joe' } ];
const toRemoveId = 2;
const filterItems = (items, fn) => items.reduce((acc, item) => {
if(item.children)
return [...acc, {...item, children: filterItems(item.children, fn)}];
else if(fn(item))
return [...acc, item];
return acc;
}, []);
const filtered = filterItems(items, item => item.id !== 2);
console.log(filtered);
This will work for arbitrary object depths.
I just wrote the filterById function I think it works for your case
var itemsTemp = [
{ id: 0, text: "Andy" },
{
id: 1,
text: "Harry",
children: [{ id: 2, text: "David" }],
},
{ id: 3, text: "Lisa" },
{ id: 4, text: "Mona" },
{ id: 5, text: "Ron" },
{ id: 6, text: "Joe" },
];
var items = itemsTemp;
const filterById = (items, id) => {
return items.reduce((accumulator, currentValue) => {
if(currentValue.children){
const newCurrentValue = filterById(currentValue.children, id)
currentValue = {...currentValue, children: newCurrentValue}
}
if(currentValue.id !== id){
return [...accumulator, currentValue]
}
return accumulator
},[])
}
console.log(filterById(itemsTemp,2));
console.log(itemsTemp)
I think you can do like this.
var itemsTemp= [
{ id: 0, text: 'Andy' },
{
id: 1, text: 'Harry',
children: [
{ id: 2, text: 'David' }
]
},
{ id: 3, text: 'Lisa' },
{ id: 4, text: 'Mona' },
{ id: 5, text: 'Ron' },
{ id: 6, text: 'Joe' }
];
var items = itemsTemp;
var filtered = items.filter(function(item) {
childrens=item.children;
if(childrens)
{
filteredchildren = childrens.filter(children=>children.id!==2);
item.children=filteredchildren;
}
return item.id !== 2;
});
console.log(filtered);
I try to write mixin for underscore, which can find node by some params, for example:
_.findDeep(tree, {id: 5456, parent_id: 555})
Tree:
var tree = [
{
id: 22,
name: 'qqqq',
depth: 0,
parent_id: 11,
children: [
{
id: 222,
name: 'ttttt',
depth: 1,
parent_id: 444,
children: [],
positions: []
},
{
id: 5456,
name: 'yyyy',
depth: 1,
parent_id: 555,
children: [
{
id: 6767,
name: 'dfgfdg',
depth: 3,
parent_id: 6564,
children: [],
positions: []
},
{
id: 4345,
name: 'dfgdgfg',
depth: 3,
parent_id: 45234,
children: [],
positions: []
},
],
positions: []
},
],
positions: [
{
id: 14,
name: 'rere',
price: 20
},
{
id: 12,
name: 'tttyty',
price: 30
},
]
},
{
id: 33,
name: 'wwww',
depth: 0,
parent_id: 22,
children: [],
positions: []
},
{
id: 44,
name: 'eeee',
depth: 0,
parent_id: 33,
children: [],
positions: []
},
]
Wrong function, which alaways returns 'undefined', but console.log display founded node:
_.mixin({
findDeep: function(items, attrs) {
var key, n_key, n_value, result, value;
result = _.findWhere(items, attrs);
console.log(items, result, _.isUndefined(result));
if (_.isUndefined(result)) {
for (key in items) {
value = items[key];
for (n_key in value) {
n_value = value[n_key];
if (_.isObject(n_value) || _.isArray(n_value)) {
result = _.findDeep(n_value, attrs);
if (!_.isUndefined(result)) {
return result;
}
}
}
}
}
return result;
}
});
where is mistake? please help me
In your code, your
for (n_key in value) {
n_value = value[n_key];
if (_.isObject(n_value) || _.isArray(n_value)) {
_.findDeep(n_value, attrs);
}
}
is doing a deep search, but doesn't return any result. You should assign the result to the search, and if the result is not undefined, return it or break the for loop imediately.
So it becomes:
_.mixin({
findDeep: function(items, attrs) {
var key, n_key, n_value, result, value;
result = _.findWhere(items, attrs);
console.log(items, result, _.isUndefined(result));
if (_.isUndefined(result)) {
for (key in items) {
value = items[key];
for (n_key in value) {
n_value = value[n_key];
if (_.isObject(n_value) || _.isArray(n_value)) {
result = _.findDeep(n_value, attrs);
}
// Once you find the result, you can return the founded result
if (!_.isUndefined(result)) {
return result;
}
}
}
}
return result;
}
});
Snippet to show the correct result:
var tree = [
{
id: 22,
name: 'qqqq',
depth: 0,
parent_id: 11,
children: [
{
id: 222,
name: 'ttttt',
depth: 1,
parent_id: 444,
children: [],
positions: []
},
{
id: 5456,
name: 'yyyy',
depth: 1,
parent_id: 555,
children: [
{
id: 6767,
name: 'dfgfdg',
depth: 3,
parent_id: 6564,
children: [],
positions: []
},
{
id: 4345,
name: 'dfgdgfg',
depth: 3,
parent_id: 45234,
children: [],
positions: []
},
],
positions: []
},
],
positions: [
{
id: 14,
name: 'rere',
price: 20
},
{
id: 12,
name: 'tttyty',
price: 30
},
]
},
{
id: 33,
name: 'wwww',
depth: 0,
parent_id: 22,
children: [],
positions: []
},
{
id: 44,
name: 'eeee',
depth: 0,
parent_id: 33,
children: [],
positions: []
},
];
_.mixin({
findDeep: function(items, attrs) {
var key, n_key, n_value, result, value;
result = _.findWhere(items, attrs);
console.log(items, result, _.isUndefined(result));
if (_.isUndefined(result)) {
for (key in items) {
value = items[key];
for (n_key in value) {
n_value = value[n_key];
if (_.isObject(n_value) || _.isArray(n_value)) {
result = _.findDeep(n_value, attrs);
}
// Once you find the result, you can return the founded result
if (!_.isUndefined(result)) {
return result;
}
}
}
}
return result;
}
});
console.log(_.findDeep(tree, {id: 5456, parent_id: 555}));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
Below are two JSON arrays. I want to get delta data (compare the two datasets and return elements that don't appear in both sets).
var data1 = [
{ id: 1, name: "Normal" },
{ id: 2, name: "Admin" }
];
var data2 = [
{ id: 1, name: "Normal" },
{ id: 2, name: "Admin" },
{ id: 3, name: "HR" },
{ id: 4, name: "finance" }
];
expected output:
var Result = [
{ id: 3, name: "HR" },
{ id: 4, name: "finance" }
];
I have tried this but didn't have any luck:
$.grep(data2, function (el) {
if ($.inArray(el, data1) == -1)
diff.push([el, IDl]);
});
You are close, the problem is you need to do a deep compare of your objects. inArray will only do a shallow compare. The following code will do a deep compare by checking equality of id and name. Also, it allows jQuery.grep to build the resulting array so you do not need to do this manually.
var data1 = [
{ id: 1, name: "Normal" },
{ id: 2, name: "Admin" }
];
var data2 = [
{ id: 1, name: "Normal" },
{ id: 2, name: "Admin" },
{ id: 3, name: "HR" },
{ id: 4, name: "finance" }
];
function compare(data1, data2) {
return $.grep(data2, function(el) {
return !data1.some(function(elToCompare) {
return elToCompare.id === el.id && elToCompare.name === el.name;
});
});
}
$("#output").text(JSON.stringify(compare(data1, data2)));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<div id="output"></div>