searching a nested javascript object, getting an array of ancestors - javascript

I have a nested array like this:
array = [
{
"id": "67",
"sub": [
{
"id": "663",
},
{
"id": "435",
}
]
},
{
"id": "546",
"sub": [
{
"id": "23",
"sub": [
{
"id": "4",
}
]
},
{
"id": "71"
}
]
}
]
I need to find 1 nested object by its id and get all its parents, producing an array of ids.
find.array("71")
=> ["546", "71"]
find.array("4")
=> ["546", "23", "4"]
What's the cleanest way to do this? Thanks.

Recursively:
function find(array, id) {
if (typeof array != 'undefined') {
for (var i = 0; i < array.length; i++) {
if (array[i].id == id) return [id];
var a = find(array[i].sub, id);
if (a != null) {
a.unshift(array[i].id);
return a;
}
}
}
return null;
}
Usage:
var result = find(array, 4);
Demo: http://jsfiddle.net/Guffa/VBJqf/

Perhaps this - jsonselect.org.
EDIT: I've just had a play with JSONSelect and I don't think it's appropriate for your needs, as JSON does not have an intrinsic 'parent' property like xml.
It can find the object with the matching id, but you can't navigate upwards from that. E.g.
JSONSelect.match(':has(:root > .id:val("4"))', array)
returns me:
[Object { id="4"}]
which is good, it's just that I can't go anywhere from there!

Related

Javascript test for existence of object key in a object with arrays which use values instead of index

