how to find all leaves of group with javascript and json - javascript

I want to list all leaves ids where group name is i.e. "group110"
So, output for this example is 014, 288, 223 and 244.
Here is content of my JSON file:
{
"name": "visualization",
"children": [
{
"name": "group100",
"children": [
{
"name": "group110",
"children": [
{
"name": "group111",
"children": [
{"id":"014","link":"-","name":" Animals/70","decade":"-"}
]
},
{
"name": "group112",
"children": [
{"id":"288","link":"-","name":"Heidelberg platen press","decade":"1960s"}
]
},
{
"name": "group113",
"children": [
{"id":"223","link":"-","name":"Camera Praktica Super TL – shutter release","decade":"1960s"},
{"id":"244","link":"-","name":"Mechanical calculator, Facit","decade":"1950s"}
]
}
]
},

Try this way. Find the group using a recursive method and collect leaf nodes using another recursive method.
function getLeafNodes(leafNodes, obj){
if(obj.children){
obj.children.forEach(function(child){getLeafNodes(leafNodes,child)});
} else{
leafNodes.push(obj);
}
}
function findIds(json,name){
if(json.children){
if(json.name==name) {
var leafNodes = [];
getLeafNodes(leafNodes,json);
console.log(leafNodes.map(function(leafNode){ return leafNode.id; })); //Logs leaf node ids to the console
} else {
json.children.forEach(function(child){
findIds(child,name);
});
}
}
}
Execution of following code will print ["014", "288", "223", "244"]
findIds(actualJSON,"group110");

The following code traverses the tree recursively. If param has children, then its children will be traversed. Otherwise, its id will be appended to the results array, thus, at the end results will contain the id of the leaves. getResults returns results to simplify its usage.
var results = [];
function getResults(param) {
if (!!param.children) {
for (var child in param.children) {
getResults(param.children[child]);
}
} else {
results[results.length] = param.id;
}
return results;
}

Here is a generic terse recursive answer for finding nodes using some jquery ($.map).
Watch out for stack overflows if the data is deep though!
Also it will not continue searching inside a matching node for more matching sub nodes, so it's only applicable if the search term does not nest logically.
This method makes use of the array flattening feature of $.map.
var found = (function walk(obj, searchKey, searchTerm) {
if(!$.isPlainObject(obj)) return null;
return obj[searchKey] === searchTerm ? [obj] : $.map(obj, function (lev) {
return walk(lev, searchKey, searchTerm);
});
})(data, 'name', 'group110');
Expanding on that to solve the specific problem above...
var found = (function walk(obj, searchTerm) {
if(!$.isPlainObject(obj)) return null;
return obj.name == searchTerm
? $.map(obj.children, function(c){
return $.map(c.children, function(f){ return f.id; }); })
: $.map(obj.children, function (lev) {
return walk(lev, searchTerm); });
})(data, 'group110');
Or rather
var found = (function walk(obj, lambda, term) {
if(!($.isPlainObject(obj) || $.isArray(obj))) return null;
return lambda.call(obj, term)
? $.map(obj.children, function(c){
return $.map(c.children, function(f){ return f.id; }); })
: $.map(obj.children, function (lev) {
return walk(lev, searchTerm); });
})(data, function(a){ return this.name == a; }, 'group110');

Related

How to return few values using map

I have function that loops array and I have four if's - if it match I push value to output array:
const generate = (resources, resourceId) => {
let output = [];
for (let i = 0; i < resources.length; i++) {
if (resources[i].id === resourceId) {
if (CREATE & resources[i].privileges) {
output.push(CREATE);
}
if (READ & resources[i].privileges) {
output.push(READ);
}
if (UPDATE & resources[i].privileges) {
output.push(UPDATE);
}
if (DELETE & resources[i].privileges) {
output.push(DELETE);
}
}
}
return output;
};
I want to change this function to use map - is it possible? I try to do something like this:
const generateUsingMap = (resources, resourceId) => {
return resources.map((resource) => {
if (resource.id === resourceId) {
if (CREATE & resource.privileges) {
return CREATE;
}
if (READ & resource.privileges) {
return READ;
}
if (UPDATE & resource.privileges) {
return UPDATE;
}
if (UPDATE & resource.privileges) {
return UPDATE;
}
}
});
};
But in this case I will have only one value, because it returns from first if.
Maybe I need to use another function? I don't want to use for or forEach because in that cases I need to create unnecessary variable.
Update
My function is working in loop, function receive 2 arguments resources and resourceId.
For example variable resources contains:
{
"id": "1",
"name": "Test name 1",
"privileges": 1
},
{
"id": "2",
"name": "Test name 2",
"privileges": 2
},
{
"id": "3",
"name": "Test name 3",
"privileges": 8
},
{
"id": "4",
"name": "Test name 4",
"privileges": 0
},
{
"id": "5",
"name": "Test name 5",
"privileges": 15
}
]
Variable resourceId contains number (id) and receive severally values, for example on first iteration 1, for second 2 and so on.
For resources from example expected output will be:
[1]
[2]
[8]
[]
[1,2,4,8]
You can use reduce if both resource object id and privileges do not exist do not check further just return what you already accrued.
Only if both are present then check the CRUD operations.
const result = resources.reduce((output) => {
if (resources[i].id !== resourceId && !resources[i].privileges) {
return output;
}
if (CREATE) {
output.push(CREATE);
}
if (READ) {
output.push(READ);
}
if (UPDATE) {
output.push(UPDATE);
}
if (DELETE) {
output.push(DELETE);
}
return output;
}, [])
const generateUsingMap = (resources, resourceId) => {
return resources.filter(resource => resource.id === resourceId)
.map((resource) => {
if (CREATE & resource.privileges) {
return CREATE;
}
if (READ & resource.privileges) {
return READ;
}
if (UPDATE & resource.privileges) {
return UPDATE;
}
if (UPDATE & resource.privileges) {
return UPDATE;
}
});
};
Create an empty array resultArr. Iterate through resources via forEach and append options to it. Return the array at the end of the function
const generateUsingMap = (resources, resourceId) => {
const resultArr = [];
resources.forEach((resource) => {
if (resource.id === resourceId && resource.privileges) {
if (CREATE) {
resultArr.push(CREATE);
}
else if (READ) {
resultArr.push(READ);
}
else if (UPDATE) {
resultArr.push(UPDATE);
}
else if (DELETE) {
resultArr.push(DELETE);
}
}
});
return resultArr;
};

Combining multiple objects to create JSON object in Node

I am trying to construct my own JSON object from multiple online image/photography sources. The below code should explain what I am trying to accomplish:
var searchUnsplash = require('./apis/unsplash');
var searchFlickr = require('./apis/flickr');
function combineObjs(callback) {
var obj = {}
var key = 'item';
obj[key] = [];
searchFlickr.searchFlickr(searchTerm, searchCount, searchPage,
function (callback) { // each API call is in a separate file with these functions exported
obj[key].push(flickrObj); // this does not work
// console.log(flickrObj) // this works
});
searchUnsplash.searchUnsplash(searchTerm, searchCount, searchPage,
function (callback) {
obj[key].push(unsplashObj);
// console.log(unsplashObj)
});
console.log(obj)
}
combineObjs();
The goal is to end up with a JSON object like below:
["item": {
"id": 1,
"title": 2,
"content": 3,
"source": "flickr"
},
"item": {
"id": 1,
"title": 2,
"content": 3,
"source": "unsplash"
}]
etc, which I can use to power my front end.
I am a beginner to javascript and I am just working off what I have learned in tutorials and articles, so I might be using the wrong approach entirely for what I am aiming to achieve. Happy to take any pointers
search function:
function searchUnsplash(term, count, page, callback) {
request(`https://api.unsplash.com/search/photos/?per_page=${count}&page=${page}&query="${term}"&client_id=KEY&`,
function searchResult(error, response, body) {
if (!error && response.statusCode == 200) {
var info = JSON.parse(body)
for ( var i = 0; i < info.results.length; i++) {
obj = {
id: `us-${info.results[i].id}`,
}
callback(obj);
}
}
})
}
module.exports.searchUnsplash = searchUnsplash;
First, your intended result is not correct. You can't name "item" the individual array entries. A corrected and working example would be this one.
[ {
"id": 1,
"title": 2,
"content": 3,
"source": "flickr"
},
{
"id": 1,
"title": 2,
"content": 3,
"source": "unsplash"
}]
Second, you mistake JSON for your data structure. JSON is just the text notation. So, let's see first how to build a suitable data array.
let results = [];
results.push( { id:1, title:2, content:3, source:"flickr" });
results.push( { id:2, title:4, content:6, source:"unsplash" });
And then with JSON.stringify(results) will code your results into JSON.
Finally, you mix up the aynchronous calls in your code with synchronous invocations. You need to save the results on the callback of the individual functions, that is when you really obtain the responses asynchronously. Also, you need to count the pending results and invoke the final callback when all done.
So, putting all the pieces together, in a contrived fake example, we just invoke twice the search functions and so we callback when two results are combined.
function combineObjs(callback) {
let results = [];
function partialResult(obj) {
results.push(obj);
if (results.length=2) callback(results);
};
searchFlickr(searchTerm, searchCount, searchPage, partialResult);
searchUnsplash(searchTerm, searchCount, searchPage,partialResult);
}
combineObjs( function(results) { console.log(JSON.stringify(results)) });
This is excessive but it would work. It can be used over and over and over again. :D
Run the snippet to see a result
class JSONBuilder
{
constructor(contents=null)
{
if(!contents)
{
//Private objecy hash that isn't publicly accessible
var objectHash = {};
//Get stashed item
this.GetItem = function(key)
{
if(!key) throw new Error("Null or Underfined Key Passed.");
let value = objectHash[key];
if(!value) throw new Error(`Key : ${key} Not Found in JSON objectHash`);
return value;
}
//Set an item in the objecy hash
this.SetItem = function(key, value)
{
if(!key) throw new Error("Null or Underfined Key Passed.");
if(!value) throw new Error("Null or Underfined Key Not Found.");
objectHash[key] = value;
}
//Remove item from the hash
this.DeleteItem = function(key)
{
if(!key) throw new Error("Null or Underfined Key Passed.");
if(!objectHash[key]) throw new Error(`Key : ${key} Not Found in JSON objectHash`);
objectHash.DeleteItem(key);
}
//Turn items into a JSON object
this.Build = function()
{
return JSON.stringify(objectHash);
}
}
else
{
//If a string is passed as a paremeter, reconstruct from that
try
{
objectHash = JSON.parse(contents);
}
catch(Err)
{
console.log("Parsing of JSON Content Failed.");
throw Err;
}
}
}
}
class Item
{
constructor(id, source, content, title)
{
this.Id = id;
this.Source = source;
this.Content = content;
this.Title = title;
}
}
let builder = new JSONBuilder();
let itemContainer = [];
itemContainer.push(new Item(1, 'flicker', 'stuff', 'item1'));
itemContainer.push(new Item(2, 'flicker', 'morestuff', 'item2'));
builder.SetItem('items', itemContainer);
console.log(builder.Build());

Javascript array.find() only finds within first instance

I have the following object array:
var data = {};
data.type = {
"types": [{
"testA": {
"testVar": "abc",
"testContent": "contentA"
}
}, {
"testB": {
"testVar": "def",
"testContent": "contentB"
}
}]
};
What I'm trying to do is find the value of testContent based on finding the object it belongs by searching it's parent and sibling:
/* within the data, find content where parent is testA and sibling testVar is "abc" */
var findSet = data.type.types.find(function(entry) {
return entry['testA'].testVar === "abc";
});
console.log(findSet['testA'].testContent); /* returns string "contentA" as expected */
This works fine for first object but fails to find next object, giving error:
Cannot read property 'testVar' of undefined
var findSet = data.type.types.find(function(entry) {
return entry['testB'].testVar === "def"; /* Cannot read property 'testVar' of undefined */
});
console.log(findSet['testB'].testContent);
How else could I find what's needed?
Here's a fiddle to test the output
var data = {};
data.type = {
"types": [{
"testA": {
"testVar": "abc",
"testContent": "contentA"
}
}, {
"testB": {
"testVar": "def",
"testContent": "contentB"
}
}]
};
var findSet = data.type.types.find(function(entry) {
return entry['testA'] && entry['testA'].testVar === "abc";
});
console.log(findSet['testA'].testContent);
var findSet = data.type.types.find(function(entry) {
return entry['testB'] && entry['testB'].testVar === "def"; /* Cannot read property 'testVar' of undefined */
});
console.log(findSet['testB'].testContent);
just check if your entry exist before testing his attribute.

Filter/Map/Reduce properly

I'm trying to filter a users JSON via JavaScript's filter, map, and reduce methods. However I cannot get the exact result I pretend.
var users = {
"fooUser": {
"apps": [
{
"id": "7i2j3bk85"
},
{
"id": "o8yasg69h"
}
]
},
"barUser": {
"apps": [
{
"id": "789gbiai7t"
}
]
}};
The logic is: I only know the AppId (and not the User it belogs to), so I'd have to map/filter each User, and return it ONLY if it has that Appid (and return ONLY that AppId).
var filteredApps = Object.keys(users).filter(function (element, index, array) {
var exists = users[element].apps.filter(function (element, index, array) {
if (element.id === 'o8yasg69h') {
return true;
} else {
return false;
}
});
if (exists[0]) {
return true;
} else {
return false;
}
}).map(function (item, index, array) {
return users[item].apps;
});
console.log(filteredApps);
I obtain (a multiArray with no-filtered Apps):
[[
{
id: "7i2j3bk85"
},
{
id: "o8yasg69h"
}
]]
But I would like to obtain (one plain Object, with the filtered App):
{
id: "o8yasg69h"
}
You can do this with the following one-liner:
[].concat(...Object.keys(users).map(x=> users[x].apps)).find(x=> x.id === "o8yasg69h")
To expand it a bit:
[].concat(... // flattens the array of apps
Object.keys(users) // gets the keys of users
.map(x=> users[x].apps) // maps the array to the apps of the user
).find(x=> x.id === "o8yasg69h") // finds app which id is "o8yasg69h"
I'd do it with reduce and ES6 find:
function searchById(id){
return Object.keys(users).reduce(function(result, user){
return result ? result : users[user].apps.find(function(obj){
return obj.id === id;
});
}, false);
}

Tree Recursion: How to get the parent root of the selected tree node

I have a tree-like structure of a json object
{
"saxena": {
"chewning": {
"betten": {},
"ching": {},
"kelley": {}
},
"kobrinsky": {
"karniely": {},
"naveh": {},
"rozenfeld": {},
"shalom": {}
},
"schriever": {
"brinker": {},
"mcleland": {},
"merrick": {}
},
"vacant": {
"akers": {},
"carlton": {
"marvin": {}
},
"fox": {
"glover": {
"clements": {},
"koya": {}
},
"holden": {}
}
}
},
"bill": {
"phil": {
"bob": {},
"smith": {},
"hello": {}
},
"bye": {
"ok": {},
"hmm": {},
"no": {},
"alright": {}
}
}
}
The root names are saxena and bill. I would like to create a function that can determine the root name of who the user searches for.
For the most simplest case, if they search for saxena, it returns saxena. If they return bill, it returns bill.
For a more complex case, saxena will be returned if the user searches for any of the names under her.
For example, if I search for betten, akers, glovers, or koya, saxena will be returned.
And if I search for bob, smith, or alright, bill will be returned.
This is my work so far. I tried using recursion, but for some reason when I find the selected name, I return an undefined.
var findRootName = function(data, ltmName) {
for (var key in data) {
if (key == ltmName) {
return key;
} else {
findNode(data[key], ltmName);
}
}
}
var findNode = function(data, ltmName) {
for (var key in data) {
if (key == ltmName) {
return key;
} else {
findNode(data[key], ltmName);
}
}
}
http://jsfiddle.net/gthnfta7/7/
Can somebody help me and figure out why my recursive function isn't working?
The problem is that you're not returning anything in the event that the node is found. You can simplify your function by writing it like this:
var findParent = function(data, childName) {
for (var key in data) {
if (key === childName || findParent(data[key], childName)) {
return key;
}
}
};
An alternative technique, if you need to make many calls over the same data, is something like the following:
function makeSearcher(data) {
var paths = (function makePaths(data, parentPath, store) {
var path = parentPath || [];
results = store || {};
Object.keys(data).forEach(function(key) {
var newPaths = path.concat(key);
results[key] = newPaths;
makePaths(data[key], newPaths, results);
});
return results;
})(data);
return function(key) {
var path = paths[key];
return path && path[0];
};
}
var search = makeSearcher(data);
search('clements'); //=> 'savena'
Note that the internal makePaths function is broader than the use here, as it could also be use to return a result like
[ "saxena", "vacant", "fox", "glover", "clements" ]

Categories