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'