Filter object containing array of object - javascript

I would like to filter my data depending on a typed keyword.
https://jsfiddle.net/LeoCoco/e96L8akn/
let keyword = '-pre';
let data = {
'Basic': [{
name: 'a-basic'
}, {
name: 'b-basic'
}, {
name: 'c-basic'
}],
'Premium': [{
name: 'a-pre'
}, {
name: 'b-pre'
}, {
name: 'c-pre'
}],
'Extra': [{
name: 'a-ext'
}, {
name: 'b-ext'
}, {
name: 'c-ext'
}],
};
Output
'Premium': [{name: 'a-pre'}, { name: 'b-pre'}, { name: 'c-pre'}]
My try
lodash.forEach(data, (card) => {
card = card.filter(o => {
return Object.keys(o).some(k => {
return typeof o[k] === 'string' && o[k].toLowerCase().includes(keyword.toLowerCase());
});
});
})
But it does not work.The difficulty for me is that the filtering must happen on the nested object keys contained in each array.

var result={};
Object.keys(data).forEach(key => {
result[key] = data[key].filter(o => {
return Object.keys(o).some(k =>typeof o[k] === 'string' && o[k].toLowerCase().includes(keyword.toLowerCase()));
});
})

Because this is object you can use reduce() on Object.keys() instead and then inside use every() to check for keyword.
let keyword = '-pre';
let data = {"Basic":[{"name":"a-basic"},{"name":"b-basic"},{"name":"c-basic"}],"Premium":[{"name":"a-pre"},{"name":"b-pre"},{"name":"c-pre"}],"Extra":[{"name":"a-ext"},{"name":"b-ext"},{"name":"c-ext"}]}
var result = Object.keys(data).reduce(function(r, e) {
var check = data[e].every(o => o.name.indexOf(keyword) != -1);
if(check) r[e] = data[e]
return r;
}, {})
console.log(result)

Related

Separating (n) keys from array of objects into a single array with keys names

