Related
Is there a way (in jQuery or JavaScript) to loop through each object and it's children and grandchildren and so on?
If so... can I also read their name?
Example:
foo :{
bar:'',
child:{
grand:{
greatgrand: {
//and so on
}
}
}
}
so the loop should do something like this...
loop start
if(nameof == 'child'){
//do something
}
if(nameof == 'bar'){
//do something
}
if(nameof =='grand'){
//do something
}
loop end
You're looking for the for...in loop:
for (var key in foo)
{
if (key == "child")
// do something...
}
Be aware that for...in loops will iterate over any enumerable properties, including those that are added to the prototype of an object. To avoid acting on these properties, you can use the hasOwnProperty method to check to see if the property belongs only to that object:
for (var key in foo)
{
if (!foo.hasOwnProperty(key))
continue; // skip this property
if (key == "child")
// do something...
}
Performing the loop recursively can be as simple as writing a recursive function:
// This function handles arrays and objects
function eachRecursive(obj)
{
for (var k in obj)
{
if (typeof obj[k] == "object" && obj[k] !== null)
eachRecursive(obj[k]);
else
// do something...
}
}
You can have an Object loop recursive function with a property execute function propExec built within it.
function loopThroughObjRecurs (obj, propExec) {
for (var k in obj) {
if (typeof obj[k] === 'object' && obj[k] !== null) {
loopThroughObjRecurs(obj[k], propExec)
} else if (obj.hasOwnProperty(k)) {
propExec(k, obj[k])
}
}
}
Test here:
// I use the foo object of the OP
var foo = {
bar:'a',
child:{
b: 'b',
grand:{
greatgrand: {
c:'c'
}
}
}
}
function loopThroughObjRecurs (obj, propExec) {
for (var k in obj) {
if (typeof obj[k] === 'object' && obj[k] !== null) {
loopThroughObjRecurs(obj[k], propExec)
} else if (obj.hasOwnProperty(k)) {
propExec(k, obj[k])
}
}
}
// then apply to each property the task you want, in this case just console
loopThroughObjRecurs(foo, function(k, prop) {
console.log(k + ': ' + prop)
})
If you want to get back a tree of relationships you can use Object.keys recursively.
function paths(item) {
function iter(r, p) {
var keys = Object.keys(r);
if (keys.length) {
return keys.forEach(x => iter(r[x], p.concat(x)));
}
result.push(p);
}
var result = [];
iter(item, []);
return result;
}
var data = {
foo: {
bar: '',
child: {
grand: {
greatgrand: {}
}
}
}
};
console.log(paths(data));
This can be extended to search for values within an object structure that match a function:
function objectSearch(rootItem, matcher) {
const visited = [];
const paths = [];
function iterate(item, path) {
if (visited.includes(item)) {
return;
}
visited.push(item);
if (typeof item === "object" && item !== null) {
var keys = Object.keys(item);
if (keys.length) {
return keys.forEach(key => iterate(item[key], path.concat(key)));
}
}
if (matcher(item)) {
paths.push(path);
}
}
iterate(rootItem, []);
return paths;
}
function searchForNaNs(rootItem) {
return objectSearch(rootItem, (v) => Object.is(NaN, v));
}
var banana = {
foo: {
bar: "",
child: {
grand: {
greatgrand: {},
nanan: "NaN",
nan: NaN,
},
},
},
};
console.log("There's a NaN at", searchForNaNs(banana)[0].join("."), "in this object:", banana);
Consider using object-scan. It's powerful for data processing once you wrap your head around it.
One great thing is that the items are traversed in "delete safe" order. So if you delete one, it won't mess up the loop. And you have access to lots of other properties like parents etc.
// const objectScan = require('object-scan');
const obj = { foo: { bar: '', child: { grand: { greatgrand: { /* and so on */ } } } } };
objectScan(['**'], {
filterFn: ({ property }) => {
console.log(property);
}
})(obj);
// => greatgrand
// => grand
// => child
// => bar
// => foo
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
I would recommend using sindresorhus's map-obj & filter-obj utilities ...
Let's say I have an object:
[
{
'title': "some title"
'channel_id':'123we'
'options': [
{
'channel_id':'abc'
'image':'http://asdasd.com/all-inclusive-block-img.jpg'
'title':'All-Inclusive'
'options':[
{
'channel_id':'dsa2'
'title':'Some Recommends'
'options':[
{
'image':'http://www.asdasd.com' 'title':'Sandals'
'id':'1'
'content':{
...
I want to find the one object where the id is 1. Is there a function for something like this? I could use Underscore's _.filter method, but I would have to start at the top and filter down.
Recursion is your friend. I updated the function to account for property arrays:
function getObject(theObject) {
var result = null;
if(theObject instanceof Array) {
for(var i = 0; i < theObject.length; i++) {
result = getObject(theObject[i]);
if (result) {
break;
}
}
}
else
{
for(var prop in theObject) {
console.log(prop + ': ' + theObject[prop]);
if(prop == 'id') {
if(theObject[prop] == 1) {
return theObject;
}
}
if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
result = getObject(theObject[prop]);
if (result) {
break;
}
}
}
}
return result;
}
updated jsFiddle: http://jsfiddle.net/FM3qu/7/
Another (somewhat silly) option is to exploit the naturally recursive nature of JSON.stringify, and pass it a replacer function which runs on each nested object during the stringification process:
const input = [{
'title': "some title",
'channel_id': '123we',
'options': [{
'channel_id': 'abc',
'image': 'http://asdasd.com/all-inclusive-block-img.jpg',
'title': 'All-Inclusive',
'options': [{
'channel_id': 'dsa2',
'title': 'Some Recommends',
'options': [{
'image': 'http://www.asdasd.com',
'title': 'Sandals',
'id': '1',
'content': {}
}]
}]
}]
}];
console.log(findNestedObj(input, 'id', '1'));
function findNestedObj(entireObj, keyToFind, valToFind) {
let foundObj;
JSON.stringify(entireObj, (_, nestedValue) => {
if (nestedValue && nestedValue[keyToFind] === valToFind) {
foundObj = nestedValue;
}
return nestedValue;
});
return foundObj;
};
What worked for me was this lazy approach, not algorithmically lazy ;)
if( JSON.stringify(object_name).indexOf("key_name") > -1 ) {
console.log("Key Found");
}
else{
console.log("Key not Found");
}
If you want to get the first element whose id is 1 while object is being searched, you can use this function:
function customFilter(object){
if(object.hasOwnProperty('id') && object["id"] == 1)
return object;
for(var i=0; i<Object.keys(object).length; i++){
if(typeof object[Object.keys(object)[i]] == "object"){
var o = customFilter(object[Object.keys(object)[i]]);
if(o != null)
return o;
}
}
return null;
}
If you want to get all elements whose id is 1, then (all elements whose id is 1 are stored in result as you see):
function customFilter(object, result){
if(object.hasOwnProperty('id') && object.id == 1)
result.push(object);
for(var i=0; i<Object.keys(object).length; i++){
if(typeof object[Object.keys(object)[i]] == "object"){
customFilter(object[Object.keys(object)[i]], result);
}
}
}
Improved #haitaka answer, using the key and predicate
function deepSearch (object, key, predicate) {
if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) return object
for (let i = 0; i < Object.keys(object).length; i++) {
let value = object[Object.keys(object)[i]];
if (typeof value === "object" && value != null) {
let o = deepSearch(object[Object.keys(object)[i]], key, predicate)
if (o != null) return o
}
}
return null
}
So this can be invoked as:
var result = deepSearch(myObject, 'id', (k, v) => v === 1);
or
var result = deepSearch(myObject, 'title', (k, v) => v === 'Some Recommends');
Here is the demo: http://jsfiddle.net/a21dx6c0/
EDITED
In the same way you can find more than one object
function deepSearchItems(object, key, predicate) {
let ret = [];
if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) {
ret = [...ret, object];
}
if (Object.keys(object).length) {
for (let i = 0; i < Object.keys(object).length; i++) {
let value = object[Object.keys(object)[i]];
if (typeof value === "object" && value != null) {
let o = this.deepSearchItems(object[Object.keys(object)[i]], key, predicate);
if (o != null && o instanceof Array) {
ret = [...ret, ...o];
}
}
}
}
return ret;
}
If you're into the whole ES6 thing you can use
const findByKey = (obj, kee) => {
if (kee in obj) return obj[kee];
for(n of Object.values(obj).filter(Boolean).filter(v => typeof v === 'object')) {
let found = findByKey(n, kee)
if (found) return found
}
}
const findByProperty = (obj, predicate) => {
if (predicate(obj)) return obj
for(n of Object.values(obj).filter(Boolean).filter(v => typeof v === 'object')) {
let found = findByProperty(n, predicate)
if (found) return found
}
}
find by value is going to be a little different
let findByValue = (o, val) => {
if (o === val) return o;
if (o === NaN || o === Infinity || !o || typeof o !== 'object') return;
if (Object.values(o).includes(val)) return o;
for (n of Object.values(o)) {
const found = findByValue(n, val)
if (found) return n
}
}
then they can be used like this
const arry = [{ foo: 0 }, null, { bar: [{ baz: { nutherKey: undefined, needle: "gotcha!" } }]}]
const obj = { alice: Infinity, bob: NaN, charlie: "string", david: true, ebert: arry }
findByKey(obj, 'needle')
// 'gotcha!'
findByProperty(obj, val => val.needle === 'gotcha!')
// { nutherKey: undefined, needle: "gotcha!" }
findByValue(obj, 'gotcha!')
// { nutherKey: undefined, needle: "gotcha!" }
I found this page through googling for the similar functionalities. Based on the work provided by Zach and regularmike, I created another version which suits my needs.
BTW, teriffic work Zah and regularmike!
I'll post the code here:
function findObjects(obj, targetProp, targetValue, finalResults) {
function getObject(theObject) {
let result = null;
if (theObject instanceof Array) {
for (let i = 0; i < theObject.length; i++) {
getObject(theObject[i]);
}
}
else {
for (let prop in theObject) {
if(theObject.hasOwnProperty(prop)){
console.log(prop + ': ' + theObject[prop]);
if (prop === targetProp) {
console.log('--found id');
if (theObject[prop] === targetValue) {
console.log('----found porop', prop, ', ', theObject[prop]);
finalResults.push(theObject);
}
}
if (theObject[prop] instanceof Object || theObject[prop] instanceof Array){
getObject(theObject[prop]);
}
}
}
}
}
getObject(obj);
}
What it does is it find any object inside of obj with property name and value matching to targetProp and targetValue and will push it to the finalResults array.
And Here's the jsfiddle to play around:
https://jsfiddle.net/alexQch/5u6q2ybc/
I've created library for this purpose: https://github.com/dominik791/obj-traverse
You can use findFirst() method like this:
var foundObject = findFirst(rootObject, 'options', { 'id': '1' });
And now foundObject variable stores a reference to the object that you're looking for.
Another recursive solution, that works for arrays/lists and objects, or a mixture of both:
function deepSearchByKey(object, originalKey, matches = []) {
if(object != null) {
if(Array.isArray(object)) {
for(let arrayItem of object) {
deepSearchByKey(arrayItem, originalKey, matches);
}
} else if(typeof object == 'object') {
for(let key of Object.keys(object)) {
if(key == originalKey) {
matches.push(object);
} else {
deepSearchByKey(object[key], originalKey, matches);
}
}
}
}
return matches;
}
usage:
let result = deepSearchByKey(arrayOrObject, 'key'); // returns an array with the objects containing the key
You can use javascript some function inside a recursive function. The advantage of some is to stop looping once the child is founded. Do not use map that would be slow in large data.
const findChild = (array, id) => {
let result;
array.some(
(child) =>
(child.id === id && (result = child)) ||
(result = findChild(child.options || [], id))
);
return result;
};
findChild(array, 1)
Just use recursive function.
See example below:
const data = [
{
title: 'some title',
channel_id: '123we',
options: [
{
channel_id: 'abc',
image: 'http://asdasd.com/all-inclusive-block-img.jpg',
title: 'All-Inclusive',
options: [
{
channel_id: 'dsa2',
title: 'Some Recommends',
options: [
{
image: 'http://www.asdasd.com',
title: 'Sandals',
id: '1',
content: {},
}
]
}
]
}
]
}
]
function _find(collection, key, value) {
for (const o of collection) {
for (const [k, v] of Object.entries(o)) {
if (k === key && v === value) {
return o
}
if (Array.isArray(v)) {
const _o = _find(v, key, value)
if (_o) {
return _o
}
}
}
}
}
console.log(_find(data, 'channel_id', 'dsa2'))
We use object-scan for our data processing. It's conceptually very simple, but allows for a lot of cool stuff. Here is how you would solve your specific question
// const objectScan = require('object-scan');
const find = (id, input) => objectScan(['**'], {
abort: true,
rtn: 'value',
filterFn: ({ value }) => value.id === id
})(input);
const data = [{ title: 'some title', channel_id: '123we', options: [{ channel_id: 'abc', image: 'http://asdasd.com/all-inclusive-block-img.jpg', title: 'All-Inclusive', options: [{ channel_id: 'dsa2', title: 'Some Recommends', options: [{ image: 'http://www.asdasd.com', title: 'Sandals', id: '1', content: {} }] }] }] }];
console.log(find('1', data));
// => { image: 'http://www.asdasd.com', title: 'Sandals', id: '1', content: {} }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
Found the answer I was looking for, especially Ali Alnoaimi's solution. I made some small adjustments to allow for the search of the value as well
function deepSearchByKey(object, originalKey, originalValue, matches = []) {
if (object != null) {
if (Array.isArray(object)) {
for (let arrayItem of object) {
deepSearchByKey(arrayItem, originalKey, originalValue, matches);
}
} else if (typeof object == 'object') {
for (let key of Object.keys(object)) {
if (key == originalKey) {
if (object[key] == originalValue) {
matches.push(object);
}
} else {
deepSearchByKey(object[key], originalKey, originalValue, matches);
}
}
}
}
return matches;
}
To use:
let result = deepSearchByKey(arrayOrObject, 'key', 'value');
This will return the object containing the matching key and value.
#Iulian Pinzaru's answer was almost exactly what I needed, but it doesn't work if your objects have any null values. This version fixes that.
function deepSearch (object, key, predicate) {
if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) return object
for (let i = 0; i < Object.keys(object).length; i++) {
const nextObject = object[Object.keys(object)[i]];
if (nextObject && typeof nextObject === "object") {
let o = deepSearch(nextObject, key, predicate)
if (o != null) return o
}
}
return null
}
function getPropFromObj(obj, prop) {
let valueToFindByKey;
if (!Array.isArray(obj) && obj !== null && typeof obj === "object") {
if (obj.hasOwnProperty(prop)) {
valueToFindByKey = obj[prop];
console.log(valueToFindByKey);
} else {
let i;
for (i = 0; i < Object.keys(obj).length; i++) {
getPropFromObj(obj[Object.keys(obj)[i]], prop);
}
}
}
return null;
}
const objToInvestigate = {
employeeInformation: {
employees: {
name: "surya",
age: 27,
job: "Frontend Developer",
},
},
};
getPropFromObj(objToInvestigate, "name");
Detecting the key in the deeply nested object.
Finally return the value of the detected key.
Improved answer to take into account circular references within objects.
It also displays the path it took to get there.
In this example, I am searching for an iframe that I know is somewhere within a global object:
const objDone = []
var i = 2
function getObject(theObject, k) {
if (i < 1 || objDone.indexOf(theObject) > -1) return
objDone.push(theObject)
var result = null;
if(theObject instanceof Array) {
for(var i = 0; i < theObject.length; i++) {
result = getObject(theObject[i], i);
if (result) {
break;
}
}
}
else
{
for(var prop in theObject) {
if(prop == 'iframe' && theObject[prop]) {
i--;
console.log('iframe', theObject[prop])
return theObject[prop]
}
if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
result = getObject(theObject[prop], prop);
if (result) {
break;
}
}
}
}
if (result) console.info(k)
return result;
}
Running the following:
getObject(reader, 'reader')
gave the following output and the iframe element in the end:
iframe // (The Dom Element)
_views
views
manager
rendition
book
reader
NOTE: The path is in reverse order reader.book.rendition.manager.views._views.iframe
I'd like to suggest an amendment to Zach/RegularMike's answer (but don't have the "reputation" to be able to comment!). I found there solution a very useful basis, but suffered in my application because if there are strings within arrays it would recursively call the function for every character in the string (which caused IE11 & Edge browsers to fail with "out of stack space" errors). My simple optimization was to add the same test used in the "object" clause recursive call to the one in the "array" clause:
if (arrayElem instanceof Object || arrayElem instanceof Array) {
Thus my full code (which is now looking for all instances of a particular key, so slightly different to the original requirement) is:
// Get all instances of specified property deep within supplied object
function getPropsInObject(theObject, targetProp) {
var result = [];
if (theObject instanceof Array) {
for (var i = 0; i < theObject.length; i++) {
var arrayElem = theObject[i];
if (arrayElem instanceof Object || arrayElem instanceof Array) {
result = result.concat(getPropsInObject(arrayElem, targetProp));
}
}
} else {
for (var prop in theObject) {
var objProp = theObject[prop];
if (prop == targetProp) {
return theObject[prop];
}
if (objProp instanceof Object || objProp instanceof Array) {
result = result.concat(getPropsInObject(objProp, targetProp));
}
}
}
return result;
}
Some time ago I have made a small lib find-and, which is available on npm, for working with nested objects in a lodash manner. There's the returnFound function which returns the found object, or an object array if there's more than one object found.
E.g.,
const findAnd = require('find-and');
const a = [
{
'title': "some title",
'channel_id':'123we',
'options': [
{
'channel_id':'abc',
'image':'http://asdasd.com/all-inclusive-block-img.jpg',
'title':'All-Inclusive',
'options':[
{
'channel_id':'dsa2',
'title':'Some Recommends',
'options':[
{
'image':'http://www.asdasd.com',
'title':'Sandals',
'id':'1',
'content':{},
},
],
},
],
},
],
},
];
findAnd.returnFound(a, {id: '1'});
returns
{
'image':'http://www.asdasd.com',
'title':'Sandals',
'id':'1',
'content':{},
}
function getPath(obj, path, index = 0) {
const nestedKeys = path.split('.')
const selectedKey = nestedKeys[index]
if (index === nestedKeys.length - 1) {
return obj[selectedKey]
}
if (!obj.hasOwnProperty(selectedKey)) {
return {}
}
const nextObj = obj[selectedKey]
return Utils.hasPath(nextObj, path, index + 1)
}
You're welcome
By: Gorillaz
This function (main()) allows you to get all objects within a JSON whose key is user-defined. Here is an example:
function main(obj = {}, property) {
const views = [];
function traverse(o) {
for (var i in o) {
if (i === property) views.push(o[i]);
if (!!o[i] && typeof(o[i]) == "object") traverse(o[i]);
}
}
traverse(obj);
return views;
}
const obj = {
id: 'id at level 1',
level2: {
id: 'id at level 2',
level3: {
id: 'id at level 3',
level4: {
level5: {
id: 'id at level 5'
}
}
}
},
text: ''
}
console.log(main(obj, 'id'));
If you're already using Underscore, use _.find()
_.find(yourList, function (item) {
return item.id === 1;
});
Is there a way (in jQuery or JavaScript) to loop through each object and it's children and grandchildren and so on?
If so... can I also read their name?
Example:
foo :{
bar:'',
child:{
grand:{
greatgrand: {
//and so on
}
}
}
}
so the loop should do something like this...
loop start
if(nameof == 'child'){
//do something
}
if(nameof == 'bar'){
//do something
}
if(nameof =='grand'){
//do something
}
loop end
You're looking for the for...in loop:
for (var key in foo)
{
if (key == "child")
// do something...
}
Be aware that for...in loops will iterate over any enumerable properties, including those that are added to the prototype of an object. To avoid acting on these properties, you can use the hasOwnProperty method to check to see if the property belongs only to that object:
for (var key in foo)
{
if (!foo.hasOwnProperty(key))
continue; // skip this property
if (key == "child")
// do something...
}
Performing the loop recursively can be as simple as writing a recursive function:
// This function handles arrays and objects
function eachRecursive(obj)
{
for (var k in obj)
{
if (typeof obj[k] == "object" && obj[k] !== null)
eachRecursive(obj[k]);
else
// do something...
}
}
You can have an Object loop recursive function with a property execute function propExec built within it.
function loopThroughObjRecurs (obj, propExec) {
for (var k in obj) {
if (typeof obj[k] === 'object' && obj[k] !== null) {
loopThroughObjRecurs(obj[k], propExec)
} else if (obj.hasOwnProperty(k)) {
propExec(k, obj[k])
}
}
}
Test here:
// I use the foo object of the OP
var foo = {
bar:'a',
child:{
b: 'b',
grand:{
greatgrand: {
c:'c'
}
}
}
}
function loopThroughObjRecurs (obj, propExec) {
for (var k in obj) {
if (typeof obj[k] === 'object' && obj[k] !== null) {
loopThroughObjRecurs(obj[k], propExec)
} else if (obj.hasOwnProperty(k)) {
propExec(k, obj[k])
}
}
}
// then apply to each property the task you want, in this case just console
loopThroughObjRecurs(foo, function(k, prop) {
console.log(k + ': ' + prop)
})
If you want to get back a tree of relationships you can use Object.keys recursively.
function paths(item) {
function iter(r, p) {
var keys = Object.keys(r);
if (keys.length) {
return keys.forEach(x => iter(r[x], p.concat(x)));
}
result.push(p);
}
var result = [];
iter(item, []);
return result;
}
var data = {
foo: {
bar: '',
child: {
grand: {
greatgrand: {}
}
}
}
};
console.log(paths(data));
This can be extended to search for values within an object structure that match a function:
function objectSearch(rootItem, matcher) {
const visited = [];
const paths = [];
function iterate(item, path) {
if (visited.includes(item)) {
return;
}
visited.push(item);
if (typeof item === "object" && item !== null) {
var keys = Object.keys(item);
if (keys.length) {
return keys.forEach(key => iterate(item[key], path.concat(key)));
}
}
if (matcher(item)) {
paths.push(path);
}
}
iterate(rootItem, []);
return paths;
}
function searchForNaNs(rootItem) {
return objectSearch(rootItem, (v) => Object.is(NaN, v));
}
var banana = {
foo: {
bar: "",
child: {
grand: {
greatgrand: {},
nanan: "NaN",
nan: NaN,
},
},
},
};
console.log("There's a NaN at", searchForNaNs(banana)[0].join("."), "in this object:", banana);
Consider using object-scan. It's powerful for data processing once you wrap your head around it.
One great thing is that the items are traversed in "delete safe" order. So if you delete one, it won't mess up the loop. And you have access to lots of other properties like parents etc.
// const objectScan = require('object-scan');
const obj = { foo: { bar: '', child: { grand: { greatgrand: { /* and so on */ } } } } };
objectScan(['**'], {
filterFn: ({ property }) => {
console.log(property);
}
})(obj);
// => greatgrand
// => grand
// => child
// => bar
// => foo
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
I would recommend using sindresorhus's map-obj & filter-obj utilities ...
I'm trying to rock a more functional style, and would like to set all properties of an object (and if possible sub-objects) to a specific value, e.g. false inplace. Is there a shortcut or do I have to iterate over the properties?
var obj = {
a: true,
b: true,
c: true,
...
z: true
}
Transforms into:
var obj = {
a: false,
b: false,
c: false,
...
z: false
}
You can use underscore for the more functional style.
You can iterate over your object if missing you can change or if it sub-object reiterate and change every missing sub-object properties.
function remap(object, missingValue, suppliedValue){
var keys= _.keys(object);
return _.reduce(keys, function(memo, key){
memo[key] = object[key];
if(memo[key] === missingValue){
memo[key] = suppliedValue;
}
if(_.isObject(memo[key])){
memo[key] = remap(memo[key],missingValue,suppliedValue);
}
return memo;
}, {});
}
var h = {val : 3, b : undefined, d : undefined , k : {
a: false, b: undefined
}, c: function(){ console.log(a);}};
console.log(remap(h,undefined,false));
If you need more complex check for comparing values then use the below function.
function remap(object, complexCheck){
var keys= _.keys(object);
return _.reduce(keys, function(memo, key){
memo[key] = object[key];
memo[key] = complexCheck(memo[key]);
if(_.isObject(memo[key])){
memo[key] = remap(memo[key],complexCheck);
}
return memo;
}, {});
}
I've written something similar for performing a regex replace on fields nested within my object which matched a given pattern. I then mixed it into the underscore/lodash object so I could use it like you're wanting to do.
Modified for your purposes it could look something like this:
function(obj) {
var $this = this,
checkField = function(field) {
if (typeof field === "undefined" || field === null || typeof field === "boolean") {
return false;
} else {
return field;
}
},
checkObject = function(obj) {
if (obj instanceof Object) {
Object.getOwnPropertyNames(obj).forEach(function (val) {
if (obj[val] instanceof Array) {
obj[val] = checkArray(obj[val]);
} else if (obj[val] instanceof Object) {
obj[val] = checkObject(obj[val]);
} else {
obj[val] = checkField(obj[val]);
}
});
return obj;
} else if (obj instanceof Array) {
return checkArray(obj);
} else {
return checkField(obj);
}
},
checkArray = function(arr) {
if (arr instanceof Array) {
arr.forEach(function(val) {
if (val instanceof Object) {
obj[val] = checkObject(val);
} else {
obj[val] = checkField(val);
}
});
return arr;
} else {
return arr;
}
};
obj = checkObject(obj);
}
To add it as a mixin:
window._.mixin({
setBoolsAndSuchToFalse: function(obj) {
. . . . // The contents of the function from above
}
});
Let's say I have an object:
[
{
'title': "some title"
'channel_id':'123we'
'options': [
{
'channel_id':'abc'
'image':'http://asdasd.com/all-inclusive-block-img.jpg'
'title':'All-Inclusive'
'options':[
{
'channel_id':'dsa2'
'title':'Some Recommends'
'options':[
{
'image':'http://www.asdasd.com' 'title':'Sandals'
'id':'1'
'content':{
...
I want to find the one object where the id is 1. Is there a function for something like this? I could use Underscore's _.filter method, but I would have to start at the top and filter down.
Recursion is your friend. I updated the function to account for property arrays:
function getObject(theObject) {
var result = null;
if(theObject instanceof Array) {
for(var i = 0; i < theObject.length; i++) {
result = getObject(theObject[i]);
if (result) {
break;
}
}
}
else
{
for(var prop in theObject) {
console.log(prop + ': ' + theObject[prop]);
if(prop == 'id') {
if(theObject[prop] == 1) {
return theObject;
}
}
if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
result = getObject(theObject[prop]);
if (result) {
break;
}
}
}
}
return result;
}
updated jsFiddle: http://jsfiddle.net/FM3qu/7/
Another (somewhat silly) option is to exploit the naturally recursive nature of JSON.stringify, and pass it a replacer function which runs on each nested object during the stringification process:
const input = [{
'title': "some title",
'channel_id': '123we',
'options': [{
'channel_id': 'abc',
'image': 'http://asdasd.com/all-inclusive-block-img.jpg',
'title': 'All-Inclusive',
'options': [{
'channel_id': 'dsa2',
'title': 'Some Recommends',
'options': [{
'image': 'http://www.asdasd.com',
'title': 'Sandals',
'id': '1',
'content': {}
}]
}]
}]
}];
console.log(findNestedObj(input, 'id', '1'));
function findNestedObj(entireObj, keyToFind, valToFind) {
let foundObj;
JSON.stringify(entireObj, (_, nestedValue) => {
if (nestedValue && nestedValue[keyToFind] === valToFind) {
foundObj = nestedValue;
}
return nestedValue;
});
return foundObj;
};
What worked for me was this lazy approach, not algorithmically lazy ;)
if( JSON.stringify(object_name).indexOf("key_name") > -1 ) {
console.log("Key Found");
}
else{
console.log("Key not Found");
}
If you want to get the first element whose id is 1 while object is being searched, you can use this function:
function customFilter(object){
if(object.hasOwnProperty('id') && object["id"] == 1)
return object;
for(var i=0; i<Object.keys(object).length; i++){
if(typeof object[Object.keys(object)[i]] == "object"){
var o = customFilter(object[Object.keys(object)[i]]);
if(o != null)
return o;
}
}
return null;
}
If you want to get all elements whose id is 1, then (all elements whose id is 1 are stored in result as you see):
function customFilter(object, result){
if(object.hasOwnProperty('id') && object.id == 1)
result.push(object);
for(var i=0; i<Object.keys(object).length; i++){
if(typeof object[Object.keys(object)[i]] == "object"){
customFilter(object[Object.keys(object)[i]], result);
}
}
}
Improved #haitaka answer, using the key and predicate
function deepSearch (object, key, predicate) {
if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) return object
for (let i = 0; i < Object.keys(object).length; i++) {
let value = object[Object.keys(object)[i]];
if (typeof value === "object" && value != null) {
let o = deepSearch(object[Object.keys(object)[i]], key, predicate)
if (o != null) return o
}
}
return null
}
So this can be invoked as:
var result = deepSearch(myObject, 'id', (k, v) => v === 1);
or
var result = deepSearch(myObject, 'title', (k, v) => v === 'Some Recommends');
Here is the demo: http://jsfiddle.net/a21dx6c0/
EDITED
In the same way you can find more than one object
function deepSearchItems(object, key, predicate) {
let ret = [];
if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) {
ret = [...ret, object];
}
if (Object.keys(object).length) {
for (let i = 0; i < Object.keys(object).length; i++) {
let value = object[Object.keys(object)[i]];
if (typeof value === "object" && value != null) {
let o = this.deepSearchItems(object[Object.keys(object)[i]], key, predicate);
if (o != null && o instanceof Array) {
ret = [...ret, ...o];
}
}
}
}
return ret;
}
If you're into the whole ES6 thing you can use
const findByKey = (obj, kee) => {
if (kee in obj) return obj[kee];
for(n of Object.values(obj).filter(Boolean).filter(v => typeof v === 'object')) {
let found = findByKey(n, kee)
if (found) return found
}
}
const findByProperty = (obj, predicate) => {
if (predicate(obj)) return obj
for(n of Object.values(obj).filter(Boolean).filter(v => typeof v === 'object')) {
let found = findByProperty(n, predicate)
if (found) return found
}
}
find by value is going to be a little different
let findByValue = (o, val) => {
if (o === val) return o;
if (o === NaN || o === Infinity || !o || typeof o !== 'object') return;
if (Object.values(o).includes(val)) return o;
for (n of Object.values(o)) {
const found = findByValue(n, val)
if (found) return n
}
}
then they can be used like this
const arry = [{ foo: 0 }, null, { bar: [{ baz: { nutherKey: undefined, needle: "gotcha!" } }]}]
const obj = { alice: Infinity, bob: NaN, charlie: "string", david: true, ebert: arry }
findByKey(obj, 'needle')
// 'gotcha!'
findByProperty(obj, val => val.needle === 'gotcha!')
// { nutherKey: undefined, needle: "gotcha!" }
findByValue(obj, 'gotcha!')
// { nutherKey: undefined, needle: "gotcha!" }
I found this page through googling for the similar functionalities. Based on the work provided by Zach and regularmike, I created another version which suits my needs.
BTW, teriffic work Zah and regularmike!
I'll post the code here:
function findObjects(obj, targetProp, targetValue, finalResults) {
function getObject(theObject) {
let result = null;
if (theObject instanceof Array) {
for (let i = 0; i < theObject.length; i++) {
getObject(theObject[i]);
}
}
else {
for (let prop in theObject) {
if(theObject.hasOwnProperty(prop)){
console.log(prop + ': ' + theObject[prop]);
if (prop === targetProp) {
console.log('--found id');
if (theObject[prop] === targetValue) {
console.log('----found porop', prop, ', ', theObject[prop]);
finalResults.push(theObject);
}
}
if (theObject[prop] instanceof Object || theObject[prop] instanceof Array){
getObject(theObject[prop]);
}
}
}
}
}
getObject(obj);
}
What it does is it find any object inside of obj with property name and value matching to targetProp and targetValue and will push it to the finalResults array.
And Here's the jsfiddle to play around:
https://jsfiddle.net/alexQch/5u6q2ybc/
I've created library for this purpose: https://github.com/dominik791/obj-traverse
You can use findFirst() method like this:
var foundObject = findFirst(rootObject, 'options', { 'id': '1' });
And now foundObject variable stores a reference to the object that you're looking for.
Another recursive solution, that works for arrays/lists and objects, or a mixture of both:
function deepSearchByKey(object, originalKey, matches = []) {
if(object != null) {
if(Array.isArray(object)) {
for(let arrayItem of object) {
deepSearchByKey(arrayItem, originalKey, matches);
}
} else if(typeof object == 'object') {
for(let key of Object.keys(object)) {
if(key == originalKey) {
matches.push(object);
} else {
deepSearchByKey(object[key], originalKey, matches);
}
}
}
}
return matches;
}
usage:
let result = deepSearchByKey(arrayOrObject, 'key'); // returns an array with the objects containing the key
You can use javascript some function inside a recursive function. The advantage of some is to stop looping once the child is founded. Do not use map that would be slow in large data.
const findChild = (array, id) => {
let result;
array.some(
(child) =>
(child.id === id && (result = child)) ||
(result = findChild(child.options || [], id))
);
return result;
};
findChild(array, 1)
Just use recursive function.
See example below:
const data = [
{
title: 'some title',
channel_id: '123we',
options: [
{
channel_id: 'abc',
image: 'http://asdasd.com/all-inclusive-block-img.jpg',
title: 'All-Inclusive',
options: [
{
channel_id: 'dsa2',
title: 'Some Recommends',
options: [
{
image: 'http://www.asdasd.com',
title: 'Sandals',
id: '1',
content: {},
}
]
}
]
}
]
}
]
function _find(collection, key, value) {
for (const o of collection) {
for (const [k, v] of Object.entries(o)) {
if (k === key && v === value) {
return o
}
if (Array.isArray(v)) {
const _o = _find(v, key, value)
if (_o) {
return _o
}
}
}
}
}
console.log(_find(data, 'channel_id', 'dsa2'))
We use object-scan for our data processing. It's conceptually very simple, but allows for a lot of cool stuff. Here is how you would solve your specific question
// const objectScan = require('object-scan');
const find = (id, input) => objectScan(['**'], {
abort: true,
rtn: 'value',
filterFn: ({ value }) => value.id === id
})(input);
const data = [{ title: 'some title', channel_id: '123we', options: [{ channel_id: 'abc', image: 'http://asdasd.com/all-inclusive-block-img.jpg', title: 'All-Inclusive', options: [{ channel_id: 'dsa2', title: 'Some Recommends', options: [{ image: 'http://www.asdasd.com', title: 'Sandals', id: '1', content: {} }] }] }] }];
console.log(find('1', data));
// => { image: 'http://www.asdasd.com', title: 'Sandals', id: '1', content: {} }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
Found the answer I was looking for, especially Ali Alnoaimi's solution. I made some small adjustments to allow for the search of the value as well
function deepSearchByKey(object, originalKey, originalValue, matches = []) {
if (object != null) {
if (Array.isArray(object)) {
for (let arrayItem of object) {
deepSearchByKey(arrayItem, originalKey, originalValue, matches);
}
} else if (typeof object == 'object') {
for (let key of Object.keys(object)) {
if (key == originalKey) {
if (object[key] == originalValue) {
matches.push(object);
}
} else {
deepSearchByKey(object[key], originalKey, originalValue, matches);
}
}
}
}
return matches;
}
To use:
let result = deepSearchByKey(arrayOrObject, 'key', 'value');
This will return the object containing the matching key and value.
#Iulian Pinzaru's answer was almost exactly what I needed, but it doesn't work if your objects have any null values. This version fixes that.
function deepSearch (object, key, predicate) {
if (object.hasOwnProperty(key) && predicate(key, object[key]) === true) return object
for (let i = 0; i < Object.keys(object).length; i++) {
const nextObject = object[Object.keys(object)[i]];
if (nextObject && typeof nextObject === "object") {
let o = deepSearch(nextObject, key, predicate)
if (o != null) return o
}
}
return null
}
function getPropFromObj(obj, prop) {
let valueToFindByKey;
if (!Array.isArray(obj) && obj !== null && typeof obj === "object") {
if (obj.hasOwnProperty(prop)) {
valueToFindByKey = obj[prop];
console.log(valueToFindByKey);
} else {
let i;
for (i = 0; i < Object.keys(obj).length; i++) {
getPropFromObj(obj[Object.keys(obj)[i]], prop);
}
}
}
return null;
}
const objToInvestigate = {
employeeInformation: {
employees: {
name: "surya",
age: 27,
job: "Frontend Developer",
},
},
};
getPropFromObj(objToInvestigate, "name");
Detecting the key in the deeply nested object.
Finally return the value of the detected key.
Improved answer to take into account circular references within objects.
It also displays the path it took to get there.
In this example, I am searching for an iframe that I know is somewhere within a global object:
const objDone = []
var i = 2
function getObject(theObject, k) {
if (i < 1 || objDone.indexOf(theObject) > -1) return
objDone.push(theObject)
var result = null;
if(theObject instanceof Array) {
for(var i = 0; i < theObject.length; i++) {
result = getObject(theObject[i], i);
if (result) {
break;
}
}
}
else
{
for(var prop in theObject) {
if(prop == 'iframe' && theObject[prop]) {
i--;
console.log('iframe', theObject[prop])
return theObject[prop]
}
if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
result = getObject(theObject[prop], prop);
if (result) {
break;
}
}
}
}
if (result) console.info(k)
return result;
}
Running the following:
getObject(reader, 'reader')
gave the following output and the iframe element in the end:
iframe // (The Dom Element)
_views
views
manager
rendition
book
reader
NOTE: The path is in reverse order reader.book.rendition.manager.views._views.iframe
I'd like to suggest an amendment to Zach/RegularMike's answer (but don't have the "reputation" to be able to comment!). I found there solution a very useful basis, but suffered in my application because if there are strings within arrays it would recursively call the function for every character in the string (which caused IE11 & Edge browsers to fail with "out of stack space" errors). My simple optimization was to add the same test used in the "object" clause recursive call to the one in the "array" clause:
if (arrayElem instanceof Object || arrayElem instanceof Array) {
Thus my full code (which is now looking for all instances of a particular key, so slightly different to the original requirement) is:
// Get all instances of specified property deep within supplied object
function getPropsInObject(theObject, targetProp) {
var result = [];
if (theObject instanceof Array) {
for (var i = 0; i < theObject.length; i++) {
var arrayElem = theObject[i];
if (arrayElem instanceof Object || arrayElem instanceof Array) {
result = result.concat(getPropsInObject(arrayElem, targetProp));
}
}
} else {
for (var prop in theObject) {
var objProp = theObject[prop];
if (prop == targetProp) {
return theObject[prop];
}
if (objProp instanceof Object || objProp instanceof Array) {
result = result.concat(getPropsInObject(objProp, targetProp));
}
}
}
return result;
}
Some time ago I have made a small lib find-and, which is available on npm, for working with nested objects in a lodash manner. There's the returnFound function which returns the found object, or an object array if there's more than one object found.
E.g.,
const findAnd = require('find-and');
const a = [
{
'title': "some title",
'channel_id':'123we',
'options': [
{
'channel_id':'abc',
'image':'http://asdasd.com/all-inclusive-block-img.jpg',
'title':'All-Inclusive',
'options':[
{
'channel_id':'dsa2',
'title':'Some Recommends',
'options':[
{
'image':'http://www.asdasd.com',
'title':'Sandals',
'id':'1',
'content':{},
},
],
},
],
},
],
},
];
findAnd.returnFound(a, {id: '1'});
returns
{
'image':'http://www.asdasd.com',
'title':'Sandals',
'id':'1',
'content':{},
}
function getPath(obj, path, index = 0) {
const nestedKeys = path.split('.')
const selectedKey = nestedKeys[index]
if (index === nestedKeys.length - 1) {
return obj[selectedKey]
}
if (!obj.hasOwnProperty(selectedKey)) {
return {}
}
const nextObj = obj[selectedKey]
return Utils.hasPath(nextObj, path, index + 1)
}
You're welcome
By: Gorillaz
This function (main()) allows you to get all objects within a JSON whose key is user-defined. Here is an example:
function main(obj = {}, property) {
const views = [];
function traverse(o) {
for (var i in o) {
if (i === property) views.push(o[i]);
if (!!o[i] && typeof(o[i]) == "object") traverse(o[i]);
}
}
traverse(obj);
return views;
}
const obj = {
id: 'id at level 1',
level2: {
id: 'id at level 2',
level3: {
id: 'id at level 3',
level4: {
level5: {
id: 'id at level 5'
}
}
}
},
text: ''
}
console.log(main(obj, 'id'));
If you're already using Underscore, use _.find()
_.find(yourList, function (item) {
return item.id === 1;
});