I'm trying to recursively search an object that contains strings, arrays, and other objects to find an item (match a value) at the deepest level however I'm always getting undefined as the return result. I can see through some console logging that I am finding the item but it gets overwritten. Any idea where I'm going wrong?
var theCobWeb = {
biggestWeb: {
item: "comb",
biggerWeb: {
items: ["glasses", "paperclip", "bubblegum"],
smallerWeb: {
item: "toothbrush",
tinyWeb: {
items: ["toenails", "lint", "wrapper", "homework"]
}
}
},
otherBigWeb: {
item: "headphones"
}
}
};
function findItem (item, obj) {
var foundItem;
for (var key in obj) {
if (obj[key] === item) {
foundItem = obj;
} else if (Array.isArray(obj[key]) && obj[key].includes(item)) {
foundItem = obj;
} else if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
findItem(item, obj[key]);
}
}
return foundItem;
}
var foundIt = findItem('glasses', theCobWeb);
console.log('The item is here: ' + foundIt); // The item is here: undefined
Edit: cleaned up the code a bit based on feedback below.
function findItem (item, obj) {
for (var key in obj) {
if (obj[key] === item) { // if the item is a property of the object
return obj; // return the object and stop further searching
} else if (Array.isArray(obj[key]) && obj[key].includes(item)) { // if the item is inside an array property of the object
return obj; // return the object and stop the search
} else if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { // if the property is another object
var res = findItem(item, obj[key]); // get the result of the search in that sub object
if(res) return res; // return the result if the search was successful, otherwise don't return and move on to the next property
}
}
return null; // return null or any default value you want if the search is unsuccessful (must be falsy to work)
}
Note 1: Array.isArray and Array.prototype.includes already returning booleans so there is no need to check them against booleans.
Note 2: You can flip the value of a boolean using the NOT operator (!).
Note3: You have to return the result (if found) immediately after it is found so you won't waste time looking for something you already have.
Note4: The return result of the search will be an object (if found) and since objects are passed by reference not by value, changing the properties of that object will change the properties of the original object too.
Edit: Find the deepest object:
If you want to find the deepest object, you'll have to go throug every object and sub-object in the object obj and everytime you have to store the object and it's depth (if the depth of the result is bigger than the previous result of course). Here is the code with some comments (I used an internal function _find that actually get called on all the objects):
function findItem (item, obj) {
var found = null; // the result (initialized to the default return value null)
var depth = -1; // the depth of the current found element (initialized to -1 so any found element could beat this one) (matched elements will not be assigned to found unless they are deeper than this depth)
function _find(obj, d) { // a function that take an object (obj) and on which depth it is (d)
for (var key in obj) { // for each ...
// first call _find on sub-objects (pass a depth of d + 1 as we are going to a one deep bellow)
if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
_find(obj[key], d + 1);
}
// then check if this object actually contain the item (we still at the depth d)
else if (obj[key] === item || (Array.isArray(obj[key]) && obj[key].includes(item))) {
// if we found something and the depth of this object is deeper than the previously found element
if(d > depth) {
depth = d; // then assign the new depth
found = obj; // and assign the new result
}
}
}
}
_find(obj, 0); // start the party by calling _find on the object obj passed to findItem with a depth of 0
// at this point found is either the initial value (null) means nothing is found or it is an object (the deepest one)
return found;
}
"I'm trying to recursively search an object that contains strings, arrays, and other objects to find an item (match a value) at the deepest level however I'm always getting undefined as the return result."
var foundIt = findItem('glasses', theCobWeb);
console.log('The item is here: ' + foundIt); // The item is here: undefined
"The item is here ..." – where?
Well what exactly do you want as a return value? Should it just say "glasses" when it's all done? In my opinion, that's kind of pointless – fundamentally it's no better than just returning true or false.
I wrote this function a while ago because I needed to search a heap of data but also know specifically where it matched. I'd probably revise this a little bit now (or at least include type annotations), but it works as-is, so here you go.
// helpers
const keys = Object.keys
const isObject = x=> Object(x) === x
const isArray = Array.isArray
const rest = ([x,...xs])=> xs
// findDeep
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 {parents: [], path: [], node: undefined}
else
return processNode(parents, path, node)
}
return loop([make(x)], [])
}
// your sample data
var theCobWeb = {biggestWeb: {item: "comb",biggerWeb: {items: ["glasses", "paperclip", "bubblegum"],smallerWeb: {item: "toothbrush",tinyWeb: {items: ["toenails", "lint", "wrapper", "homework"]}}},otherBigWeb: {item: "headphones"}}};
// find path returns {parents, path, node}
let {path, node} = findDeep((value,key)=> value === "glasses", theCobWeb)
// path to get to the item, note it is in reverse order
console.log(path) // => [0, 'items', 'biggerWeb', 'biggestWeb']
// entire matched node
console.log(node) // => ['glasses', 'paperclip', 'bubblegum']
The basic intuition here is node[path[0]] === searchTerm
Complete path to the matched query
We get the entire key path to the matched data. This is useful because we know exactly where it is based on the root of our search. To verify the path is correct, see this example
const lookup = ([p,...path], x) =>
(p === undefined) ? x : lookup(path,x)[p]
lookup([0, 'items', 'biggerWeb', 'biggestWeb'], theCobWeb) // => 'glasses'
Unmatched query
Note if we search for something that is not found, node will be undefined
let {path, node} = findDeep((value,key)=> value === "sonic the hog", theCobWeb)
console.log(path) // => []
console.log(node) // => undefined
Searching for a specific key/value pair
The search function receives a value and key argument. Use them as you wish
let {path, node} = findDeep((value,key)=> key === 'item' && value === 'toothbrush', theCobWeb)
console.log(path) // => [ 'item', 'smallerWeb', 'biggerWeb', 'biggestWeb' ]
console.log(node) // => { item: 'toothbrush', tinyWeb: { items: [ 'toenails', 'lint', 'wrapper', 'homework' ] } }
Short circuit – 150cc
Oh and because I spoil you, findDeep will give an early return as soon as the first match is found. It won't waste computation cycles and continue iterating through your pile of data after it knows the answer. This is a good thing.
Go exploring
Have courage, be adventurous. The findDeep function above also gives a parents property on returned object. It's probably useful to you in some ways, but it's a little more complicated to explain and not really critical for answering the question. For the sake of keeping this answer simplified, I'll just mention it's there.
That's because the recursive call doesn't assign the return to the variable.
And you should check the return from the recursive call and return if true or break from the for loop if you have other logic after it.
function findItem(item, obj) {
for (var key in obj) {
if (obj[key] === item) {
return obj;
} else if (Array.isArray(obj[key]) === true && obj[key].includes(item) === true) {
return obj;
} else if (typeof obj[key] === 'object' && Array.isArray(obj[key]) === false) {
var foundItem = findItem(item, obj[key]);
if(foundItem)
return foundItem;
}
}
Related
I am trying to write a javascript recursive function that receives one parameter - nested JSON object.
The function goes through the potentially infinitely nested object and converts all the keys (property names) to a string that is stored in array. Array is returned to a place where the function was called.
Example of JSON object:
{
OBJECT1: {
ATTRIBUTE3: {
PARAMETER2: {
PROPERTY1: {
}
}
}
}
}
The object does not hold any values.
What i tried and did not work:
function convertKeysToString(obj) {
let keys = [];
for (let key in obj) {
if (typeof obj[key] === 'object') {
keys = keys.concat(convertKeysToString(obj[key]));
} else {
keys.push(key.toString());
}
}
return keys;
}
As a result, I expected that returned key is pushed to an array, but the funciton didnt get the key at all or was not pushed to keys array.
Another code I tried:
function getNestedObjectKeys(obj) {
var keys = []
var firstLevel = null
var property = Object.keys(obj)
property = property[0]
firstLevel = Object.keys(obj[property])[0]
if (firstLevel == undefined) {
return 0
}
let returnedValue = keys.unshift(getNestedObjectKeys(obj[property]))
if (returnedValue == 0) {
return Object.keys(obj[property])[0]
}
returnedValue = Object.keys(obj[property])[0]
if (returnedValue != obj[property[0]]) {
return Object.keys(obj[property])[0]
}
else if (returnedValue == firstLevel) {
return keys
}
}
The function should return the key name and push (unshift) it to string and then return it, but the unshift doesnt do what I expect and in the returnedValue is not a expected returned string.
I approached it the way that the function findd the deepest (empty) object, and starts returning the name of the key. The thing is that I must return the key name AND push it to the string, which I can't find the way to accomplish at once.
Your first solution is pretty close, but has one problem (well, one main problem): when the value is type object, you don't add its key to the array. So how is it supposed to get into the array? Give this a shot:
function convertKeysToString(obj) {
let keys = [];
for (let key in obj) {
keys.push(key.toString());
if (typeof obj[key] === 'object') {
keys = keys.concat(convertKeysToString(obj[key]));
}
}
return keys;
}
Other things you may want to consider:
typeof null is object.
typeof [] is also object.
You could have a look to object, which are truthy and typeof object.
const
getKeys = object => (keys => [
...keys.flatMap(key => object[key] && typeof object[key] === 'object'
? [key, ...getKeys(object[key])]
: [key]
)
])(Object.keys(object)),
data = { OBJECT1: { ATTRIBUTE3: { PARAMETER2: { PROPERTY1: {} } } } },
result = getKeys(data);
console.log(result);
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')
To give some background: By using Postman (the REST api tool) we are comparing XMLs to a template by converting the XMLs to JSON and compare those as Javascript objects. The comparison can handle wildcards in the values and will return a new JS object (or JSON) with only the differences. When there are no differences, I receive an empty object which is the correct state. In some cases empty values or objects are returned and we remove them from the object with a clean step.
This is how the clean function looks like:
Utils = {
clean: function(object) {
Object
.entries(object)
.forEach(([k, v]) => {
if (v && typeof v === 'object')
Utils.clean(v);
if (v && typeof v === 'object' && !Object.keys(v).length || v === null || v === undefined)
Array.isArray(object) ? object.splice(k, 1) : delete object[k];
});
return object;
}
}
This works fine for most cases except when we have an array with multiple the same empty object because of the object.splice in combination with the foreach as pointed out here.
Normally, I would use a filter function, use _.pickBy from lodash or iterate backwards through the array, but because of the layout of the clean function, I can not figure out how to do that.
Can you help me to point out what I need to do to remove multiple empty items and objects from an array correctly.
Real life testcase:
var x = {"Document":{"CstmrDrctDbtInitn":{"GrpHdr":{},"PmtInf":{"DrctDbtTxInf":[{"PmtId":{}},{"PmtId":{}},{"PmtId":{}},{"PmtId":{}},{"PmtId":{}}]}}}};
console.log(JSON.stringify(Utils.clean(x)));
// returns {"Document":{"CstmrDrctDbtInitn":{"PmtInf":{"DrctDbtTxInf":[{},{}]}}}}
// desired result: {}
Other testcases:
console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{},{},{},{}]})));
// returns {"a":[null,"c",{},{},{}]}
// desired: {"a":["b", "c"]}
console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{"d": {}},{}]})));
// returns {"a":[null,"c",{},{}]}
// desired: {"a":["b", "c"]}
console.log(JSON.stringify(Utils.clean({ "a" : [null,null,{"d": {}, "e": [null, {}]},{}]})));
// returns {"a":[null,{}]}
// desired: {}
Give this a shot, and here's a working example: https://jsfiddle.net/3rno4L7d/
Utils Object (with extra helpers)
const Utils = {
doDelete: function(val) {
return !Boolean(val) ||
Utils.isEmptyObj(val) ||
Utils.isEmptyArray(val);
},
isEmptyArray: function(val) {
return Array.isArray(val) && val.length === 0;
},
isEmptyObj: function(obj) {
return Object.keys(obj).length === 0 &&
obj.constructor === Object;
},
hasKeys: function(obj) {
return Object.keys(obj).length > 0;
},
clean: function(object) {
Object
.keys(object)
.forEach(key => {
const val = object[key];
// If dealing with an object, clean it.
if (val && typeof val === 'object') {
Utils.clean(val);
}
// If deleteable, delete and return
if (Utils.doDelete(val)) {
delete object[key];
return object;
}
// If array, loop over entries
if (Array.isArray(val)) {
let i = val.length;
// While lets us delete from the array without affecting the loop.
while (i--) {
let entry = val[i];
// If deleteable, delete from the array
if (Utils.doDelete(entry)) {
val.splice(i, 1)
} else if (Utils.hasKeys(entry)) {
// If an object, clean it
entry = Utils.clean(entry);
// Check to see if cleaned object is deleteable
if (Utils.doDelete(entry)) {
val.splice(i, 1)
}
}
}
// Once done with the array, check if deleteable
if (Utils.doDelete(val)) {
delete object[key];
}
}
});
return object;
}
}
Output
console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{},{},{},{}]})));
// Returns {"a":["b","c"]}
console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{"d": {}},{}]})));
// Returns {"a":["b","c"]}
console.log(JSON.stringify(Utils.clean({ "a" : [null,null,{"d": {}, "e": [null, {}]},{}]})));
// Returns {}
Take this:
var lists:{
item1:{}
,item2:{}
,item3:{}
,item4:{}
}
Since it's substantially empty, I want a function (maybe but not necessarily a _lodash one) that checks it and say that is empty.
Something like
is_empty(lists) // >> true (because every property resolves to an empty object)
How to?
You can iterate over the values of the object and check if all of them are empty:
var lists = {
item1:{},
item2:{},
item3:{},
item4:{}
}
//ES6:
function isEmpty(obj) {
return Object.keys(obj).every(k => !Object.keys(obj[k]).length)
}
console.log(isEmpty(lists));
// ES5
function isEmpty(obj) {
return Object.keys(obj).every(function(k) {
return !Object.keys(obj[k]).length}
)
}
console.log(isEmpty(lists));
If lists is always an object of objects, you can iterate over all values with Object.values and check that each value (inner object) has no keys:
const isEmpty = outer => Object.values(outer).every(
inner => Object.keys(inner).length === 0
);
var lists = {
item1:{}
,item2:{}
,item3:{}
,item4:{}
}
var lists2 = {
item1:{}
,item2:{}
,item3:{}
,item4:{}
,item5:{ foo: 'bar' }
}
console.log(isEmpty(lists));
console.log(isEmpty(lists2));
This solution with check for the emptyness of the eternally nested object.
Note: This will treat empty string '' and boolean false as empty as well. If you need special support for stings then may be you can do some tweaking in the below code.
const isDeeplyEmpty = item => {
if(typeof item === 'boolean') return !item;
else if(typeof item === 'number') return false;
else if(typeof item === 'object') {
return Object.keys(item).every(k => {
if(['object', 'boolean', 'number'].includes(typeof item[k])) {
return isDeeplyEmpty(item[k]);
}
return _.isEmpty(item[k]);
})
}
return !item;
};
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>