I need to perform filter in the array of objects to get all the keys. Although, whenever there is a obj inside of that key, I would need to get the key name and concat with the key name from the obj, so for example:
const data = [ id: 5, name: "Something", obj: { lower: True, higher: False } ]
result = ["id", "name", "obj.lower", "obj.higher"]
I could manage to do the above code, but, if there is more objs inside the data, I would need to keep adding a if condition inside of my logic, I would like to know if there is any other way, so it doesn't matter how many objects I have inside the objects, It will concat always.
The code I used from the above mention:
const itemsArray = [
{ id: 1, item: "Item 001", obj: { name: 'Nilton001', message: "Free001", obj2: { test: "test001" } } },
{ id: 2, item: "Item 002", obj: { name: 'Nilton002', message: "Free002", obj2: { test: "test002" } } },
{ id: 3, item: "Item 003", obj: { name: 'Nilton003', message: "Free003", obj2: { test: "test003" } } },
];
const csvData = [
Object.keys(itemsArray[0]),
...itemsArray.map(item => Object.values(item))
].map(e => e.join(",")).join("\n")
// Separating keys
let keys = []
const allKeys = Object.entries(itemsArray[0]);
for (const data of allKeys) {
if (typeof data[1] === "object") {
const gettingObjKeys = Object.keys(data[1]);
const concatingKeys = gettingObjKeys.map((key) => data[0] + "." + key);
keys.push(concatingKeys);
} else {
keys.push(data[0])
}
}
//Flating
const flattingKeys = keys.reduce((acc, val: any) => acc.concat(val), []);
What I would like to achieve, lets suppose I have this array of object:
const data =
[
{ id: 10, obj: {name: "Name1", obj2: {name2: "Name2", test: "Test"}}}
...
]
Final result = ["id", "obj.name", "obj.obj2.name2", "obj.obj2.test"]
OBS: The first obj contains all the keys I need, no need to loop through other to get KEYS.
I would like to achieve, all the keys from the first object of the array, and if there is objects inside of objects, I would like to concat the obj names (obj.obj2key1)
You could map the key or the keys of the nested objects.
const
getKeys = object => Object
.entries(object)
.flatMap(([k, v]) => v && typeof v === 'object'
? getKeys(v).map(s => `${k}.${s}`)
: k
),
getValues = object => Object
.entries(object)
.flatMap(([k, v]) => v && typeof v === 'object'
? getValues(v)
: v
),
data = { id: 1, item: "Item 001", obj: { name: 'Nilton001', message: "Free001", obj2: { test: "test001" } } },
keys = getKeys(data),
values = getValues(data);
console.log(keys);
console.log(values);
.as-console-wrapper { max-height: 100% !important; top: 0; }
something like this
const itemsArray = [
{ id: 1, item: "Item 001", obj: { name: 'Nilton001', message: "Free001", obj2: { test: "test001" } } },
{ id: 2, item: "Item 002", obj: { name: 'Nilton002', message: "Free002", obj2: { test: "test002" } } },
{ id: 3, item: "Item 003", obj: { name: 'Nilton003', message: "Free003", obj2: { test: "test003" } } },
];
const item = itemsArray[0];
const getAllKeys = (obj, prefix=[]) => {
if(typeof obj !== 'object'){
return prefix.join('.')
}
return Object.entries(obj).flatMap(([k, v]) => getAllKeys(v, [...prefix, k]))
}
console.log(getAllKeys(item))
The OP solution can be simplified by accepting a prefix param (the parent key) and a results param (defaulted to [] and passed into the recursion) to do the flattening...
let obj = { key0: 'v0', key1: { innerKey0: 'innerV0', innerInner: { deeplyNested: 'v' } }, key2: { anotherInnerKey: 'innerV' } }
function recursiveKeys(prefix, obj, result=[]) {
let keys = Object.keys(obj);
keys.forEach(key => {
if (typeof obj[key] === 'object')
recursiveKeys(key, obj[key], result);
else
result.push(`${prefix}.${key}`)
});
return result;
}
console.log(recursiveKeys('', obj))
function getKeys(obj) {
return Object.keys((typeof obj === 'object' && obj) || {}).reduce((acc, key) => {
if (obj[key] && typeof obj[key] === 'object') {
const keys = getKeys(obj[key]);
keys.forEach((k) => acc.add(`${key}.${k}`));
} else {
acc.add(key);
}
return acc;
}, new Set());
}
// accumulate the keys in a set (the items of the array may
// have different shapes). All of the possible keys will be
// stored in a set
const s = itemsArray.reduce(
(acc, item) => new Set([...acc, ...getKeys(item)]),
new Set()
);
console.log('Keys => ', Array.from(s));
You can use recursion as follows. Since typeof([1,3,5]) is object, we also have to confirm that value is not an array, !Array.isArray(value):
const obj = { id: 10, obj: {name: "Name1", obj2: {name2: "Name2", test: "Test"}}};
const getKeys = (o,p) => Object.entries(o).flatMap(([key,value]) =>
typeof(value) === 'object' && !Array.isArray(value) ?
getKeys(value, (p?`${p}.`:"") + key) :
(p ? `${p}.`: "") + key
);
console.log( getKeys(obj) );

How to flatten an object with nested objects in javascript

