I need to recursively check a dynamic Tree Structure in Javascript - javascript

I need your help. So the goal this time is that i want to recursively check my tree structure.
Basically this is what i need it to do:
On the on side i have a so called treeNode (example: 'Article.Artnr') and on the other a so called contextEntry (which has an 'Article').
I need to check if the name in treeNode exists in contextEntry, if it exists i need to check the type of it and than go on. Now the tricky Part: The next name in the namesSplit is 'Artnr' which i need to check if it is an property from 'Article' in the contextEntry. Again if it is i need to check it's type and go on. I don't know beforehand how deep my structure will be so i really would appreciate your help on this one.
let namesSplit = treeNode.name.split('.');
let key = namesSplit[0];
let contextEntry = exprData.contextEntry.find(_x => _x.name === key);
I have implemented my Recursive check like so:
function recursive(names, contextEntry) {
for (let i = 0; i < names.length; i++) {
if (names[i] === contextEntry.name) {
// getType(contextEntry.value);
console.log('Name found in Context: ' + names[i]);
continue;
}
if (names[i] == Object.keys(contextEntry.value)) {
console.log(getType(contextEntry.value));
console.log('Child Name found in Context: ' + names[i]);
// console.log(Object.values(contextEntry.value));
}
}
}
recursive(namesSplit, contextEntry);
I also have to check the type of every value from the Context.
There are 4 possible datatypes: Primitive, Array of Primitive,
Object, Array of Objects
if the value in the context is either Primitive or an Array of Primitives it will throw an error, if it is an Object i need to just extract the value of it and if it is an Array of Objects i need to find the right object and than extract the value from it
VariableType is an enum.
My Check Function is implemented like so:
function getType(contextEntry.value) {
if (Array.isArray(contextEntry.value)) {
for (let i of contextEntry.value) {
if (isPrimitive(i)) {
return VariableType.ARRAY_OF_PRIMITIVES;
}
}
if (contextEntry.value.some(val => typeof val === 'object')) {
return VariableType.ARRAY_OF_OBJECTS;
}
}
if (typeof contextEntry.value === 'object' && contextEntry.value !== null) {
return VariableType.OBJECT;
}
if (isPrimitive(contextEntry.value)) {
return VariableType.PRIMITIVE;
}
}

Related

