I'm working on a search filter for mine object. But he only shows exact result like:
Array data:
name => 'foo',
name => 'fa',
name => 'bar',
What i've is:
getObjects(array, 'name', 'foo');
If i search on 'f' i get nothing. when i search on 'foo' i get 'foo'. What i want is, if i search on 'f', i want 'foo' and 'fa'.
Code
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] === 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else if (i == key && obj[key] == val) {
objects.push(obj);
}
}
return objects;
}
I like to hear from you, thanks a lot!
var a = 'blabla';
alert(a.includes('la'))
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] === 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else if (i.includes(key) && obj[key].includes(val)) {
objects.push(obj);
}
}
return objects;
}
You could use String#indexOf, or with ES6 String#includes
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] === 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else if (i.indexOf(key) !== -1 && obj[key].indexOf(val) !== -1) {
objects.push(obj);
}
}
return objects;
}
var array = [{ name: 'foo' }, { name: 'fa' }, { name: 'bar' }];
console.log(getObjects(array, 'name', 'f'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Let's make this a lot simpler.
If I read the question correctly, this is your data:
var array = [{name: 'foo'},{name: 'fa'},{name: 'bar'}];
Then it's not that much work to get all the items where a specific key contains a specific value:
var array = [{name: 'foo'},{name: 'fa'},{name: 'bar'}];
function getObjects(array, key, value) {
return array.filter(function(item) { // I only want items that...
return item[key] && // Have the current key
item[key].includes(value); // And it contains the value I'm looking for.
});
}
console.log(getObjects(array, 'name', 'foo'));
console.log(getObjects(array, 'name', 'a'));
console.log(getObjects(array, 'name', 'f'));
console.log(getObjects(array, 'baz', 'woo')); // No results for bad key.
Note that you could use item.hasOwnproperty(key) && instead of item[key] &&, if that's something you need to worry about.
for
i == key && obj[key] == val
you can use
i.includes(key) && obj[key].includes(val)
Related
I have a nested object as follows, and I want to find out the key name if value is given to me.
const a = {
"key1": 12,
"key2": {
"nkey1": 123,
"nkey2": 345
}
};
const temp = (obj, val) => {
return Object.keys(obj).find(key => !(typeof obj[key] === "object") ? obj[key] === val : temp(obj[key], val))
}
console.log(temp(a, 345));
I wrote the above piece of code. But it gives me output as key2 whereas I want output as nkey2.
What am I doing wrong?
A function returns null if the value is not found, otherwise return the key.
const findKey = (obj, val) => {
if (typeof obj === "object") {
for (const key in obj) {
if (obj[key] === val) {
return key;
} else {
const result = findKey(obj[key], val);
if (result !== null) return result;
}
}
}
return null;
};
const a = {
key1: 12,
key2: {
nkey1: 123,
nkey2: 345,
nkey3: {
dkey1: 232,
dkey2: 777,
},
},
};
const output = findKey(a, 777);
console.log(output);
You can use for-in loop, to iterate over keys. If the value of that key matches val, you can return the key. Else, you can check if value is an object, and use recursion, to check if val matches any value of key in that object.
const a = {
"key1": 12,
"key2": {
"nkey1": 123,
"nkey2": 345
}
};
const temp = (obj, val) => {
for (let i in obj) {
if(obj[i] === val){
return i;
} else if(typeof obj[i] === "object"){
return temp(obj[i], val);
}
}
}
console.log(temp(a, 345));
Take a look at this:
const a = {
"key1": 12,
"key2": {
"nkey1": 123,
"nkey2": 345
}
};
// recursive loop
const find = (obj, search = null) => {
if (typeof obj === "object") {
for (const key in obj) { // loop over each property
if (obj.hasOwnProperty(key) && obj[key] === search) // if obj[key] is the value you are looking for (search)
return key; // return key
else {
let r = find(obj[key], search); // recursive call to find with the new object
if (r !== null) return r; // return key
}
}
}
return null; // default return value is null
}
console.log(12, find(a, 12));
console.log(123, find(a, 123));
console.log(345, find(a, 345));
console.log(0, find(a, 0));
The find() function returns the first value of the the array for which the condition is true.
In the case of key === "key2" the recursive function is called which returns "nkey2" which results in the condition to be true and thats why "key2" is returned.
So again maybe to clear my way of thinking: You expect the find() function to return the result of a function call which is part of a condition. This wont work.
The condition is true on key2 so find returns key2.
Hope this helps you out!
i would like to push keys inside array if found undefined or null
const obj = {
name:'ab',
edu:'av',
degres:{
a1:'',
b1:'1'
},
platform:undefined
}
i want an output like
`['a1','platform']`
as the value for a1 and platform were null and undefined
i have treid this solution but it doesnt work
function iterater(obj){
let blankValues = [];
Object.keys(obj).map((key) => {
if (obj.hasOwnProperty(key) && (typeof obj[key] === "object")) {
iterater(obj[key])
} else {
if (typeof obj[key] === "undefined" || obj[key] === ''){
blankValues.push(key);
}
}
})
return blankValues;
}
but this somehow only return ['platform'] only,but the expected output should be ['platform','a1'],i think when running iterater(obj[key]),the value of array (blankValues) gets blank as it doesnt perserve it,but please help me with appropriate logic and structure
The issue is because you're re-defining blankValues as an empty array in every iteration of the recursive loop. To fix this you could accept the array as an optional argument of the function so that values get pushed to it on each iteration.
Also note that, as #ziggy wiggy pointed out in the comments, your logic will fail when a null value is encountered as typeof obj[key] === "object" would be true. You need a specific null check too.
const obj = {
name: 'ab',
edu: 'av',
degres: {
a1: '',
b1: '1'
},
platform: undefined,
foo: null
}
function iterater(obj, arr) {
arr = arr || [];
Object.keys(obj).map((key) => {
if (obj.hasOwnProperty(key) && (typeof obj[key] === "object") && obj[key] !== null) {
iterater(obj[key], arr)
} else {
if (typeof obj[key] === "undefined" || obj[key] === null || obj[key].trim() === '') {
arr.push(key);
}
}
})
return arr;
}
console.log(iterater(obj));
Note that I also added a trim() call to test the empty string. Your previous logic would accept whitespace-filled strings as valid values.
As you said yourself, when you call iterater(obj[key]) it sets a new local blankValues and puts values in it. So i think you should put blankValues outside the function.
And then you don't have to return it (or you can if you want it as a return value).
Or you can pass blankValues as a parameter of iterater in both the main call and the "inside" call
You need to consume the result of recursive call. For example add it back to blankValues like this blankValues.push(...iterater(obj[key]))
const obj = {
name:'ab',
edu:'av',
degres:{
a1:'',
b1:'1'
},
platform:undefined
}
function iterater(obj){
let blankValues = [];
Object.keys(obj).map((key) => {
if (obj.hasOwnProperty(key) && (typeof obj[key] === "object")) {
blankValues.push(...iterater(obj[key]))
} else {
if (typeof obj[key] === "undefined" || obj[key] === ''){
blankValues.push(key);
}
}
})
return blankValues;
}
console.log(iterater(obj))
You must push the result returned by the recursive call to your array.
Change:
iterater(obj[key])
for:
blankValues.push(...iterater(obj[key]))
const obj = {
name: 'ab',
edu: 'av',
degres: {
a1: '',
b1: '1'
},
platform: undefined
}
function iterater(obj){
let blankValues = [];
Object.keys(obj).map((key) => {
if (obj.hasOwnProperty(key) && (typeof obj[key] === "object")) {
blankValues.push(...iterater(obj[key]))
} else {
if (typeof obj[key] === "undefined" || obj[key] === ''){
blankValues.push(key);
}
}
})
return blankValues;
}
console.log(iterater(obj));
Here is another way to do it using Object.entries(), Object.keys(), Array.reduce(), Array.flat() and Array.isArray(). This implementation works for arrays too.
const obj = {
name:'ab',
edu:'av',
something: [{ a: 1 }, { a: '' }],
degres:{
a1:'',
b1:'1'
},
platform:undefined
};
function getEmptyProps(obj) {
if (!Object.keys(obj).length) { return []; }
return Object.entries(obj).reduce((acc, [key, val]) => {
if (val === undefined || val === null || val.toString().trim() === '') {
acc.push(key);
} else if (Array.isArray(val)) {
acc.push(val.map(getEmptyProps).flat());
} else if (typeof val === 'object') {
acc.push(getEmptyProps(val));
}
return acc.flat();
}, []);
}
console.log(getEmptyProps(obj))
You could take a check for falsy keys and return the key, if the property is an object, the check the object.
const
getFalsy = o => Object.keys(o).reduce((r, k) => {
if (!o[k]) return [...r, k];
if (typeof o[k] === 'object') return [...r, ...getFalsy(o[k])];
return r;
}, []),
object = { name: 'ab', edu: 'av', degres: { a1: '', b1: '1' }, platform: undefined };
console.log(getFalsy(object));
I have a HUGE collection and I am looking for a property by key someplace inside the collection. What is a reliable way to get a list of references or full paths to all objects containing that key/index? I use jQuery and lodash if it helps and you can forget about infinite pointer recursion, this is a pure JSON response.
fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "d");
// [o.c]
fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "e");
// [o.c.d]
fn({ 'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}, 'd');
// [o.cc,o.cc.dd]
fwiw lodash has a _.find function that will find nested objects that are two nests deep, but it seems to fail after that. (e.g. http://codepen.io/anon/pen/bnqyh)
This should do it:
function fn(obj, key) {
if (_.has(obj, key)) // or just (key in obj)
return [obj];
// elegant:
return _.flatten(_.map(obj, function(v) {
return typeof v == "object" ? fn(v, key) : [];
}), true);
// or efficient:
var res = [];
_.forEach(obj, function(v) {
if (typeof v == "object" && (v = fn(v, key)).length)
res.push.apply(res, v);
});
return res;
}
a pure JavaScript solution would look like the following:
function findNested(obj, key, memo) {
var i,
proto = Object.prototype,
ts = proto.toString,
hasOwn = proto.hasOwnProperty.bind(obj);
if ('[object Array]' !== ts.call(memo)) memo = [];
for (i in obj) {
if (hasOwn(i)) {
if (i === key) {
memo.push(obj[i]);
} else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
findNested(obj[i], key, memo);
}
}
}
return memo;
}
here's how you'd use this function:
findNested({'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}, 'd');
and the result would be:
[{x: 9}, {y: 9}]
this will deep search an array of objects (hay) for a value (needle) then return an array with the results...
search = function(hay, needle, accumulator) {
var accumulator = accumulator || [];
if (typeof hay == 'object') {
for (var i in hay) {
search(hay[i], needle, accumulator) == true ? accumulator.push(hay) : 1;
}
}
return new RegExp(needle).test(hay) || accumulator;
}
If you can write a recursive function in plain JS (or with combination of lodash) that will be the best one (by performance), but if you want skip recursion from your side and want to go for a simple readable code (which may not be best as per performance) then you can use lodash#cloneDeepWith for any purposes where you have to traverse a object recursively.
let findValuesDeepByKey = (obj, key, res = []) => (
_.cloneDeepWith(obj, (v,k) => {k==key && res.push(v)}) && res
)
So, the callback you passes as the 2nd argument of _.cloneDeepWith will recursively traverse all the key/value pairs recursively and all you have to do is the operation you want to do with each. the above code is just a example of your case. Here is a working example:
var object = {
prop1: 'ABC1',
prop2: 'ABC2',
prop3: {
prop4: 'ABC3',
prop5Arr: [{
prop5: 'XYZ'
},
{
prop5: 'ABC4'
},
{
prop6: {
prop6NestedArr: [{
prop1: 'XYZ Nested Arr'
},
{
propFurtherNested: {key100: '100 Value'}
}
]
}
}
]
}
}
let findValuesDeepByKey = (obj, key, res = []) => (
_.cloneDeepWith(obj, (v,k) => {k==key && res.push(v)}) && res
)
console.log(findValuesDeepByKey(object, 'prop1'));
console.log(findValuesDeepByKey(object, 'prop5'));
console.log(findValuesDeepByKey(object, 'key100'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
With Deepdash you can pickDeep and then get paths from it, or indexate (build path->value object)
var obj = { 'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}
var cherry = _.pickDeep(obj,"d");
console.log(JSON.stringify(cherry));
// {"cc":{"d":{}},"dd":{"d":{}}}
var paths = _.paths(cherry);
console.log(paths);
// ["cc.d", "dd.d"]
paths = _.paths(cherry,{pathFormat:'array'});
console.log(JSON.stringify(paths));
// [["cc","d"],["dd","d"]]
var index = _.indexate(cherry);
console.log(JSON.stringify(index));
// {"cc.d":{},"dd.d":{}}
Here is a Codepen demo
Something like this would work, converting it to an object and recursing down.
function find(jsonStr, searchkey) {
var jsObj = JSON.parse(jsonStr);
var set = [];
function fn(obj, key, path) {
for (var prop in obj) {
if (prop === key) {
set.push(path + "." + prop);
}
if (obj[prop]) {
fn(obj[prop], key, path + "." + prop);
}
}
return set;
}
fn(jsObj, searchkey, "o");
}
Fiddle: jsfiddle
In case you don't see the updated answer from #eugene, this tweak allows for passing a list of Keys to search for!
// Method that will find any "message" in the Apex errors that come back after insert attempts
// Could be a validation rule, or duplicate record, or pagemessage.. who knows!
// Use in your next error toast from a wire or imperative catch path!
// message: JSON.stringify(this.findNested(error, ['message', 'stackTrace'])),
// Testing multiple keys: this.findNested({thing: 0, list: [{message: 'm'}, {stackTrace: 'st'}], message: 'm2'}, ['message', 'stackTrace'])
findNested(obj, keys, memo) {
let i,
proto = Object.prototype,
ts = proto.toString,
hasOwn = proto.hasOwnProperty.bind(obj);
if ('[object Array]' !== ts.call(memo)) memo = [];
for (i in obj) {
if (hasOwn(i)) {
if (keys.includes(i)) {
memo.push(obj[i]);
} else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
this.findNested(obj[i], keys, memo);
}
}
}
return memo.length == 0 ? null : memo;
}
Here's how I did it:
function _find( obj, field, results )
{
var tokens = field.split( '.' );
// if this is an array, recursively call for each row in the array
if( obj instanceof Array )
{
obj.forEach( function( row )
{
_find( row, field, results );
} );
}
else
{
// if obj contains the field
if( obj[ tokens[ 0 ] ] !== undefined )
{
// if we're at the end of the dot path
if( tokens.length === 1 )
{
results.push( obj[ tokens[ 0 ] ] );
}
else
{
// keep going down the dot path
_find( obj[ tokens[ 0 ] ], field.substr( field.indexOf( '.' ) + 1 ), results );
}
}
}
}
Testing it with:
var obj = {
document: {
payload: {
items:[
{field1: 123},
{field1: 456}
]
}
}
};
var results = [];
_find(obj.document,'payload.items.field1', results);
console.log(results);
Outputs
[ 123, 456 ]
We use object-scan for data processing tasks. It's pretty awesome once you've wrapped your head around how to use it.
// const objectScan = require('object-scan');
const haystack = { a: { b: { c: 'd' }, e: { f: 'g' } } };
const r = objectScan(['a.*.*'], { joined: true, rtn: 'entry' })(haystack);
console.log(r);
// => [ [ 'a.e.f', 'g' ], [ 'a.b.c', 'd' ] ]
.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
There are plenty more examples on the website.
The shortest and simplest solution:
Array.prototype.findpath = function(item,path) {
return this.find(function(f){return item==eval('f.'+path)});
}
[['id', '1111'], ['name', 'aaaaa']]
I have a list like this.
{ id: '1111', name: 'aaaa' }
And, I want to format the list to something like this.
So, I've tried to convert the list to the object in that format with reduce method of JavaScript like the below.
But, it doesn't work!
Could anyone tell me what I'm doing wrong and how to fix it, please?
Code
const result = [['id', '1111'], ['name', 'aaaaa']].reduce(
(accumulator, list, index) => {
const KEY = list[0];
const VALUE = list[1];
console.log(accumulator)
if(KEY === 'id' || KEY === 'name') {
return accumulator[KEY] = VALUE;
}
return accumulator
},
{
id: '',
name: ''
}
);
log from the console.log(accumulator)
{ id: '', name: '' }
1111 // why this is not { id: '1111', name: '' } ???
Expected Result
{ id: '1111', name: 'aaaa' }
You should return accumulator in reduce callback, but the result of accumulator[KEY] = VALUE statement is not accmulator, so remove return key word in the if clause:
if(KEY === 'id' || KEY === 'name') {
accumulator[KEY] = VALUE;
}
Only return once. In the conditional only set the property
if(KEY === 'id' || KEY === 'name') {
/*return*/ accumulator[KEY] = VALUE;
// ^^ remove the return
}
You aren't returning the full object otherwise
This code is returning the value...remove the return statement.
if(KEY === 'id' || KEY === 'name') {
return accumulator[KEY] = VALUE;
}
To
if(KEY === 'id' || KEY === 'name') {
accumulator[KEY] = VALUE;
}
I am trying to return a specific node in a JSON object structure which looks like this
{
"id":"0",
"children":[
{
"id":"1",
"children":[...]
},
{
"id":"2",
"children":[...]
}
]
}
So it's a tree-like child-parent relation. Every node has a unique ID.
I'm trying to find a specific node like this
function findNode(id, currentNode) {
if (id == currentNode.id) {
return currentNode;
} else {
currentNode.children.forEach(function (currentChild) {
findNode(id, currentChild);
});
}
}
I execute the search for example by findNode("10", rootNode). But even though the search finds a match the function always returns undefined. I have a bad feeling that the recursive function doesn't stop after finding the match and continues running an finally returns undefined because in the latter recursive executions it doesn't reach a return point, but I'm not sure how to fix this.
Please help!
When searching recursively, you have to pass the result back by returning it. You're not returning the result of findNode(id, currentChild), though.
function findNode(id, currentNode) {
var i,
currentChild,
result;
if (id == currentNode.id) {
return currentNode;
} else {
// Use a for loop instead of forEach to avoid nested functions
// Otherwise "return" will not work properly
for (i = 0; i < currentNode.children.length; i += 1) {
currentChild = currentNode.children[i];
// Search in the current child
result = findNode(id, currentChild);
// Return the result if the node has been found
if (result !== false) {
return result;
}
}
// The node has not been found and we have no more options
return false;
}
}
function findNode(id, currentNode) {
if (id == currentNode.id) {
return currentNode;
} else {
var result;
currentNode.children.forEach(function(node){
if(node.id == id){
result = node;
return;
}
});
return (result ? result : "No Node Found");
}
}
console.log(findNode("10", node));
This method will return the node if it present in the node list. But this will loop through all the child of a node since we can't successfully break the forEach flow. A better implementation would look like below.
function findNode(id, currentNode) {
if (id == currentNode.id) {
return currentNode;
} else {
for(var index in currentNode.children){
var node = currentNode.children[index];
if(node.id == id)
return node;
findNode(id, node);
}
return "No Node Present";
}
}
console.log(findNode("1", node));
I use the following
var searchObject = function (object, matchCallback, currentPath, result, searched) {
currentPath = currentPath || '';
result = result || [];
searched = searched || [];
if (searched.indexOf(object) !== -1 && object === Object(object)) {
return;
}
searched.push(object);
if (matchCallback(object)) {
result.push({path: currentPath, value: object});
}
try {
if (object === Object(object)) {
for (var property in object) {
if (property.indexOf("$") !== 0) {
//if (Object.prototype.hasOwnProperty.call(object, property)) {
searchObject(object[property], matchCallback, currentPath + "." + property, result, searched);
//}
}
}
}
}
catch (e) {
console.log(object);
throw e;
}
return result;
}
Then you can write
searchObject(rootNode, function (value) { return value != null && value != undefined && value.id == '10'; });
Now this works on circular references and you can match on any field or combination of fields you like by changing the matchCallback function.
Since this old question has been brought back up, here's a different approach. We can write a fairly generic searchTree function which we then use in a findId function. searchTree does the work of traversing the object; it accepts a callback as well as the tree; the callback determines if a node matches. As well as the node, the callback is supplied two functions, next and found, which we call with no parameters to signal, respectively, that we should proceed or that we've found our match. If no match is found, we return null.
It looks like this:
const searchTree = (fn) => (obj) =>
Array.isArray(obj)
? obj.length == 0
? null
: searchTree (fn) (obj [0]) || searchTree (fn) (obj .slice (1))
: fn (
obj,
() => searchTree (fn) (obj .children || []),
() => obj
)
const findId = (target, obj) => searchTree (
(node, next, found) => node.id == target ? found () : next(),
) (tree)
const tree = {id: 1, name: 'foo', children: [
{id: 2, name: 'bar', children: []},
{id: 3, name: 'baz', children: [
{id: 17, name: 'qux', children: []},
{id: 42, name: 'corge', children: []},
{id: 99, name: 'grault', children: []}
]}
]}
console .log (findId (42, tree))
console .log (findId (57, tree))
This code is specific to the structure where subnodes are found in an array under the property children. While we can make this more generic as necessary, I find this a common structure to support.
There is a good argument that this would be better written with mutual recursion. If we wanted, we could get the same API with this version:
const searchArray = (fn) => ([x, ...xs]) =>
x === undefined
? null
: searchTree (fn) (x) || searchArray (fn) (xs)
const searchTree = (fn) => (obj) =>
fn (
obj,
() => searchArray (fn) (obj .children || []),
(x) => x
)
This works the same way. But I find the code cleaner. Either should do the job, though.
We use object-scan for our data processing needs. It's conceptually very simple, but allows for a lot of cool stuff. Here is how you could solve your question
// const objectScan = require('object-scan');
const findNode = (id, input) => objectScan(['**'], {
abort: true,
rtn: 'value',
filterFn: ({ value }) => value.id === id
})(input);
const data = { id: '0', children: [{ id: '1', children: [ { id: '3', children: [] }, { id: '4', children: [] } ] }, { id: '2', children: [ { id: '5', children: [] }, { id: '6', children: [] } ] }] };
console.log(findNode('6', data));
// => { id: '6', children: [] }
.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
Similar questions were answered several times, but I just want to add a universal method that includes nested arrays
const cars = [{
id: 1,
name: 'toyota',
subs: [{
id: 43,
name: 'supra'
}, {
id: 44,
name: 'prius'
}]
}, {
id: 2,
name: 'Jeep',
subs: [{
id: 30,
name: 'wranger'
}, {
id: 31,
name: 'sahara'
}]
}]
function searchObjectArray(arr, key, value) {
let result = [];
arr.forEach((obj) => {
if (obj[key] === value) {
result.push(obj);
} else if (obj.subs) {
result = result.concat(searchObjectArray(obj.subs, key, value));
}
});
console.log(result)
return result;
}
searchObjectArray(cars, 'id', '31')
searchObjectArray(cars, 'name', 'Jeep')
I hope this helps someone
I really liked a tree search! A tree is an extremely common data structure for most of today's complex structured tasks. So I just had similar task for lunch too. I even did some deep research, but havent actually found anything new! So what I've got for you today, is "How I implemented that in modern JS syntax":
// helper
find_subid = (id, childArray) => {
for( child of childArray ) {
foundChild = find_id( i, child ); // not sub_id, but do a check (root/full search)!
if( foundChild ) // 200
return foundChild;
}
return null; // 404
}
// actual search method
find_id = (id, parent) => (id == parent.id) : parent : find_subid(id, parent.childArray);
Recursive structure search, modification, keys/values adjustments/replacement.
Usage Example:
const results = []; // to store the search results
mapNodesRecursively(obj, ({ v, key, obj, isCircular }) => {
// do something cool with "v" (or key, or obj)
// return nothing (undefined) to keep the original value
// if we search:
if (key === 'name' && v === 'Roman'){
results.push(obj);
}
// more example flow:
if (isCircular) {
delete obj[key]; // optionally - we decide to remove circular links
} else if (v === 'Russia') {
return 'RU';
} else if (key.toLocaleLowerCase() === 'foo') {
return 'BAR';
} else if (key === 'bad_key') {
delete obj[key];
obj['good_key'] = v;
} else {
return v; // or undefined, same effect
}
});
Tips and hints:
You can use it as a search callback, just return nothing (won't affect anything) and pick values you need to your Array/Set/Map.
Notice that callback is being run on every leaf/value/key (not just objects).
Or you can use the callback to adjust particular values and even change keys. Also it automatically detects circular loops and provides a flag for you to decide how to handle them.
The code
(uses ES6)
Function itself + some example demo data
function mapNodesRecursively(obj, mapCallback, { wereSet } = {}) {
if (!wereSet) {
wereSet = new Set();
}
if (obj && (obj === Object(obj) || Array.isArray(obj))) {
wereSet.add(obj);
for (let key in obj) {
if (!obj.hasOwnProperty(key)){
continue;
}
let v = obj[key];
const isCircular = wereSet.has(v);
const mapped = mapCallback({ v, key, obj, isCircular });
if (typeof (mapped) !== 'undefined') {
obj[key] = mapped;
v = mapped;
}
if (!isCircular) {
mapNodesRecursively(v, mapCallback, { wereSet });
}
}
}
return obj;
}
let obj = {
team: [
{
name: 'Roman',
country: 'Russia',
bad_key: 123,
},
{
name: 'Igor',
country: 'Ukraine',
FOO: 'what?',
},
{
someBool: true,
country: 'Russia',
},
123,
[
1,
{
country: 'Russia',
just: 'a nested thing',
a: [{
bad_key: [{
country: 'Russia',
foo: false,
}],
}],
},
],
],
};
// output the initial data
document.getElementById('jsInput').innerHTML = JSON.stringify(obj, null, 2);
// adding some circular link (to fix with our callback)
obj.team[1].loop = obj;
mapNodesRecursively(obj, ({ v, key, obj, isCircular }) => {
if (isCircular) {
delete obj[key]; // optionally - we decide to remove circular links
} else if (v === 'Russia') {
return 'RU';
} else if (key.toLocaleLowerCase() === 'foo') {
return 'BAR';
} else if (key === 'bad_key') {
delete obj[key];
obj['good_key'] = v;
} else {
return v;
}
});
// output the result - processed object
document.getElementById('jsOutput').innerHTML = JSON.stringify(obj, null, 2);
.col {
display: inline-block;
width: 40%;
}
<div>
<h3>Recursive structure modification, keys/values adjustments/replacement</h3>
<ol>
<li>
Replacing "Russia" values with "RU"
</li>
<li>
Setting the value "BAR" for keys "FOO"
</li>
<li>
Changing the key "bad_key" to "good_key"
</li>
</ol>
<div class="col">
<h4>BEFORE</h4>
<pre id="jsInput"></pre>
</div>
<div class="col">
<h4>AFTER</h4>
<pre id="jsOutput"></pre>
</div>
</div>