I have some attributes from a nested object that is inside the parent object but I would like to merge nested object with the parent object to be flatten.
Original object:
enrollment = {
user: {
id: 'string',
name: 'string'
},
finished: 'boolean',
path: 'string'
}
expected flatten object:
user: {
id: 'string',
name: 'string',
finished: 'boolean',
path: 'string'
}
You can recursively build object any number of nested objects. So, this function is not your case dependent:
var enrollment = {
user: {
id: 'string',
name: 'string'
},
finished: 'boolean',
path: 'boolean'
}
var enrollment2 = {
user: {
id: 'string',
name: 'string'
},
test: {
test1: {
test2: {
val0:'val0',
test4: { //3rd level nested object for example
val1: 'val1',
val2: 'val2'
}
}
}
},
finished: 'boolean',
path: 'boolean'
}
const flat = (obj, out) => {
Object.keys(obj).forEach(key => {
if (typeof obj[key] == 'object') {
out = flat(obj[key], out) //recursively call for nesteds
} else {
out[key] = obj[key] //direct assign for values
}
})
return out
}
console.log(flat(enrollment, {}))
console.log(flat(enrollment2, {}))
I needed something that avoids rewriting keys with the same name that were in different levels in the original object. So I wrote the following:
const flattenObject = (obj, parentKey = '') => {
if (parentKey !== '') parentKey += '.';
let flattened = {};
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(flattened, flattenObject(obj[key], parentKey + key))
} else {
flattened[parentKey + key] = obj[key]
}
})
return flattened;
}
var test = {
foo: 'bar',
some: 'thing',
father: {
son1: 'son1 value',
son2: {
grandchild: 'grandchild value',
duplicatedKey: 'note this is also used in first level',
},
},
duplicatedKey: 'note this is also used inside son2',
}
let flat = flattenObject(test);
console.log(flat);
// how to access the flattened keys:
let a = flat['father.son2.grandchild'];
console.log(a);
Also checks if the object is null, as I was having some problems with that in my usage.
Here's a quick and dirty way to flatten your object:
var enrollment = {
user: {
id: 'string',
name: 'string',
},
fineshed: true,
path: false,
};
var user = Object.assign(enrollment.user);
user.fineshed = enrollment.fineshed;
user.path = enrollment.path;
For a generic method with a couple of caveats of no shared key names and only flattening 1 level of depth:
var enrollment = {
user: {
id: 'string',
name: 'string',
},
fineshed: true,
path: false,
};
const flatten = (object) => {
let value = {};
for (var property in object) {
if (typeof object[property] === 'object') {
for (var p in object[property]) {
value[p] = object[property][p];
}
} else {
value[property] = object[property];
}
}
return value;
};
let user = flatten(enrollment);
console.log(user);
using recursion and reduce.
note that if value itself is an array containing objects, you might want add another check like !Array.isArray(value) depending on your case
function flatObj(obj) {
return Object.entries(obj).reduce(
(flatted, [key, value]) =>
typeof value == "object"
? { ...flatted, ...flatObj(value) }
: { ...flatted, [key]: value },
{}
);
}
Just want a single Object:
const enrollment = {
user: {
id: 'string',
name: 'string'
},
finished: 'boolean',
path: 'boolean'
}
function propsToUser(enrollObj){
const u = {...enrollObj.user};
for(let i in enrollObj){
if(i !== 'user')u[i] = enrollObj[i];
}
return u;
}
const user = propsToUser(enrollment);
console.log(user);
Below code snippet takes nested input object like this :
{
name:'Namig',
surname:'Hajiyev',
address:{
city:'Sumgait',
country:'Azerbaijan',
geo: {
lat:'40.5897200',
long:'49.6686100'
}
}
}
and returns result flattened object like this:
{
"name": "Namig",
"surname": "Hajiyev",
"address.city": "Sumgait",
"address.country": "Azerbaijan",
"address.geo.lat": "40.5897200",
"address.geo.long": "49.6686100"
}
Here is my code :
function flattenObject(obj, newObj, prefix) {
newObj = newObj || {};
prefix = prefix || "";
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
const type = typeof obj[key];
const newKey = !!prefix ? prefix + "." + key : key;
if (type === "string") {
newObj[newKey] = obj[key];
}
else if (type === "object") {
flattenObject(obj[key], newObj, newKey);
}
}
}
return newObj;
}
var obj = {
name:'Namig',
surname:'Hajiyev',
address:{
city:'Sumgait',
country:'Azerbaijan',
geo: {
lat:'40.5897200',
long:'49.6686100'
}
}
}
console.log(flattenObject(obj));

Sanitizing all string values in a complex object?

I have a sanitizeStr() function that I need to run on EVERY property/subproperty that exists in an object like the one below:
const data = {
info: 'schools',
schools: [
{ name: 'Johnson Elementary', type: 'elementary' },
{ name: 'Iselin Middle School', type: 'middle' }
],
bestStudent: {
name: 'John',
grade: 'sixth'
}
};
The issue is that for every single one of these properties, they may or may not exist. Right now, I'm having to do multiple if checks for each property and manually running the function:
// Is there a better way to do this rather than what I have here:
if (data.info) {
data.info = sanitizeStr(data.info);
}
if (data.bestStudent) {
if (data.bestStudent.name) {
data.bestStudent.name = sanitizeStr(data.bestStudent.name);
}
if (data.bestStudent.grade) {
data.bestStudent.grade = sanitizeStr(data.bestStudent.grade);
}
}
if (data.schools) {
data.schools.forEach((school, i) => {
if (school.name) {
data.schools[i].name = sanitizeStr(school.name);
}
if (school.grade) {
data.schools[i].grade = sanitizeStr(school.grade);
}
});
}
If anyone knows of a cleaner/less manual way of doing this, it would be appreciated.
You could use an iterative and recursive approach for objects and call the function for non objects only.
function sanitizeStr(s) {
return '#' + s;
}
function iterAll(object) {
Object.keys(object).forEach(function (k) {
if (object[k] && typeof object[k] === 'object') {
iterAll(object[k]);
return;
}
object[k] = sanitizeStr(object[k]);
})
}
var data = { info: 'schools', schools: [{ name: 'Johnson Elementary', type: 'elementary' }, { name: 'Iselin Middle School', type: 'middle' }], bestStudent: { name: 'John', grade: 'sixth' } };
iterAll(data);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You must me looking for this
const sanitizeObject = (obj, callBack, isClone = false) => {
let tempObj = obj;
if(typeof callBack === 'function' && (typeof tempObj === 'string' || typeof tempObj === 'number')){
return callBack(tempObj)
}else if(typeof tempObj === 'object' && tempObj !== null){
tempObj = isClone ? (Array.isArray(tempObj) ? [...tempObj] : {...tempObj}) : tempObj;
Object.keys(tempObj).forEach(objKey => {
const valueOfobject = tempObj[objKey]
if(typeof valueOfobject === 'string' || typeof valueOfobject === 'number'){
tempObj[objKey] = callBack(tempObj[objKey])
}else {
tempObj[objKey] = sanitizeObject(valueOfobject, callBack, isClone)
}
})
}
return tempObj;
}
const data = {
test1: {
test2: [{
property: "any string",
property2: null
}]}
}
console.log(sanitizeObject(data, function (stringValue){
return stringValue + " apend"
}))