Search through literal object and all its nested objects (up to nth level) with a generalized function [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Original close reason(s) were not resolved
Improve this question
Yet, another question about JSON-like Object literals.
the object that I work with has the following structure:
let family ={
"id":"someId",
"participants":[
{
"name":"Q",
"sex":"m",
"age":23,
"occupations":[
{
"jobId":"some ID"
"finished": true,
"start": dateTime,
"end":dateTime,
"skills":[
{
"name":"welding"
},
{
"name":"concrete mixing"
},
]
},
{
"jobId": "someId",
"finished": false,
"skills":[
{
"name":"power lifting"
},
{
"name":"swimming"
},
{
}
]
},
{"OTHER Participant"},
{"OTHER Participant"}
]
}
It is for the sake of example.
when I receive data, every literal object is going to be unique so there is no "schema" that I can create and refer to based on the type, for example.
And I need to find the unique objects on the 2nd, 3rd, 4th etc levels.IF they exist
My take on this:
Every time I would try to use something either like :
let personIneed; //to save him later;
let part = family.participants;
for (const xx of part){
let partJobs =xx.occupations;
for (const xxx of partJobs){
if(xxx.end && xxx.finished == true){
let partJobSkills = xxx.skills;
let isSkillPresent =false; //to change to true once skill iteration is finished
for (const xxxx of partJobSkills){
if(xxxx.name ==="concrete mixing"){
isSkillPresent =true;
}
}
//check if skill's there and save the participant
if(isSkillPresent){
personIneed = xx;
return;
}
}
}
}
Which is very bulky and if i have to build a function like that for every set of criteria... well I'd rather build a guillotine. It also looks a little bit like a callback Hell to me (the way it cascades and only) :)
Either when the search is only at the high level,
let part = family.participants;
let man = part.find (p=>p.sex==="m");
Which would only return me the first person to match the criteria. which is not ideal.
I would like to build a generalized function that will be able to go through the Object literals finding the nested objects that match my criteria. The criteria should be passed as parameters of the function. Function should also consider that there can be several nested objects with the same keys ( for example one person can be a Builder on many different jobs) or there can be in fact none of the objects that match the criteria.
The ultimate goal is somehow to make all the nested objects easily accessible when the object is found. (for example when we found a person who has worked as a builder and who has finished his contract, we return this object but if i need to find the start and end of his working period, I need to go through all the nested objects again and it becomes hell again)
Please note that some arrays will simply not exist and some will have one value.
From your comments, it sounds like you're looking for a generalized way to loop through nested objects and arrays. You'd do that with recursion. If you want to make it possible to collect information, or to stop the operation, in different ways for different places you use it, you can pass in a callback function.
Here's an example:
// A version of `Object.prototype.hasOwnProperty` that we can call with
// any object and key. For arrays, we use this to check for empty slots
// (holes in a sparse arary). For non-array objects, we use this to skip
// inherited properties; in the not-array path below, you could remove
// the `hasOwn` check if you wanted to process inherited properties (but
// probably best to keept it for the array path).
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
// The deep search function
function deepSearch(obj, callback) {
if (Array.isArray(obj)) {
// It's an array, loop through it
for (let index = 0, len = obj.length; index < len; ++index) {
// Is there an entry at this index?
if (hasOwn(obj, index)) {
// Yes, get its value
const value = obj[index];
// Call the callback
if (callback(obj, index, value)) {
// Callback returned a truthy value, stop here and
// return true
return true;
}
// Is this value an object?
if (value && typeof value === "object") {
// Yes, recurse
if (deepSearch(value, callback)) {
// Recursion found it, stop here
return true;
}
}
}
}
} else {
// It's not an array, loop through the object keys
for (const key in obj) {
// Is this an "own" property (not inherited)?
if (hasOwn(obj, key)) {
// Yes, get its value
const value = obj[key];
// Callback the callback
if (callback(obj, key, value)) {
// Callback returned a truthy value, stop here and
// return true
return true;
}
// Is this value an object?
if (value && typeof value === "object") {
// Yes, recurse
if (deepSearch(value, callback)) {
// Recursion found it, stop here
return true;
}
}
}
}
}
// Not found, return false
return false;
}
Here's a live version using a callback to find something specific and stop the iteration:
const example = {
first1: "value of first1",
first2: {
second1: "value of second1",
second2: [
{
third11: "value of third11",
third12: {
fourth11: "value of fourth11",
},
},
{
third21: "value of third21",
third22: {
fourth21: "value of fourth21",
},
},
],
},
};
// A version of `Object.prototype.hasOwnProperty` that we can call with
// any object and key. For arrays, we use this to check for empty slots
// (holes in a sparse arary). For non-array objects, we use this to skip
// inherited properties; in the not-array path below, you could remove
// the `hasOwn` check if you wanted to process inherited properties (but
// probably best to keept it for the array path).
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
// The deep search function
function deepSearch(obj, callback) {
if (Array.isArray(obj)) {
// It's an array, loop through it
for (let index = 0, len = obj.length; index < len; ++index) {
// Is there an entry at this index?
if (hasOwn(obj, index)) {
// Yes, get its value
const value = obj[index];
// Call the callback
if (callback(obj, index, value)) {
// Callback returned a truthy value, stop here and
// return true
return true;
}
// Is this value an object?
if (value && typeof value === "object") {
// Yes, recurse
if (deepSearch(value, callback)) {
// Recursion found it, stop here
return true;
}
}
}
}
} else {
// It's not an array, loop through the object keys
for (const key in obj) {
// Is this an "own" property (not inherited)?
if (hasOwn(obj, key)) {
// Yes, get its value
const value = obj[key];
// Callback the callback
if (callback(obj, key, value)) {
// Callback returned a truthy value, stop here and
// return true
return true;
}
// Is this value an object?
if (value && typeof value === "object") {
// Yes, recurse
if (deepSearch(value, callback)) {
// Recursion found it, stop here
return true;
}
}
}
}
}
// Not found, return false
return false;
}
deepSearch(example, (obj, key, value) => {
console.log(`Looking at ${key}, value: ${Array.isArray(value) ? "(array)" : value && typeof value === "object" ? "(object)" : JSON.stringify(value)}`);
if (key === "third21") {
console.log(`*** Found third21, value = ${value}`);
return true;
}
});
.as-console-wrapper {
max-height: 100% !important;
}

Checking if object is already in array not working

I am trying to check if an object is already in an array, following this answer here: How to determine if object is in array
I adjusted the function to suit my needs, and now it looks like this:
var _createDatesArray, _objInArray;
_objInArray = function(array, obj) {
var i;
i = 0;
while (i < array.length) {
console.log("array[i] == obj is ", array[i] === obj, " array[i] is ", array[i], " and obj is ", obj);
if (array[i] === obj) {
return true;
}
i++;
}
};
_createDatesArray = function(val) {
var result;
if (val != null) {
result = {
text: val
};
if (!_objInArray(scope.datesQuestion.dates, result)) {
scope.datesQuestion.dates.push(result);
}
return console.log(scope.datesQuestion.dates);
}
};
What I need to do, is basically see if the object is already in the array, and if is,t return true.
When debugging, the result of the console log is the following:
array[i] == obj is false array[i] is {text: "10/08/17"} and obj is
{text: "10/08/17"}
and the function says they are different (array[i] == obj is false) but they look the same to me.
I also checked the type of both, which is this:
typeof array[i] is "object"
typeof obj is "object"
can you help me with this? Why are they different? what can be different?
_createDatesArray is called when $scope of my angular app changes its value based on a ng-model, but I don't think this is relevant
They're two different objects with the same content. Comparing them with == or === will yield false.
Since you're using AngularJS, you can use angular.equals() instead to perform a deep comparison of the object's properties.
The objects you are comparing don't have the same reference, so == is returning false. See Object comparison in JavaScript for a more detailed explanation.
In this particular case, you could simply compare the text of dates to see if they are equivilant. However this wouldn't work for all objects like the function name suggests.
if (arr[i].text === obj.text)
Alternatively, you could create a method specific for checking if your array includes a given date and simplify it greatly using Array.prototype.some:
dateInArray = function (array, date) {
return array.some(function (arrayDate) {
return arrayDate.text === date.text
})
}
Or, more succinctly using ES6 arrow functions:
dateInArray = (array, date) => array.some(arrayDate => arrayDate.text === date.text)
array[i] === obj will return true ONLY if its the same object. In the link that you have referred the object being checked is the same object that is inserted in the array, thats why it returns true. In your case, you are creating a new object 'result' and adding the value in there. So the array does not contain the exact same object and hence returns false.
If 'text' is the only property in the object, instead of checking for the entire object you could check if the 'text' property in both the objects is same.
_objInArray = function(array, obj) {
var i;
i = 0;
while (i < array.length) {
if (array[i].text === obj.text) {
return true;
}
i++;
}
};
This happens because objects in JS are compared by reference, but not by values they have. But you need to compare objects by their value. So you need to get some third-party function or to write your own. One more option is to use angular built-in equals function.
angular.equals($scope.user1, $scope.user2);
For a better understanding you can read a good article on this subject here.

How to rewrite the following function so it doesn't use for loops?

The following function takes an object, loops through each value and returns false if the object or its children have an empty or undefined property web. Otherwise, it returns true:
hasNoCategories (object) {
for (let key in object) {
const value = object[key]
for (let i = 0; i < value.length; i++) {
const item = value[i]
if (item.web !== undefined && item.web !== '') return false
}
if (key === 'web' && value !== '') {
return false
}
}
return true
},
Example input:
{
"livingroom": [],
"garage": [],
"outdoors": [],
"other": [],
"id": "ZI4hteKxgr",
"name": "Cuiti",
"description": "",
"user": "",
"date": "2016/5/13",
}
How to rewrite this function without using for loops?
I'm not 100% sure what you expect the code to do, because your existing code and your description differ.
Your description is, rephrased, that this function checks whether object.web or any object.XXX.web are undefined. Your code however assumes that all members are arrays and checks whether object.web or object.XXX[YYY].web are undefined. (Note that it also doesn't do it correctly and accesses .length even though the member in question might be undefined.)
Since I'm not sure which of those is right, I'm providing two answers.
Functionality as per your textual description:
function hasNoCategories(object) {
if(!object.web) return false;
return Object.keys(object).every(function(key) {
if(typeof object[key] !== 'object') return true;
return !!object[key].web;
});
}
Functionality as per your existing code: (but with the length property access fixed)
function hasNoCategories(object) {
if(!object.web) return false;
return Object.keys(object).every(function(key) {
if(!Array.isArray(object[key])) return true;
return object[key].every(function(el) {
if(typeof object[key] !== 'object') return true;
return !!el.web;
});
});
}
To understand how this works, check out the documentation on Object.keys (which returns an array with the names of all keys in your object) and Array.prototype.every (which runs a callback function for every element in an array and returns true only if the callback returned true for every element).
Note that I'm assuming that your "empty or undefined" should reject all kinds of falsy values including null and the number (not string) zero. If not, then all the checks like if(!something) and return !!something would need to be changed to if(typeof something === "undefined" || something === '') and return typeof something !== "undefined" && something !== '', respectively.
Side note to prevent nitpicking: Of course there are still loops going on. But it was specifically asked "without for loop" and there is no for in this code.
I assume this is what you are looking for:
var hasNoCategories = function(object) {
if (!object.web) {
return false;
}
for (let key in object) {
var value = object[key];
if (!value.web) {
return false;
}
}
return true;
};
I got rid of 1 loop. But this cannot be done without loops because you have to loop over all children. You can hide this loop inside some another function but you cannot get rid of it.
If you really don't want to use loops (I don't know why), one of your options is to serialize the object and phrase the string for the word "web".
var s = JSON.stringify(object);
var webIndex = s.indexOf('web');
Now perform some checks around this index to ascertain if that has the value 'undefined' or ''. Please keep in mind that the word "web" can match as a part of another property name too. So, you need to include this possibility too to your checks.

How to check if values in one JavaScript object are present in another one?

I am trying to compare json_str1 and json_str2, here it should return true as all elements in json_str1 are present in json_str2.
For now I am doing this the long way like this
json_str1 = '{"0":"a","1":"b","2":"c"}';
json_str2 = '{"0":"c","1":"b","2":"a"}';
json_obj1 = $.parseJSON(json_str1);
json_obj2 = $.parseJSON(json_str2);
arr1 = $.map(json_obj1, function(el) { return el });
arr2 = $.map(json_obj2, function(el) { return el });
if($(arr1).not(arr2).length === 0 && $(arr2).not(arr1).length === 0)
alert("equal");
else
alert("not equal");
How could I make it short and simple, without converting the objects into an array ?
https://jsfiddle.net/kq9gtdr0/
Use the following code:
Object.keys(json_obj1) . every(k1 =>
Object.keys(json_obj2) . some(k2 =>
json_obj1[k1] === json_obj2[k2]
)
);
In English:
Every key k1 in json_obj1 satisfies the condition that some key k2 in json_obj2 satisifies the condition that the value of json_obj1 with key k1 is equal to the value of json_obj2 with key k2.
Or in more conversational English:
Every value in the first object matches some value in the second.
Using lodash
var _ = require('lodash');
function compareValues(jstr1, jstr2) {
return _.isEqual(_.valuesIn(JSON.parse(jstr1)).sort(), _.valuesIn(JSON.parse(jstr2)).sort());
}
json_str1 = '{"0":"a","1":"b","2":"c"}';
json_str2 = '{"0":"c","1":"b","2":"a"}';
console.log(compareValues(json_str1, json_str2));
There is short and easy accurate way to this.
You can use a third party but extremely popular utility library called Lodash. Chaining functions you can check for equality.
First parse both JSON into objects
Then use _.values() to extract the values of all keys of each into separate arrays
Find difference of two arrays. If its an empty array then both of them are equal.
You can chain all the steps into one statement like:
_.isEmpty(_.difference(_.values(json_obj1), _.values(json_obj2)))
Example: https://jsfiddle.net/kq9gtdr0/4/
For more information:
https://lodash.com/docs#values
https://lodash.com/docs#difference
https://lodash.com/docs#isEmpty
You can include the library from CDN(https://cdn.jsdelivr.net/lodash/4.5.1/lodash.min.js) or download and use it as normal script. Lodash offers plenty of useful utility functions that makes JS programming a lot easier. You better try it out.
If you prefer using libraries, then you could use underscore isMatch
_.isMatch(object, properties)
Tells you if the keys and values in properties are contained in
object.
Extending the awesome answer by #user663031, in case you need to do deep comparison, here's code that works:
export function objectOneInsideObjectTwo(jsonObj1: any, jsonObj2: any): boolean {
return Object.keys(jsonObj1).every((k1) => {
if (parseType(jsonObj1[k1]) === 'dict') {
return objectOneInsideObjectTwo(jsonObj1[k1], jsonObj2[k1]);
}
if (parseType(jsonObj1[k1]) === 'array') {
const results: boolean[] = [];
jsonObj1[k1].forEach((o: any, i: number) => {
if (parseType(o) === 'dict') {
results.push(objectOneInsideObjectTwo(o, jsonObj2[k1][i]));
} else {
results.push(o === jsonObj2[k1][i]);
}
});
return results.every((r) => r);
}
return Object.keys(jsonObj2).some((k2) => jsonObj1[k1] === jsonObj2[k2]);
});
}
export function parseType<T>(v: T): string {
if (v === null || v === undefined) {
return 'null';
}
if (typeof v === 'object') {
if (v instanceof Array) {
return 'array';
}
if (v instanceof Date) {
return 'date';
}
return 'dict';
}
return typeof v;
}
You can try this
var json_str1 = {"0":"a","1":"b","2":"c"};
var json_str2 = {"0":"c","1":"b","2":"a"};
var flag = 1;
if(Object.keys(json_str1).length == Object.keys(json_str2).length){
Object.keys(json_str1).forEach(function(x){
if(!json_str2.hasOwnProperty(x) || json_str2[x] != json_str1[x]){
flag = 0;
return;
}
});
}
if(flag)
alert('equal');
else
alert('Not Equal');
If you want to find out if both Objects have the same keys there is no way to do this without at least converting the keys of both Objects to an array with Object.keys or looping through both Objects!
The reason is simple: It's clear that you have to compare the number of keys of both Objects and the only way to do this is by looping through all properties or Object.keys.
So I think the shortest way to do this is:
json_obj1 = JSON.parse('{"0":"a","1":"b","2":"c"}');
json_obj2 = JSON.parse('{"0":"c","1":"b","2":"a"}');
keys_1 = Object.keys(json_obj1);
keys_2 = Object.keys(json_obj2);
if(keys_1.length === keys_2.length && keys_1.every(key => keys_2.indexOf(key) >= 0)) {
alert('equal')
} else {
alert('not equal')
}
If you only want to check if all keys from json1 are present in json2 you can do:
json_obj1 = JSON.parse('{"0":"a","1":"b","2":"c"}');
json_obj2 = JSON.parse('{"0":"c","1":"b","2":"a"}');
if(Object.keys(json_obj1).every(key => key in json_obj2)) {
alert('equal');
} else {
alert('not equal');
}
In your question and comments you indicate you are only looking to verify that "all elements in json_str1 are present in json_str2". Your example code doesn't just do that, it checks for the complete equality of keys by testing if all the keys (not values) in the first object are in the second object AND all the keys in the second object are in the first object. By looking at your code, i assume that when you say "elements" you mean keys.
All that aside, this might help:
// Your first few lines of code
json_str1 = '{"0":"a","1":"b","2":"c"}';
json_str2 = '{"0":"c","1":"b","2":"a"}';
json_obj1 = $.parseJSON(json_str1);
json_obj2 = $.parseJSON(json_str2);
// My new code
var values1 = Object.keys(json_obj1).map(key => json_obj1[key]);
var values2 = Object.keys(json_obj2).map(key => json_obj2[key]);
// Check if every key in the first object is in the second object.
values1.every(k1 => values2.indexOf(k1) >= 0);
// OR
// Check for equality of keys by checking both directions.
values1.every(k1 => values2.indexOf(k1) >= 0) && values2.every(k2 => values1.indexOf(k2) >= 0);
That's 2 lines to get the keys, and one line to check. You only need one of those two checks.

javascript find parent of array item

I have an array item like this:
var array = USA.NY[2];
// gives "Albany"
{"USA" : {
"NY" : ["New York City", "Long Island", "Albany"]
}}
I want to find the state from just having the array. How do I do this? Thanks.
function findParent(array) {
// do something
// return NY
}
In Javascript, array elements have no reference to the array(s) containing them.
To achieve this, you will have to have a reference to the 'root' array, which will depend on your data model.
Assuming USA is accessible, and contains only arrays, you could do this:
function findParent(item) {
var member, i, array;
for (member in USA) {
if (USA.hasOwnProperty(member) && typeof USA[member] === 'object' && USA[member] instanceof Array) {
array = USA[member];
for(i = 0; i < array.length; i += 1) {
if (array[i] === item) {
return array;
}
}
}
}
}
Note that I’ve renamed the array parameter to item since you’re passing along a value (and array item), and you expect the array to be returned.
If you want to know the name of the array, you should return member instead.
Here is a generic function that can be used to find the parent key of any kind of multi-dimentional object. I use underscore.js by habit and for conciseness to abstract array vs associative array loops.
(function (root, struct) {
var parent = null;
var check = function (root, struct) {
_.each(root, function (value, key) {
if (value == struct) {
parent = key;
} else if (root == struct) {
parent = '_root';
} else if (typeof value === 'object') {
check(value, struct);
}
});
}
check(root, struct);
return parent;
})

Categories

Resources