I have been reading through many of the great code examples which test for the existence of object key in a object with arrays. These are great...
My problem is the JSON returned has key value that must be used to get to the items inside the array. Here is an example. Look at "orders":
{"Routes": [
{
"route": {
"id": "1daf1f53-80b6-49d6-847a-0ee8b814e784-20180821"
},
"vehicle": {
"id": "1daf1f53-80b6-49d6-847a-0ee8b814e784"
},
"driver": {
"id": "6c2823be-374e-49e5-9d99-2c3f586fc093"
},
"orders": {
"6df85e5f-c8bc-4290-a544-03d7895526b9": {
"id": "6df85e5f-c8bc-4290-a544-03d7895526b9",
"delivery": {
"customFields": {
"custom": "5379"
}
},
"isService": true
}
}
}
]
};
The code I am using works up to the point where I have to specify the key value:
function checkProperty(obj, prop) {
var parts = prop.split('.');
for (var i = 0, l = parts.length; i < l; i++) {
var part = parts[i];
if (obj !== null && typeof obj === "object" && part in obj) {
obj = obj[part];
} else {
return false;
}
return true;
}
Here are some samples that work and fail:
console.log(checkProperty(test, 'Routes.0.orders')); //Works returns true
console.log(checkProperty(test, 'Routes.0.orders.id')); //Fails returns false
console.log(checkProperty(test, 'Routes.0.orders.6df85e5f-c8bc-4290-a544-03d7895526b9.id)); //Fails returns false
I am at my wits end and would appreciate any help...
"orders": {
"6df85e5f-c8bc-4290-a544-03d7895526b9": {
"id": "6df85e5f-c8bc-4290-a544-03d7895526b9"
second test:
Id is not a direct child of "Orders" in your example:
orders.6df85e5f-c8bc-4290-a544-03d7895526b9.id
third test:
syntax error in the third - missing '.'

indexOf on array of object instead of array [duplicate]

This question already has answers here:
Get the index of the object inside an array, matching a condition
(16 answers)
Closed 5 years ago.
I know to find a value exist or not in an array I can use indexOf, but how to do it with an array of object?
const x = [{
"id": "roadshows",
"name": "Roadshows"
}, {
"id": "sporting_events",
"name": "Sporting Events"
}]
console.log( x.indexOf('roadshows') ) // don't work
Since this is tagged ecmascript-6, here's an ES6 array method: Array#findIndex():
const x = [{
"id": "roadshows",
"name": "Roadshows"
}, {
"id": "sporting_events",
"name": "Sporting Events"
}]
console.log( x.findIndex( o => o.id === 'roadshows' ) )
If you want a more re-useable way of doing this, consider creating a factory isId(id):
function isId(id) {
return (o) => o.id === id;
}
const x = [{
"id": "roadshows",
"name": "Roadshows"
}, {
"id": "sporting_events",
"name": "Sporting Events"
}]
console.log( x.findIndex( isId('roadshows') ) )
This is referred to as a "factory" because it is a function that returns a function with the passed parameter in its scope.
You have to loop through since you have object's inside array.
for(var i = 0; i < x.length; i++) {
if (x[i].id== 'roadshows') {
console.log(i);
break;
}
}
Or if you just checking that the object exist with that id, filter is handy
if (x.filter(function(e) x.id== 'roadshows').length > 0) {
// Yay. Do Something
}
manually I'd do something like this:
for(let item of x) {
if ( item.hasOwnProperty('id') && item['id'] == 'roadshows' ) {
//do your stuff here
}
}
And if you can use es6 and want to return the object in question, then there is always Array.prototype.find()
x.find( item => { return item.id === "roadshows" } )
// returns {id: "roadshows", name: "Roadshows"}
You have a couple of options.
First and foremost, findIndex. You pass it a function that tests if an element is what you are looking for, it returns the index of the first element that makes that function return true.
x.findIndex((o) => o.id === 'roadshows');
const x = [{
"id": "roadshows",
"name": "Roadshows"
}, {
"id": "sporting_events",
"name": "Sporting Events"
}];
console.log(x.findIndex((o) => o.id === 'roadshows'));
Another option is first mapping the relevant property to an array and searching in that one.
x.map((o) => o.id).indexOf('roadshows');
const x = [{
"id": "roadshows",
"name": "Roadshows"
}, {
"id": "sporting_events",
"name": "Sporting Events"
}];
console.log(x.map((o) => o.id).indexOf('roadshows'));

How to add a new key to multiple indices of an array of objects?

I've got an array of three people. I want to add a new key to multiple objects at once based on an array of indices. Clearly my attempt at using multiple indices doesn't work but I can't seem to find the correct approach.
var array = [
{
"name": "Tom",
},
{
"name": "Dick",
},
{
"name": "Harry",
}
];
array[0,1].title = "Manager";
array[2].title = "Staff";
console.log(array);
Which returns this:
[
{
"name": "Tom",
},
{
"name": "Dick",
"title": "Manager"
},
{
"name": "Harry",
"title": "Staff"
}
]
But I'd like it to return this.
[
{
"name": "Tom",
"title": "Manager"
},
{
"name": "Dick",
"title": "Manager"
},
{
"name": "Harry",
"title": "Staff"
}
]
You cannot use multiple keys by using any separator in arrays.
Wrong: array[x, y]
Correct: array[x] and array[y]
In your case, it will be array[0].title = array[1].title = "manager";
1st method::
array[0].title = "Manager";
array[1].title = "Manager";
array[2].title = "Staff";
array[0,1] will not work.
2nd method::
for(var i=0;i<array.length;i++) {
var msg = "Manager";
if(i===2) {
msg = "Staff"
}
array[i].title = msg
}
You can use a helper function like this
function setMultiple(array, key, indexes, value)
{
for(i in array.length)
{
if(indexes.indexOf(i)>=0){
array[i][key] = value;
}
}
}
And then
setMultiple(array, "title", [0,1], "Manager");
Try this: `
for (var i=0; var<= array.length; i++){
array[i].title = "manager";
}`
Or you can change it around so var is less than or equal to any n range of keys in the index.
EDIT: instead make var <= 1. The point is to make for loops for the range of indices you want to change the title to.
Assuming that you have a bigger set of array objects.
var array = [
{
"name": "Tom",
},
{
"name": "Dick",
},
{
"name": "Harry",
},
.
.
.
];
Create an object for the new keys you want to add like so:
let newKeys = {
'Manager': [0,2],
'Staff': [1]
}
Now you can add more such titles here with the required indexes.
with that, you can do something like:
function addCustomProperty(array, newKeys, newProp) {
for (let key in newKeys) {
array.forEach((el, index) => {
if (key.indexOf(index) > -1) { // if the array corresponding to
el[newProp] = key // the key has the current array object
} // index, then add the key to the
}) // object.
}
return array
}
let someVar = addCustomProperty(array, newKeys, 'title')

Nested JSON find item

I have the following valid JSON. It describes a tree structure:
{
"items": [
{
"id": "d1"
},
{
"id": "2",
"children": [
{
"id": "3"
},
{
"id": "4"
},
{
"id": "5",
"children": [
{
"id": "6"
},
{
"id": "7",
"children": [
{
"id": "8"
},
{
"id": "9"
}
]
},
{
"id": "10"
}
]
},
{
"id": "11"
},
{
"id": "12"
}
]
},
{
"id": "13"
},
{
"id": "14"
}
]
}
I need to be able to get any of the "items" by id and any of the child items. For example. Initially I tried grep:
var returnedData = $.grep(obj.items, function(element, index){return element.id == "2";
});
This worked great for item with id==2 but fails completely when I try to obtain element.id=="7"
Any assistance would be appreciated. Thanks in advance.
You can make a recursive function to search in the data:
function find(source, id)
{
for (key in source)
{
var item = source[key];
if (item.id == id)
return item;
// Item not returned yet. Search its children by recursive call.
if (item.children)
{
var subresult = find(item.children, id);
// If the item was found in the subchildren, return it.
if (subresult)
return subresult;
}
}
// Nothing found yet? return null.
return null;
}
// In the root object, the array of items is called 'items', so we pass in
// data.items to look into. The root object itself doesn't seem to have an id anyway.
var result = find(data.items, 7);
// Show the name of item 7, if it had one...
alert(result.name);
Demo: http://jsfiddle.net/rj26H/
In this function I just looped over the object, so its a bit more verbose. You could probably also use $.grep to do the searching and make the code a bit smaller. Anyway, the trick is to search all children if the item is not found on the main level. Apparently grep doesn't work in a recursive fashion.
Try this:
var id = 7;
var data = {"items": [{"id": "d1"},{"id": "2","children": [{"id": "3"},{"id": "7"},{"id": "11"},{"id": "12"}]}]};
function search(values) {
$.each(values, function(i, v) {
if (v.id == id) {
console.log('found', v);
return false;
}
if (v.children) {
search(v.children);
}
});
}
search(data.items);
Demo Link
I know this have been already answered, but I wanted to show how you could leverage the new the new JavaScript 1.7 features to solve this. Please note that the same approach could have been used without support for generators, but the code would have been longer.
//Returns an iterator that knows how to walk a tree
function treeIterator(root, childGetter, childCountGetter) {
let stack = [root], node;
while (node = stack.pop()) {
yield node;
for (let i = childCountGetter(node); i--;) stack.push(childGetter(node, i));
}
}
//Our custom search function
function findNodeById(tree, id) {
let it = treeIterator(tree,
function (node, i) { return node.children[i]; },
function (node) { return node.children? node.children.length : 0; }
);
for (let node in it) if (node.id === id) return node;
return null;
}
var tree = {
id: 'root',
children: [
{ id: 'a' },
{
id: 'b',
children: [
{ id: 'b1' },
{ id: 'b2' }
]
},
{ id: 'c' }
]
};
findNodeById(tree, 'b1'); //Object { id="b1"}
Note that you can also set the __iterator__ on the data structure so that functions that needs to iterate over this data structure do not have to know implementation details.
tree.__iterator__ = treeIterator.bind(null, tree,
function (node, i) { return node.children[i]; },
function (node) { return node.children? node.children.length : 0; }
);
Then the findNodeById function can be:
function findNodeById(tree, id) {
for (let node in it) if (node.id === id) return node;
return null;
}

JSON tree to parent-link structure

I have a JSON tree structure:
nodes =
[
{
"name": "user1",
"children": [
{
"name": "user2"
},
{
"name": "user3",
"children": [
{
"name": "user4"
}
]
},
{
"name": "user5"
}
]
}
]
that I would like to convert to a parent-link structure:
[{"name": "user1","parent": "null"},
{"name": "user2","parent": "user1"},
{"name": "user3","parent": "user1"},
{"name": "user4","parent": "user3"},
{"name": "user5","parent": "user1"}]
I have tried to traverse the tree recursively but without success accessing the parent object:
rebuild(nodes,parentLink);
function parentlink(key,value) {
var obj = { name: value , parent: ??? };
if (key == "name"){
nodes.push(obj);
}
}
function rebuild(o,func) {
for (i in o) {
func.apply(this,[i,o[i]])
if (typeof(o[i])=="object") {
traverse(o[i],func,nodes);
}
}
}
In developer tools I can see the parent objects for each child, but I don't know how to access them. What should I do to add the parent to each user?
I'm not gonna lie, I didn't bother looking at your code - this is how I would do it:
http://jsfiddle.net/J6G2W/1/
function processChildren(item, ret, parent) {
for (var i = 0; i < item.length; i++) {
var cur = item[i];
var cur_name = cur.name;
ret.push({"user": cur_name, "parent": parent});
if ("children" in cur && cur.children.length > 0) {
processChildren(cur.children, ret, cur_name);
}
}
}
var all = [];
processChildren(nodes, all, null);
console.log(JSON.stringify(all));
Output is:
[{"user":"user1","parent":null},{"user":"user2","parent":"user1"},{"user":"user3","parent":"user1"},{"user":"user4","parent":"user3"},{"user":"user5","parent":"user1"}]
Which seems to be what you're looking for. You're welcome to modify what of my code to work more like yours, I just thought I'd share what I'd do :)
UPDATE:
If, for some reason, you want to make it more extendable, you can customize which keys are the "name" and which are the "children"...for example:
http://jsfiddle.net/J6G2W/2/
function startProcess(item, ret, key_look, children_look, parent) {
function processChildren(item2, ret2, parent2) {
for (var i = 0; i < item2.length; i++) {
var cur = item2[i];
var cur_name = key_look in cur ? cur[key_look] : null;
ret.push({"user": cur_name, "parent": parent2});
if (children_look in cur && cur[children_look].length > 0) {
processChildren(cur[children_look], ret, cur_name);
}
}
}
processChildren(item, ret, parent);
}
var all = [];
startProcess(nodes, all, "name", "children", null);
console.log(JSON.stringify(all));
Notice how you only have to specify the key_look, children_look arguments once. The inner function can access those parameters while only passing the important things each recursion. This probably isn't important, I just wanted to figure it out :)

Categories