Return true if value matches any value including nested object value

I have a nested object which consists of :
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
}
So if I have a string or an array of values that match any of the values including the nested object values in the collection, it will return true. I have tried using _.includes of lodash but I don't know how I can iterate through the nested object.
_.includes(obj.department, 'Operations')
What I am trying to do is more like
_.includes(obj, ['Stephen', 'Operations']) // return true
Use recursion with Array#some to check if the value exists. Array#some returns immediately when the result of the predicate is true.
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
}
function recursiveIncludes(obj) {
var values = [].slice.call(arguments, 1);
return Object.keys(obj).some(function(key) {
var current = obj[key];
if(values.indexOf(current) !== -1) {
return true;
}
if(typeof current === 'object' && current !== null) {
return recursiveIncludes.apply(null, [current].concat(values));
}
return false;
});
}
console.log('Operations: ', recursiveIncludes(obj, 'Operations'));
console.log('Moses, Stephen: ', recursiveIncludes(obj, 'Moses', 'Stephen'));
console.log('Moses, 58: ', recursiveIncludes(obj, 'Moses', 58));
Here is a recursive approach to it. It extracts all the properties of an object (the leaf properties) into an array. You can then call _.includes() on it.
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
}
function objToArray(obj) {
var result = [];
for (const prop in obj) {
const value = obj[prop];
if (typeof value === 'object') {
result = result.concat(toArray(value));
}
else {
result.push(value);
}
}
return result;
}
_.includes(objToArray(obj), ['Stephen', 'Operations'])
Given an array of strings, this will check and see if the property exists recursively.
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
}
function hasValues(obj, props)
{
var keys = Object.keys(obj);
for(var i = 0; i < keys.length; i++){
var item = obj[keys[i]];
// If the item is a string or number, do a comparison
if(typeof item === "string" || typeof item === "number"){
var idx = props.indexOf(item);
if(idx >= 0) props.splice(idx, 1);
// If it's an object then search the object recursively
} else if(typeof item === "object"){
hasValues(item, props);
}
}
return props.length === 0;
}
console.log(hasValues(obj, ['Stephen', 'Operations']))
console.log(hasValues(obj, [18, 1]))
console.log(hasValues(obj, [18, '13lkj4']))
You can use flatMap as a mechanism to flatten all the values taken from recursively using map inside the flatMap callback function. After obtaining all the values, we use difference to get the difference between all the values between the object and the values. Lastly, we check the resulting difference if it is empty using isEmpty.
function includesDeep(object, values) {
return _(obj)
.flatMap(function cb(v) { return _.isObject(v)? _.map(v, cb): v; })
.thru(_.partial(_.difference, values))
.isEmpty();
}
var result = includesDeep(obj, ['Stephen', 'Operations']);
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
};
function includesDeep(object, values) {
return _(obj)
.flatMap(function cb(v) { return _.isObject(v)? _.map(v, cb): v; })
.thru(_.partial(_.difference, values))
.isEmpty();
}
var result = includesDeep(obj, ['Stephen', 'Operations']);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
the tests shows that you can't check multi-values & value in-depth with lodash includes,so you must write a function for yourself.
Your solution
describe('includes', () => {
var value = {
foo: 'bar',
fuzz: 'buzz',
value2: {
key: 'value'
}
};
function includes(collection, values) {
return [].concat(values).every((value) => {
return Object.keys(collection).some((key) => {
let it = collection[key];
return typeof it == 'object' ? includes(it, value) : _.includes(it, value);
})
});
}
it('check single value', () => {
expect(includes(value, 'bar')).toBe(true);
expect(includes(value, 'baz')).toBe(false);
});
it('check multi values', () => {
expect(includes(value, ['bar', 'buzz'])).toBe(true);
expect(includes(value, ['baz', 'buzz'])).toBe(false);
});
it('check value in depth', () => {
expect(includes(value, 'value')).toBe(true);
expect(includes(value, 'no-exists')).toBe(false);
});
});
Test
describe('includes', () => {
var value = {
foo: 'bar',
fuzz: 'buzz',
value2: {
key: 'value'
}
};
it('check single value', () => {
expect(_.includes(value, 'bar')).toBe(true);
expect(_.includes(value, 'baz')).toBe(false);
});
it('check multi values', () => {
expect(_.includes(value, ['bar', 'buzz'])).toBe(false);
expect(_.includes(value, ['baz', 'buzz'])).toBe(false);
});
it('check value in depth', () => {
expect(_.includes(value, 'value')).toBe(false);
});
});
function includes(collection, values) {
return [].concat(values).every(function (value) {
return Object.keys(collection).some(function (key) {
var it = collection[key];
return (typeof it == 'object') ? includes(it, value) : _.includes(it, value);
});
});
}
var obj = {
id: 1,
name: 'Stephen',
age: 18,
department: {
id: 1,
text: 'Operations'
}
};
var tests=[
"Operations",
"Non-Existing Value",
['Stephen', 'Operations'],
['Stephen', 'Non-Existing Value'],
];
tests.forEach(function(test){
console.log("includes(obj,"+JSON.stringify(test)+") => "+ includes(obj,test));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

How to find a node in a tree with JavaScript

I have and object literal that is essentially a tree that does not have a fixed number of levels. How can I go about searching the tree for a particualy node and then return that node when found in an effcient manner in javascript?
Essentially I have a tree like this and would like to find the node with the title 'randomNode_1'
var data = [
{
title: 'topNode',
children: [
{
title: 'node1',
children: [
{
title: 'randomNode_1'
},
{
title: 'node2',
children: [
{
title: 'randomNode_2',
children:[
{
title: 'node2',
children: [
{
title: 'randomNode_3',
}]
}
]
}]
}]
}
]
}];
Basing this answer off of #Ravindra's answer, but with true recursion.
function searchTree(element, matchingTitle){
if(element.title == matchingTitle){
return element;
}else if (element.children != null){
var i;
var result = null;
for(i=0; result == null && i < element.children.length; i++){
result = searchTree(element.children[i], matchingTitle);
}
return result;
}
return null;
}
Then you could call it:
var element = data[0];
var result = searchTree(element, 'randomNode_1');
Here's an iterative solution:
var stack = [], node, ii;
stack.push(root);
while (stack.length > 0) {
node = stack.pop();
if (node.title == 'randomNode_1') {
// Found it!
return node;
} else if (node.children && node.children.length) {
for (ii = 0; ii < node.children.length; ii += 1) {
stack.push(node.children[ii]);
}
}
}
// Didn't find it. Return null.
return null;
Here's an iterative function using the Stack approach, inspired by FishBasketGordo's answer but taking advantage of some ES2015 syntax to shorten things.
Since this question has already been viewed a lot of times, I've decided to update my answer to also provide a function with arguments that makes it more flexible:
function search (tree, value, key = 'id', reverse = false) {
const stack = [ tree[0] ]
while (stack.length) {
const node = stack[reverse ? 'pop' : 'shift']()
if (node[key] === value) return node
node.children && stack.push(...node.children)
}
return null
}
This way, it's now possible to pass the data tree itself, the desired value to search and also the property key which can have the desired value:
search(data, 'randomNode_2', 'title')
Finally, my original answer used Array.pop which lead to matching the last item in case of multiple matches. In fact, something that could be really confusing. Inspired by Superole comment, I've made it use Array.shift now, so the first in first out behavior is the default.
If you really want the old last in first out behavior, I've provided an additional arg reverse:
search(data, 'randomNode_2', 'title', true)
My answer is inspired from FishBasketGordo's iterativ answer. It's a little bit more complex but also much more flexible and you can have more than just one root node.
/**searchs through all arrays of the tree if the for a value from a property
* #param aTree : the tree array
* #param fCompair : This function will receive each node. It's upon you to define which
condition is necessary for the match. It must return true if the condition is matched. Example:
function(oNode){ if(oNode["Name"] === "AA") return true; }
* #param bGreedy? : us true to do not stop after the first match, default is false
* #return an array with references to the nodes for which fCompair was true; In case no node was found an empty array
* will be returned
*/
var _searchTree = function(aTree, fCompair, bGreedy){
var aInnerTree = []; // will contain the inner children
var oNode; // always the current node
var aReturnNodes = []; // the nodes array which will returned
// 1. loop through all root nodes so we don't touch the tree structure
for(keysTree in aTree) {
aInnerTree.push(aTree[keysTree]);
}
while(aInnerTree.length > 0) {
oNode = aInnerTree.pop();
// check current node
if( fCompair(oNode) ){
aReturnNodes.push(oNode);
if(!bGreedy){
return aReturnNodes;
}
} else { // if (node.children && node.children.length) {
// find other objects, 1. check all properties of the node if they are arrays
for(keysNode in oNode){
// true if the property is an array
if(oNode[keysNode] instanceof Array){
// 2. push all array object to aInnerTree to search in those later
for (var i = 0; i < oNode[keysNode].length; i++) {
aInnerTree.push(oNode[keysNode][i]);
}
}
}
}
}
return aReturnNodes; // someone was greedy
}
Finally you can use the function like this:
var foundNodes = _searchTree(data, function(oNode){ if(oNode["title"] === "randomNode_3") return true; }, false);
console.log("Node with title found: ");
console.log(foundNodes[0]);
And if you want to find all nodes with this title you can simply switch the bGreedy parameter:
var foundNodes = _searchTree(data, function(oNode){ if(oNode["title"] === "randomNode_3") return true; }, true);
console.log("NodeS with title found: ");
console.log(foundNodes);
FIND A NODE IN A TREE :
let say we have a tree like
let tree = [{
id: 1,
name: 'parent',
children: [
{
id: 2,
name: 'child_1'
},
{
id: 3,
name: 'child_2',
children: [
{
id: '4',
name: 'child_2_1',
children: []
},
{
id: '5',
name: 'child_2_2',
children: []
}
]
}
]
}];
function findNodeById(tree, id) {
let result = null
if (tree.id === id) {
return tree;
}
if (Array.isArray(tree.children) && tree.children.length > 0) {
tree.children.some((node) => {
result = findNodeById(node, id);
return result;
});
}
return result;}
You have to use recursion.
var currChild = data[0];
function searchTree(currChild, searchString){
if(currChild.title == searchString){
return currChild;
}else if (currChild.children != null){
for(i=0; i < currChild.children.length; i ++){
if (currChild.children[i].title ==searchString){
return currChild.children[i];
}else{
searchTree(currChild.children[i], searchString);
}
}
return null;
}
return null;
}
ES6+:
const deepSearch = (data, value, key = 'title', sub = 'children', tempObj = {}) => {
if (value && data) {
data.find((node) => {
if (node[key] == value) {
tempObj.found = node;
return node;
}
return deepSearch(node[sub], value, key, sub, tempObj);
});
if (tempObj.found) {
return tempObj.found;
}
}
return false;
};
const result = deepSearch(data, 'randomNode_1', 'title', 'children');
This function is universal and does search recursively.
It does not matter, if input tree is object(single root), or array of objects (many root objects). You can configure prop name that holds children array in tree objects.
// Searches items tree for object with specified prop with value
//
// #param {object} tree nodes tree with children items in nodesProp[] table, with one (object) or many (array of objects) roots
// #param {string} propNodes name of prop that holds child nodes array
// #param {string} prop name of searched node's prop
// #param {mixed} value value of searched node's prop
// #returns {object/null} returns first object that match supplied arguments (prop: value) or null if no matching object was found
function searchTree(tree, nodesProp, prop, value) {
var i, f = null; // iterator, found node
if (Array.isArray(tree)) { // if entry object is array objects, check each object
for (i = 0; i < tree.length; i++) {
f = searchTree(tree[i], nodesProp, prop, value);
if (f) { // if found matching object, return it.
return f;
}
}
} else if (typeof tree === 'object') { // standard tree node (one root)
if (tree[prop] !== undefined && tree[prop] === value) {
return tree; // found matching node
}
}
if (tree[nodesProp] !== undefined && tree[nodesProp].length > 0) { // if this is not maching node, search nodes, children (if prop exist and it is not empty)
return searchTree(tree[nodesProp], nodesProp, prop, value);
} else {
return null; // node does not match and it neither have children
}
}
I tested it localy and it works ok, but it somehow won't run on jsfiddle or jsbin...(recurency issues on those sites ??)
run code :
var data = [{
title: 'topNode',
children: [{
title: 'node1',
children: [{
title: 'randomNode_1'
}, {
title: 'node2',
children: [{
title: 'randomNode_2',
children: [{
title: 'node2',
children: [{
title: 'randomNode_3',
}]
}]
}]
}]
}]
}];
var r = searchTree(data, 'children', 'title', 'randomNode_1');
//var r = searchTree(data, 'children', 'title', 'node2'); // check it too
console.log(r);
It works in http://www.pythontutor.com/live.html#mode=edit (paste the code)
no BS version:
const find = (root, title) =>
root.title === title ?
root :
root.children?.reduce((result, n) => result || find(n, title), undefined)
This is basic recursion problem.
window.parser = function(searchParam, data) {
if(data.title != searchParam) {
returnData = window.parser(searchParam, children)
} else {
returnData = data;
}
return returnData;
}
here is a more complex option - it finds the first item in a tree-like node with providing (node, nodeChildrenKey, key/value pairs & optional additional key/value pairs)
const findInTree = (node, childrenKey, key, value, additionalKey?, additionalValue?) => {
let found = null;
if (additionalKey && additionalValue) {
found = node[childrenKey].find(x => x[key] === value && x[additionalKey] === additionalValue);
} else {
found = node[childrenKey].find(x => x[key] === value);
}
if (typeof(found) === 'undefined') {
for (const item of node[childrenKey]) {
if (typeof(found) === 'undefined' && item[childrenKey] && item[childrenKey].length > 0) {
found = findInTree(item, childrenKey, key, value, additionalKey, additionalValue);
}
}
}
return found;
};
export { findInTree };
Hope it helps someone.
A flexible recursive solution that will work for any tree
// predicate: (item) => boolean
// getChildren: (item) => treeNode[]
searchTree(predicate, getChildren, treeNode) {
function search(treeNode) {
if (!treeNode) {
return undefined;
}
for (let treeItem of treeNode) {
if (predicate(treeItem)) {
return treeItem;
}
const foundItem = search(getChildren(treeItem));
if (foundItem) {
return foundItem;
}
}
}
return search(treeNode);
}
find all parents of the element in the tree
let objects = [{
id: 'A',
name: 'ObjA',
children: [
{
id: 'A1',
name: 'ObjA1'
},
{
id: 'A2',
name: 'objA2',
children: [
{
id: 'A2-1',
name: 'objA2-1'
},
{
id: 'A2-2',
name: 'objA2-2'
}
]
}
]
},
{
id: 'B',
name: 'ObjB',
children: [
{
id: 'B1',
name: 'ObjB1'
}
]
}
];
let docs = [
{
object: {
id: 'A',
name: 'docA'
},
typedoc: {
id: 'TD1',
name: 'Typde Doc1'
}
},
{
object: {
id: 'A',
name: 'docA'
},
typedoc: {
id: 'TD2',
name: 'Typde Doc2'
}
},
{
object: {
id: 'A1',
name: 'docA1'
},
typedoc: {
id: 'TDx1',
name: 'Typde Doc x1'
}
},
{
object: {
id: 'A1',
name: 'docA1'
},
typedoc: {
id: 'TDx2',
name: 'Typde Doc x1'
}
},
{
object: {
id: 'A2',
name: 'docA2'
},
typedoc: {
id: 'TDx2',
name: 'Type de Doc x2'
}
},
{
object: {
id: 'A2-1',
name: 'docA2-1'
},
typedoc: {
id: 'TDx2-1',
name: 'Type de Docx2-1'
},
},
{
object: {
id: 'A2-2',
name: 'docA2-2'
},
typedoc: {
id: 'TDx2-2',
name: 'Type de Docx2-2'
},
},
{
object: {
id: 'B',
name: 'docB'
},
typedoc: {
id: 'TD1',
name: 'Typde Doc1'
}
},
{
object: {
id: 'B1',
name: 'docB1'
},
typedoc: {
id: 'TDx1',
name: 'Typde Doc x1'
}
}
];
function buildAllParents(doc, objects) {
for (let o = 0; o < objects.length; o++) {
let allParents = [];
let getAllParents = (o, eleFinded) => {
if (o.id === doc.object.id) {
doc.allParents = allParents;
eleFinded = true;
return { doc, eleFinded };
}
if (o.children) {
allParents.push(o.id);
for (let c = 0; c < o.children.length; c++) {
let { eleFinded, doc } = getAllParents(o.children[c], eleFinded);
if (eleFinded) {
return { eleFinded, doc };
} else {
continue;
}
}
}
return { eleFinded };
};
if (objects[o].id === doc.object.id) {
doc.allParents = [objects[o].id];
return doc;
} else if (objects[o].children) {
allParents.push(objects[o].id);
for (let c = 0; c < objects[o].children.length; c++) {
let eleFinded = null;`enter code here`
let res = getAllParents(objects[o].children[c], eleFinded);
if (res.eleFinded) {
return res.doc;
} else {
continue;
}
}
}
}
}
docs = docs.map(d => buildAllParents(d, objects`enter code here`))
This is an iterative breadth first search. It returns the first node that contains a child of a given name (nodeName) and a given value (nodeValue).
getParentNode(nodeName, nodeValue, rootNode) {
const queue= [ rootNode ]
while (queue.length) {
const node = queue.shift()
if (node[nodeName] === nodeValue) {
return node
} else if (node instanceof Object) {
const children = Object.values(node)
if (children.length) {
queue.push(...children)
}
}
}
return null
}
It would be used like this to solve the original question:
getParentNode('title', 'randomNode_1', data[0])
Enhancement of the code based on "Erick Petrucelli"
Remove the 'reverse' option
Add multi-root support
Add an option to control the visibility of 'children'
Typescript ready
Unit test ready
function searchTree(
tree: Record<string, any>[],
value: unknown,
key = 'value',
withChildren = false,
) {
let result = null;
if (!Array.isArray(tree)) return result;
for (let index = 0; index < tree.length; index += 1) {
const stack = [tree[index]];
while (stack.length) {
const node = stack.shift()!;
if (node[key] === value) {
result = node;
break;
}
if (node.children) {
stack.push(...node.children);
}
}
if (result) break;
}
if (withChildren !== true) {
delete result?.children;
}
return result;
}
And the tests can be found at: https://gist.github.com/aspirantzhang/a369aba7f84f26d57818ddef7d108682
Wrote another one based on my needs
condition is injected.
path of found branch is available
current path could be used in condition statement
could be used to map the tree items to another object
// if predicate returns true, the search is stopped
function traverse2(tree, predicate, path = "") {
if (predicate(tree, path)) return true;
for (const branch of tree.children ?? [])
if (traverse(branch, predicate, `${path ? path + "/" : ""}${branch.name}`))
return true;
}
example
let tree = {
name: "schools",
children: [
{
name: "farzanegan",
children: [
{
name: "classes",
children: [
{ name: "level1", children: [{ name: "A" }, { name: "B" }] },
{ name: "level2", children: [{ name: "C" }, { name: "D" }] },
],
},
],
},
{ name: "dastgheib", children: [{ name: "E" }, { name: "F" }] },
],
};
traverse(tree, (branch, path) => {
console.log("searching ", path);
if (branch.name === "C") {
console.log("found ", branch);
return true;
}
});
output
searching
searching farzanegan
searching farzanegan/classes
searching farzanegan/classes/level1
searching farzanegan/classes/level1/A
searching farzanegan/classes/level1/B
searching farzanegan/classes/level2
searching farzanegan/classes/level2/C
found { name: 'C' }
In 2022 use TypeScript and ES5
Just use basic recreation and built-in array method to loop over the array. Don't use Array.find() because this it will return the wrong node. Use Array.some() instead which allow you to break the loop.
interface iTree {
id: string;
children?: iTree[];
}
function findTreeNode(tree: iTree, id: string) {
let result: iTree | null = null;
if (tree.id === id) {
result = tree;
} else if (tree.children) {
tree.children.some((node) => {
result = findTreeNode(node, id);
return result; // break loop
});
}
return result;
}
const flattenTree = (data: any) => {
return _.reduce(
data,
(acc: any, item: any) => {
acc.push(item);
if (item.children) {
acc = acc.concat(flattenTree(item.children));
delete item.children;
}
return acc;
},
[]
);
};
An Approach to convert the nested tree into an object with depth 0.
We can convert the object in an object like this and can perform search more easily.
The following is working at my end:
function searchTree(data, value) {
if(data.title == value) {
return data;
}
if(data.children && data.children.length > 0) {
for(var i=0; i < data.children.length; i++) {
var node = traverseChildren(data.children[i], value);
if(node != null) {
return node;
}
}
}
return null;
}

Categories

Resources