const find = (json) => {
let result;
for (const key in json) {
if(typof json[key] === "number") {
result = json[key];
break;
} else {
find(json[key])
}
return result;
}
Above code helps in finding the value of property which has type number, is there a chance I can break the for..in without continuing the recursion till it's end and access the value as I'm trying to do in the above code. Please let me know the ideal way to access particular value in nested JSON and break the loop once it's achieved.
When doing recursive functions, there are a few key things:
There must always be a termination condition (a property with a number value, if your case)
If the function finds the termination condition, it returns something indicating that the condition was found (often by returning the value that you're looking for).
If the function finds that it should recurse, it calls itself and then checks the result of calling itself in case that recursive call found the termination condition.
If it did, return the value that was returned from the recusive call.
If the function neither finds the termination condition nor recurses, return a value indicating that fact.
See comments:
const find = (obj) => {
for (const key in obj) {
const value = obj[key];
if (typeof value === "number") {
// Found one -- return it (this is #2)
return value;
} else if (value && typeof value === "object") {
// Found an object -- recurse (this is #3)
const result = find(value);
// Did recursion find a number?
if (typeof result === "number") {
// Yes, return it (this is #3.1)
return result;
}
}
}
// Nothing found, return `undefined` (explicitly or implicitly)
// This is #4
// This is explicitly: `return undefined;`
};
Note that I changed json to obj. It clearly isn't JSON (a string).
Live Example:
const find = (obj) => {
console.log(`Looking in ${JSON.stringify(obj)}`);
for (const key in obj) {
const value = obj[key];
if (typeof value === "number") {
// Found one -- return it (this is #2)
console.log(`Found ${value}, returning it`);
return value;
} else if (value && typeof value === "object") {
// Found an object -- recurse (this is #3)
console.log(`Found object, recursing`);
const result = find(value);
// Did recursion find a number?
if (typeof result === "number") {
// Yes, return it (this is #3.1)
console.log(`Recursive call found it`);
return result;
}
}
}
// Nothing found, return `undefined` (explicitly or implicitly)
// This is #4
// This is explicitly: `return undefined;`
console.log(`Didn't find anything`);
};
// Found at the top level:
console.log(find({answer: 42}));
// Found nested
console.log(find({
s: "string",
o: {
n: null,
o: {
s: "string",
answer: 42,
},
},
n: null,
}));
// Not found
console.log(find({}));
.as-console-wrapper {
max-height: 100% !important;
}
A simple way to organize breakable deep recursion is to use a recursive generator. When we consume it in a for-of loop and break the loop, all pending recursions will be stopped automatically:
function* values(obj) {
if (obj instanceof Object)
for (let val of Object.values(obj))
yield* values(val);
else
yield obj;
}
//
obj = {deep: {object: 'hey'}, here: {be: {dragons: ['a', 'b', {see: 42}, 'skip']}}}
for (let val of values(obj))
if (typeof val === 'number') {
console.log('Number', val, 'found')
break
}
The loop can be abstracted away as well:
function find(iter, predicate) {
for (let x of iter)
if (predicate(x))
return x
}
and then
num = find(values(obj), x => typeof x === 'number')
I primarily work with React and often find that when I write a function that relies on a component's state, I have to perform a check to see if the piece of state is defined before performing any actions.
For example: I have a function that uses .map() to loop over an array of objects fetched from a database and generates jsx for each object in the array. This function is called in the render() function of my component. The first time render() is called, the initial array is empty. This results in an error because, of course, the first index of the array is undefined.
I have been circumventing this by making a conditional check to see if the value of the array is undefined or not. This process of writing an if statement each time feels a little clumsy and I was wondering if there is a better way to perform this check or a way to avoid it entirely.
Check the array before using map:
arr && arr.map()
OR,
arr && arr.length && arr.map() // if you want to map only if not empty array
OR,
We can even use like this (as commented by devserkan):
(arr || []).map()
As per your comment:
I wish there was a safe navigation operator like with C# (arr?.map())
Yes, obviously. This is called optional chaining in JavaScript which is still in proposal. If it is accepted, then you may use like this:
arr?.map()
You can see it in staging 1 for which you may use babel preset stage1
But obviously, except the checking array length, your requirement will not be fulfilled:
This results in an error because, of course, the first index of the array is undefined.
So, I suggest you to use:
arr && arr.length && arr.map()
What you actually need here is called optional chaining:
obj?.a?.b?.c // no error if a, b, or c don't exist or are undefined/null
The ?. is the existential operator and it allows you to access properties safely and won't throw if the property is missing. However optional chaining is not yet part of JavaScript but has been proposed and is in stage 3, see State 3 of TC39.
But, using proxies and a Maybe class, you can implement optional chaining and return a default value when the chain fails.
A wrap() function is used to wrap objects on which you want to apply optional chaining. Internally, wrap creates a Proxy around your object and manages missing values using a Maybe wrapper.
At the end of the chain, you unwrap the value by chaining getOrElse(default) with a default value which is returned when the chain is not valid:
const obj = {
a: 1,
b: {
c: [4, 1, 2]
},
c: () => 'yes'
};
console.log(wrap(obj).a.getOrElse(null)) // returns 1
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null)) // returns null
console.log(wrap(obj).b.c.getOrElse([])) // returns [4, 1, 2]
console.log(wrap(obj).b.c[0].getOrElse(null)) // returns 4
console.log(wrap(obj).b.c[100].getOrElse(-1)) // returns -1
console.log(wrap(obj).c.getOrElse(() => 'no')()) // returns 'yes'
console.log(wrap(obj).d.getOrElse(() => 'no')()) // returns 'no'
wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2
The complete example:
class Maybe {
constructor(value) {
this.__value = value;
}
static of(value){
if (value instanceof Maybe) return value;
return new Maybe(value);
}
getOrElse(elseVal) {
return this.isNothing() ? elseVal : this.__value;
}
isNothing() {
return this.__value === null || this.__value === undefined;
}
map(fn) {
return this.isNothing()
? Maybe.of(null)
: Maybe.of(fn(this.__value));
}
}
function wrap(obj) {
function fix(object, property) {
const value = object[property];
return typeof value === 'function' ? value.bind(object) : value;
}
return new Proxy(Maybe.of(obj), {
get: function(target, property) {
if (property in target) {
return fix(target, property);
} else {
return wrap(target.map(val => fix(val, property)));
}
}
});
}
const obj = { a: 1, b: { c: [4, 1, 2] }, c: () => 'yes' };
console.log(wrap(obj).a.getOrElse(null))
console.log(wrap(obj).a.b.c.d.e.f.getOrElse(null))
console.log(wrap(obj).b.c.getOrElse([]))
console.log(wrap(obj).b.c[0].getOrElse(null))
console.log(wrap(obj).b.c[100].getOrElse(-1))
console.log(wrap(obj).c.getOrElse(() => 'no')())
console.log(wrap(obj).d.getOrElse(() => 'no')())
wrap(obj).noArray.getOrElse([1]).forEach(v => console.log(v)) // Shows 1
wrap(obj).b.c.getOrElse([]).forEach(v => console.log(v)) // Shows 4, 1, 2
I am trying to solve an extra credit problem using recursion. Basically there is a "tree" that has matching "leaves" and I need to use recursion to check those matching leaves and return true if they match and return false if they do not.
I have no idea how to do this and no materials I can find are helping me understand recursion any better. This is for an online program to learn how to program.
Any help would be appreciated!
Psuedo:
// initialize some value
// initialize some flag.. boolean
// initialize some helper function and pass obj...leaf checker recursive function
// check all the keys ...for loop/forEach
// if a key is an object && value is undefined
// assign value
// return
// if a value is an object ==> recurse
// if a value is found and it doesn't match our initial value
// trip our flag to false
// return
// return true or false
const checkMatchingLeaves = (obj) => {
};
My attempt:
const checkMatchingLeaves = (obj) => {
// return true if every property on `obj` is the same
// otherwise return false
let checker = Object.values(obj);
if (Object.values(obj).length === 1) return true;
if (checker.map(checker[i] === checker[i + 1])) {
i > checker.length; i++;
}
};
This isn't exactly what (I think) you're asking for, but you should be able to use it as a template to figure out what to do:
// searchTree takes a value to try to match and an array/primitive
// value.
function searchTree(val, node) {
// Check if it's a value or an array. If it's a value we can
// do the test and return, otherwise we recursively call
// searchTree on all the children.
// Array.some returns true if any of the function calls return
// true. This is a shorthand for your boolean flag: it lets us
// return early as soon as we find a match.
return Array.isArray(node) ?
node.some(child => searchTree(val, child)) : // recursive call
val === node;
}
searchTree(3, [1, 2, [8], [[[3]]]]); // true
searchTree('abc', 'a'); // false
searchTree('abc', ['ab', 'bc', ['abc']]); // true
This is a DFS search implementation.
is there any function or any fast way to check if some value in our object startsWith e.g asd
Example:
let obj = {
'child' : {
'child_key': 'asdfghhj'
},
'free': 'notasd',
'with': 'asdhaheg'
}
// check here if our obj has value that startsWith('asd')
Regards
Use #trincot's solution if you really don't care about which node/value matched. It's straightforward, well-written, and solves your problem very effectively.
If you want more than just a Boolean value as the result of your digging, read along ...
I really doubt your need for this, but if your object is significantly large, you will want an early exit behaviour – what this means is that as soon as a match is found, iteration through your input data will stop and true/false result will be returned immediately. #trincot's solution offers early exit, but solutions that using map, filter, or reduce offer no such behaviour.
findDeep is much more useful than just checking if a string value starts with another string value – it takes a higher-order function that is applied for each leaf node in your data.
This answer uses my findDeep procedure to define a generic anyStartsWith procedure by checking if findDeep returns undefined (no match)
It will work any any input type and it will traverse Object and Array child nodes.
const isObject = x=> Object(x) === x
const isArray = Array.isArray
const keys = Object.keys
const rest = ([x,...xs]) => xs
const findDeep = f => x => {
let make = (x,ks)=> ({node: x, keys: ks || keys(x)})
let processNode = (parents, path, {node, keys:[k,...ks]})=> {
if (k === undefined)
return loop(parents, rest(path))
else if (isArray(node[k]) || isObject(node[k]))
return loop([make(node[k]), make(node, ks), ...parents], [k, ...path])
else if (f(node[k], k))
return {parents, path: [k,...path], node}
else
return loop([{node, keys: ks}, ...parents], path)
}
let loop = ([node,...parents], path) => {
if (node === undefined)
return undefined
else
return processNode(parents, path, node)
}
return loop([make(x)], [])
}
const startsWith = x => y => y.indexOf(x) === 0
const anyStartsWith = x => xs => findDeep (startsWith(x)) (xs) !== undefined
let obj = {
'child' : {
'child_key': 'asdfghhj'
},
'free': 'notasd',
'with': 'asdhaheg'
}
console.log(anyStartsWith ('asd') (obj)) // true
console.log(anyStartsWith ('candy') (obj)) // false
You'll see this is kind of a waste of findDeep's potential, but if you don't need it's power then it's not for you.
Here's the real power of findDeep
findDeep (startsWith('asd')) (obj)
// =>
{
parents: [
{
node: {
child: {
child_key: 'asdfghhj'
},
free: 'notasd',
with: 'asdhaheg'
},
keys: [ 'free', 'with' ]
}
],
path: [ 'child_key', 'child' ],
node: {
child_key: 'asdfghhj'
}
}
The resulting object has 3 properties
parents – the full object reference to each node in the matched value's lineage
path – the path of keys to get to the matched value (stack reversed)
node – the key/value pair that matched
You can see that if we take the parent object as p and reverse the path stack, we get to the matched value
p['child']['child_key']; //=> 'asdfghhj'
Here is a function with mild ES6 usage:
function startsWithRecursive(obj, needle) {
return obj != null &&
(typeof obj === "object"
? Object.keys(obj).some( key => startsWithRecursive(obj[key], needle) )
: String(obj).startsWith(needle));
}
// Sample data
let obj = {
'child' : {
'child_key': 'asdfghhj'
},
'free': 'notasd',
'with': 'asdhaheg'
};
// Requests
console.log( 'obj, "asd":', startsWithRecursive(obj, 'asd' ) );
console.log( 'obj, "hello":', startsWithRecursive(obj, 'hello' ) );
console.log( 'null, "":', startsWithRecursive(null, '' ) );
console.log( 'undefined, "":', startsWithRecursive(undefined, '' ) );
console.log( '"test", "te":', startsWithRecursive('test', 'te' ) );
console.log( '12.5, 1:', startsWithRecursive(12.5, 1 ) );
Explanation:
The function is recursive: it calls itself as it goes through a nested object structure. The value passed as obj can fall in one of the following three categories:
It is equivalent to null (like also undefined): in that case neither a recursive call, nor a call of the startsWith method can be made: the result is false as this value obviously does not start with the given search string;
It is an object: in that case that object's property values should be inspected. This will be done through recursive calls. The some method makes sure that as soon a match has been found, the iteration stops, and no further property values are inspected. In that case some returns true. If none of the property values matched, some returns false;
It is none of the above. In that case we cast it to string (by applying the String function) and apply startsWith on it.
The value calculated in the applicable step will be returned as function result. If this was a recursive call, it will be treated as return value in the some callback, ...etc.
Note that this function also returns the correct result when you call it on a string, like so:
startsWithRecursive('test', 'te'); // true
Non-Recursive Alternative
In answer to comments about potential stack limitations, here is an alternative non-recursive function which maintains a "stack" in a variable:
function startsWithRecursive(obj, needle) {
var stack = [obj];
while (stack.length) {
obj = stack.pop();
if (obj != null) {
if (typeof obj === "object") {
stack = stack.concat(Object.keys(obj).map( key => obj[key] ));
} else {
if (String(obj).startsWith(needle)) return true;
}
}
}
return false;
}
You can recursively iterate object properties and check if property starts with prefix using find function:
function hasPropertyStartingWith(obj, prefix) {
return !!Object.keys(obj).find(key => {
if (typeof obj[key] === 'object') {
return hasPropertyStartingWith(obj[key], prefix)
}
if (typeof obj[key] === 'string') {
return obj[key].startsWith(prefix)
}
return false
})
}
console.log(hasPropertyStartingWith(obj, 'asd'))
You may get away with something as simple as using a RegExp on a JSON string, something like
var obj = {
'child': {
'child_key': 'asdfghhj'
},
'free': 'notasd',
'with': 'asdhaheg'
};
function customStartsWith(obj, prefix) {
return new RegExp(':"' + prefix + '[\\s\\S]*?"').test(JSON.stringify(obj));
}
console.log('obj, "asd":', customStartsWith(obj, 'asd'));
console.log('obj, "hello":', customStartsWith(obj, 'hello'));
console.log('null, "":', customStartsWith(null, ''));
console.log('undefined, "":', customStartsWith(undefined, ''));
console.log('"test", "te":', customStartsWith('test', 'te'));
console.log('12.5, 1:', customStartsWith(12.5, 1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.9/es5-shim.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.js"></script>
Update: Another recursive object walker that will work in a shimmed environment. This is just an example and it is easily customised.
var walk = returnExports;
var obj = {
'child': {
'child_key': 'asdfghhj'
},
'free': 'notasd',
'with': 'asdhaheg'
};
function customStartsWith(obj, prefix) {
var found = false;
walk(obj, Object.keys, function(value) {
if (typeof value === 'string' && value.startsWith(prefix)) {
found = true;
walk.BREAK;
}
});
return found;
}
console.log('obj, "asd":', customStartsWith(obj, 'asd'));
console.log('obj, "hello":', customStartsWith(obj, 'hello'));
console.log('null, "":', customStartsWith(null, ''));
console.log('undefined, "":', customStartsWith(undefined, ''));
console.log('"test", "te":', customStartsWith('test', 'te'));
console.log('12.5, 1:', customStartsWith(12.5, 1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.9/es5-shim.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.js"></script>
<script src="https://rawgithub.com/Xotic750/object-walk-x/master/lib/object-walk-x.js"></script>
I have some simple objects in an Array. I want to add new objects to the Array only if they are not already in it, based on an object property.
var o = {text: 'foo'}
var a = [o]
var checkExisting = function (list, obj) {
list.forEach(function(elem) {
if (elem.text === obj) {
return true
}
}
}
checkExisting(a, 'foo')
This doesn't work. I can't figure out why. Any help or alternatives would be great.
Because you can't return value from callback in forEach, you can use for, like this
var checkExisting = function (list, obj) {
for (var i = 0, len = list.length; i < len; i++) {
if (list[i].text === obj) {
return true;
}
}
}
This can be done very similar to how you are but with .every() because .forEach() always returns undefined. So you can't chain it either.
.every() runs a method over every element like .forEach() but if it receives a false value it will abort. Finally if every iteration returns true it will return true to the caller otherwise it will return false.
Because we return false to make it abort when a value is found (so it wont keep iterating), we then have to flip the value it returns before we return it from checkExisting.
So using that you could do something like:
var checkExisting = function (list, obj) {
return !list.every(function(elem) {
return elem.text !== obj;
});
}
Obviously you would have to extend that for error handling if the object doesn't have a property text etc.
See fiddle: http://jsfiddle.net/reLsqhkm/
And docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every