Merge two arrays with objects - javascript

I plan to merge two objects:
var c = {
name: "doo",
arr: [
{
id: 1,
ver: 1
},
{
id: 3,
ver: 3
}
]
};
var b = {
name: "moo",
arr: [
{
id: 1,
ver: 0
},
{
id: 2,
ver: 0
}
]
};
When using Object.assign({},b,c) what happens is, that the b.arr is simply being replaced with c.arr.
My question is, how do I preserve objects inside the b.arr that are not in c.arr but still merge objects from that array when they match b.arr[0].id === c.arr[0].id. The desired outcome would look like:
{
name: "doo",
arr: [
{
id: 1,
ver: 1
},
{
id: 2,
ver: 0
},
{
id: 3,
ver: 3
}
]
}
Thanks.

You could have a look at ArrayUtils.addAll() from the apache commons

As soon as you use lodash - you may use a combination of lodash's functions. It may look a bit complex but it's not:
_.assign({}, b, c, function(objectValue, sourceValue, key, object, source) {
//merging array - custom logic
if (_.isArray(sourceValue)) {
//if the property isn't set yet - copy sourceValue
if (typeof objectValue == 'undefined') {
return sourceValue.slice();
} else if (_.isArray(objectValue)) {
//if array already exists - merge 2 arrays
_.forEach(sourceValue, function(sourceArrayItem) {
//find object with the same ID's
var objectArrayItem = _.find(objectValue, {id: sourceArrayItem.id});
if (objectArrayItem) {
//merge objects
_.assign(objectArrayItem, sourceArrayItem);
} else {
objectValue.push(sourceArrayItem);
}
});
return objectValue;
}
}
//if sourceValue isn't array - simply use it
return sourceValue;
});
See the full demo here.

Try this function:
function mergeArrayObjects (a, b) {
var tmp, // Temporary array that will be returned
// Cache values
i = 0,
max = 0;
// Check if a is an array
if ( typeof a !== 'object' || typeof a.indexOf === 'undefined')
return false;
// Check if b is an array
if ( typeof b !== 'object' || typeof b.indexOf === 'undefined')
return false;
// Populate tmp with a
tmp = a;
// For each item in b, check if a already has it. If not, add it.
for (i = 0, max = b.length; i < max; i++) {
if (tmp.indexOf(b[i]) === -1)
tmp.push(b[i]);
}
// Return the array
return tmp;
}
JsFiddle here
Note: Because I'm anal, I decided to see if this function is faster than the alternative proposed. It is.

Using lodash, I would do something like this:
var first = {
name: 'doo',
arr: [
{ id: 1, ver: 1 },
{ id: 3, ver: 3 }
]
};
var second = {
name: 'moo',
arr: [
{ id: 1, ver: 0 },
{ id: 2, ver: 0 }
]
};
_.merge(first, second, function(a, b) {
if (_.isArray(a)) {
return _.uniq(_.union(a, b), 'id');
} else {
return a;
}
});
// →
// {
// name: 'doo',
// arr: [
// { id: 1, ver: 1 },
// { id: 2, ver: 0 },
// { id: 3, ver: 3 }
// ]
// }
The merge() function let's you specify a customizer callback for things like arrays. So we just need to check it it's an array we're dealing with, and if so, use the uniq() and union() functions to find the unique values by the id property.

Related

Sort only few objects from an array of object coming from api response in React

I am looking for an efficient way of sorting the API response which is array of Objects. This Array has many fields and I just want to sort only few of them.
The Array looks like this
result = {type: Array(), status: Array(), nature: Array(), health: Array(), fitness: Array(), wealth: Array()}
and Array have name and value property like {name:"", value:""}
so let's say I just need to sort type, status, and nature out of this result. The thing that I have tried now looks like this which juts sorts one of the records.
const typeName = "type"
if(result[typeName]){
result[typeName] = sortingFunction(result[typeName], "name")
}
Now I need to sort other fields as well and also for few fields I need to sort on the basis of "value" property as well.
So please let me know if you have any efficient way of doing this.
You could create a sort function which can sort the given input object for the given keys.
I have create a sample function for sorting.
This function has two parameters.
First the object which needs to be sorted
Second option, you can pass the option for sort.
a. sortBy: Name of the property on which the function will perform the sort .
b. sortKeys: Array | String, the keys/key of the object which need to be sorted.
Function:
function sortObject(input, options = {}) {
if (!options)
return;
let keys = options.sortKeys;
let sortBy = options.sortby
if (!sortBy) {
console.error("sort by option is not defiend");
return;
}
if (!keys) {
console.error("sort keys are not defiend");
return;
}
if (Array.isArray(keys) && keys.length > 0) {
keys.forEach(item => sortObjectByKey(item, sortBy));
return;
}
if (typeof keys === "string" && keys) {
sortObjectByKey(keys, sortBy);
return;
}
function sortObjectByKey(sortKey, sortBy) {
input[sortKey].sort(function (a, b) {
let _a = (typeof a[sortBy] === "string") ? a[sortBy].toLowerCase() : a[sortBy];
let _b = (typeof b[sortBy] === "string") ? b[sortBy].toLowerCase() : b[sortBy];
if (_a < _b)
return -1
if (_a > _b)
return 1
return 0
});
}
}
Example:
//sortObject(sampleObject, { sortby: ["name", "value"], sortKeys: ["status", "type"] });
function sortObject(input, options = {}) {
if (!options)
return;
let keys = options.sortKeys;
let sortBy = options.sortby
if (!sortBy) {
console.error("sort by option is not defiend");
return;
}
if (!keys) {
console.error("sort keys are not defiend");
return;
}
if (Array.isArray(keys) && keys.length > 0) {
keys.forEach(item => sortObjectByKey(item, sortBy));
return;
}
if (typeof keys === "string" && keys) {
sortObjectByKey(keys, sortBy);
return;
}
function sortObjectByKey(sortKey, sortBy) {
input[sortKey].sort(function (a, b) {
let _a = (typeof a[sortBy] === "string") ? a[sortBy].toLowerCase() : a[sortBy];
let _b = (typeof b[sortBy] === "string") ? b[sortBy].toLowerCase() : b[sortBy];
if (_a < _b)
return -1
if (_a > _b)
return 1
return 0
});
}
}
let sampleObject = {
type: [
{ name: "c", value: 4 },
{ name: "a", value: 2 },
{ name: "b", value: 1 },
{ name: "d", value: 3 },
],
status: [
{ name: "c", value: 25 },
{ name: "a", value: 25 },
{ name: "b", value: 25 },
{ name: "d", value: 25 },
],
nature: [
{ name: "c", value: 25 },
{ name: "a", value: 25 },
{ name: "b", value: 25 },
{ name: "d", value: 25 },
],
}
sortObject(sampleObject, { sortby: "value", sortKeys: ["type"] });
sortObject(sampleObject, { sortby: "name", sortKeys: ["status", "nature"] });
console.log(sampleObject)
One way is to translate the object of arrays into an array of objects, then merge it back after sorting.
const result = {
type: ['foo', 'bar', 'baz'],
status: [4, 3, 5],
nature: ['forest', 'animal', 'water'],
health: ['athlete', 'couch potato', 'dead'],
fitness: [200, 50, 60],
wealth: [5, 2, 99]
};
// 1. combine
const combined = result.type.map((_, index) => {
return Object.fromEntries(Object.keys(result).map(key => [key, result[key][index]]));
});
// 2. example sort by status
combined.sort((a, b) => a.status - b.status)
// 3. merge
combined.forEach((object, index) => {
for (const [key, value] of Object.entries(object)) {
result[key][index] = value
}
})
console.log(result);

How to add non duplicate objects in an array in javascript?

I want to add non-duplicate objects into a new array.
var array = [
{
id: 1,
label: 'one'
},
{
id: 1,
label: 'one'
},
{
id: 2,
label: 'two'
}
];
var uniqueProducts = array.filter(function(elem, i, array) {
return array.indexOf(elem) === i;
});
console.log('uniqueProducts', uniqueProducts);
// output: [object, object, object]
live code
I like the class based approach using es6. The example uses lodash's _.isEqual method to determine equality of objects.
var array = [{
id: 1,
label: 'one'
}, {
id: 1,
label: 'one'
}, {
id: 2,
label: 'two'
}];
class UniqueArray extends Array {
constructor(array) {
super();
array.forEach(a => {
if (! this.find(v => _.isEqual(v, a))) this.push(a);
});
}
}
var unique = new UniqueArray(array);
console.log(unique);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.4/lodash.min.js"></script>
Usually, you use an object to keep track of your unique keys. Then, you convert the object to an array of all property values.
It's best to include a unique id-like property that you can use as an identifier. If you don't have one, you need to generate it yourself using JSON.stringify or a custom method. Stringifying your object will have a downside: the order of the keys does not have to be consistent.
You could create an objectsAreEqual method with support for deep comparison, but this will slow your function down immensely.
In two steps:
var array=[{id:1,label:"one"},{id:1,label:"one"},{id:2,label:"two"}];
// Create a string representation of your object
function getHash(obj) {
return Object.keys(obj)
.sort() // Keys don't have to be sorted, do it manually here
.map(function(k) {
return k + "_" + obj[k]; // Prefix key name so {a: 1} != {b: 1}
})
.join("_"); // separate key-value-pairs by a _
}
function getHashBetterSolution(obj) {
return obj.id; // Include unique ID in object and use that
};
// When using `getHashBetterSolution`:
// { '1': { id: '1', label: 'one' }, '2': /*etc.*/ }
var uniquesObj = array.reduce(function(res, cur) {
res[getHash(cur)] = cur;
return res;
}, {});
// Convert back to array by looping over all keys
var uniquesArr = Object.keys(uniquesObj).map(function(k) {
return uniquesObj[k];
});
console.log(uniquesArr);
// To show the hashes
console.log(uniquesObj);
You can use Object.keys() and map() to create key for each object and filter to remove duplicates.
var array = [{
id: 1,
label: 'one'
}, {
id: 1,
label: 'one'
}, {
id: 2,
label: 'two'
}];
var result = array.filter(function(e) {
var key = Object.keys(e).map(k => e[k]).join('|');
if (!this[key]) {
this[key] = true;
return true;
}
}, {});
console.log(result)
You could use a hash table and store the found id.
var array = [{ id: 1, label: 'one' }, { id: 1, label: 'one' }, { id: 2, label: 'two' }],
uniqueProducts = array.filter(function(elem) {
return !this[elem.id] && (this[elem.id] = true);
}, Object.create(null));
console.log('uniqueProducts', uniqueProducts);
Check with all properties
var array = [{ id: 1, label: 'one' }, { id: 1, label: 'one' }, { id: 2, label: 'two' }],
keys = Object.keys(array[0]), // get the keys first in a fixed order
uniqueProducts = array.filter(function(a) {
var key = keys.map(function (k) { return a[k]; }).join('|');
return !this[key] && (this[key] = true);
}, Object.create(null));
console.log('uniqueProducts', uniqueProducts);
You can use reduce to extract out the unique array and the unique ids like this:
var array=[{id:1,label:"one"},{id:1,label:"one"},{id:2,label:"two"}];
var result = array.reduce(function(prev, curr) {
if(prev.ids.indexOf(curr.id) === -1) {
prev.array.push(curr);
prev.ids.push(curr.id);
}
return prev;
}, {array: [], ids: []});
console.log(result);
.as-console-wrapper{top:0;max-height:100%!important;}
If you don't know the keys, you can do this - create a unique key that would help you identify duplicates - so I did this:
concat the list of keys and values of the objects
Now sort them for the unique key like 1|id|label|one
This handles situations when the object properties are not in order:
var array=[{id:1,label:"one"},{id:1,label:"one"},{id:2,label:"two"}];
var result = array.reduce(function(prev, curr) {
var tracker = Object.keys(curr).concat(Object.keys(curr).map(key => curr[key])).sort().join('|');
if(!prev.tracker[tracker]) {
prev.array.push(curr);
prev.tracker[tracker] = true;
}
return prev;
}, {array: [], tracker: {}});
console.log(result);
.as-console-wrapper{top:0;max-height:100%!important;}

Create new array from iterating JSON objects and getting only 1 of its inner array

See jsfiddle here: https://jsfiddle.net/remenyLx/2/
I have data that contains objects that each have an array of images. I want only the first image of each object.
var data1 = [
{
id: 1,
images: [
{ name: '1a' },
{ name: '1b' }
]
},
{
id: 2,
images: [
{ name: '2a' },
{ name: '2b' }
]
},
{
id: 3
},
{
id: 4,
images: []
}
];
var filtered = [];
var b = data1.forEach((element, index, array) => {
if(element.images && element.images.length)
filtered.push(element.images[0].name);
});
console.log(filtered);
The output needs to be flat:
['1a', '2a']
How can I make this prettier?
I'm not too familiar with JS map, reduce and filter and I think those would make my code more sensible; the forEach feels unnecessary.
First you can filter out elements without proper images property and then map it to new array:
const filtered = data1
.filter(e => e.images && e.images.length)
.map(e => e.images[0].name)
To do this in one loop you can use reduce function:
const filtered = data1.reduce((r, e) => {
if (e.images && e.images.length) {
r.push(e.images[0].name)
}
return r
}, [])
You can use reduce() to return this result.
var data1 = [{
id: 1,
images: [{
name: '1a'
}, {
name: '1b'
}]
}, {
id: 2,
images: [{
name: '2a'
}, {
name: '2b'
}]
}, {
id: 3
}, {
id: 4,
images: []
}];
var result = data1.reduce(function(r, e) {
if (e.hasOwnProperty('images') && e.images.length) r.push(e.images[0].name);
return r;
}, [])
console.log(result);
All answers are creating NEW arrays before projecting the final result : (filter and map creates a new array each) so basically it's creating twice.
Another approach is only to yield expected values :
Using iterator functions
function* foo(g)
{
for (let i = 0; i < g.length; i++)
{
if (g[i]['images'] && g[i]["images"].length)
yield g[i]['images'][0]["name"];
}
}
var iterator = foo(data1) ;
var result = iterator.next();
while (!result.done)
{
console.log(result.value)
result = iterator.next();
}
This will not create any additional array and only return the expected values !
However if you must return an array , rather than to do something with the actual values , then use other solutions suggested here.
https://jsfiddle.net/remenyLx/7/

How do I recursively use Array.prototype.find() while returning a single object?

The bigger problem I am trying to solve is, given this data:
var data = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4, children: [
{ id: 6 },
{ id: 7, children: [
{id: 8 },
{id: 9 }
]}
]},
{ id: 5 }
]
I want to make a function findById(data, id) that returns { id: id }. For example, findById(data, 8) should return { id: 8 }, and findById(data, 4) should return { id: 4, children: [...] }.
To implement this, I used Array.prototype.find recursively, but ran into trouble when the return keeps mashing the objects together. My implementation returns the path to the specific object.
For example, when I used findById(data, 8), it returns the path to { id: 8 }:
{ id: 4, children: [ { id: 6 }, { id: 7, children: [ { id: 8}, { id: 9] } ] }
Instead I would like it to simply return
{ id: 8 }
Implementation (Node.js v4.0.0)
jsfiddle
var data = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4, children: [
{ id: 6 },
{ id: 7, children: [
{id: 8 },
{id: 9 }
]}
]},
{ id: 5 }
]
function findById(arr, id) {
return arr.find(a => {
if (a.children && a.children.length > 0) {
return a.id === id ? true : findById(a.children, id)
} else {
return a.id === id
}
})
return a
}
console.log(findById(data, 8)) // Should return { id: 8 }
// Instead it returns the "path" block: (to reach 8, you go 4->7->8)
//
// { id: 4,
// children: [ { id: 6 }, { id: 7, children: [ {id: 8}, {id: 9] } ] }
The problem what you have, is the bubbling of the find. If the id is found inside the nested structure, the callback tries to returns the element, which is interpreted as true, the value for the find.
The find method executes the callback function once for each element present in the array until it finds one where callback returns a true value. [MDN]
Instead of find, I would suggest to use a recursive style for the search with a short circuit if found.
var data = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4, children: [{ id: 6 }, { id: 7, children: [{ id: 8 }, { id: 9 }] }] }, { id: 5 }];
function findById(data, id) {
function iter(a) {
if (a.id === id) {
result = a;
return true;
}
return Array.isArray(a.children) && a.children.some(iter);
}
var result;
data.some(iter);
return result
}
console.log(findById(data, 8));
Let's consider the implementation based on recursive calls:
function findById(tree, nodeId) {
for (let node of tree) {
if (node.id === nodeId) return node
if (node.children) {
let desiredNode = findById(node.children, nodeId)
if (desiredNode) return desiredNode
}
}
return false
}
Usage
var data = [
{ id: 1 }, { id: 2 }, { id: 3 },
{ id: 4, children: [
{ id: 6 },
{ id: 7,
children: [
{ id: 8 },
{ id: 9 }
]}]},
{ id: 5 }
]
findById(data, 7 ) // {id: 7, children: [{id: 8}, {id: 9}]}
findById(data, 5 ) // {id: 5}
findById(data, 9 ) // {id: 9}
findById(data, 11) // false
To simplify the picture, imagine that:
you are the monkey sitting on the top of a palm tree;
and searching for a ripe banana, going down the tree
you are in the end and searches aren't satisfied you;
come back to the top of the tree and start again from the next branch;
if you tried all bananas on the tree and no one is satisfied you, you just assert that ripe bananas don't grow on this this palm;
but if the banana was found you come back to the top and get pleasure of eating it.
Now let's try apply it to our recursive algorithm:
Start iteration from the top nodes (from the top of the tree);
Return the node if it was found in the iteration (if a banana is ripe);
Go deep until item is found or there will be nothing to deep. Hold the result of searches to the variable (hold the result of searches whether it is banana or just nothing and come back to the top);
Return the searches result variable if it contains the desired node (eat the banana if it is your find, otherwise just remember not to come back down by this branch);
Keep iteration if node wasn't found (if banana wasn't found keep testing other branches);
Return false if after all iterations the desired node wasn't found (assert that ripe bananas doesn't grow on this tree).
Keep learning recursion it seems not easy at the first time, but this technique allows you to solve daily issues in elegant way.
I would just use a regular loop and recursive style search:
function findById(data, id) {
for(var i = 0; i < data.length; i++) {
if (data[i].id === id) {
return data[i];
} else if (data[i].children && data[i].children.length && typeof data[i].children === "object") {
findById(data[i].children, id);
}
}
}
//findById(data, 4) => Object {id: 4, children: Array[2]}
//findById(data, 8) => Object {id: 8}
I know this is an old question, but as another answer recently revived it, I'll another version into the mix.
I would separate out the tree traversal and testing from the actual predicate that we want to test with. I believe that this makes for much cleaner code.
A reduce-based solution could look like this:
const nestedFind = (pred) => (xs) =>
xs .reduce (
(res, x) => res ? res : pred(x) ? x : nestedFind (pred) (x.children || []),
undefined
)
const findById = (testId) =>
nestedFind (({id}) => id == testId)
const data = [{id: 1}, {id: 2}, {id: 3}, {id: 4, children: [{id: 6}, {id: 7, children: [{id: 8}, {id: 9}]}]}, {id: 5}]
console .log (findById (8) (data))
console .log (findById (4) (data))
console .log (findById (42) (data))
.as-console-wrapper {min-height: 100% !important; top: 0}
There are ways we could replace that reduce with an iteration on our main list. Something like this would do the same:
const nestedFind = (pred) => ([x = undefined, ...xs]) =>
x == undefined
? undefined
: pred (x)
? x
: nestedFind (pred) (x.children || []) || nestedFind (pred) (xs)
And we could make that tail-recursive without much effort.
While we could fold the two functions into one in either of these, and achieve shorter code, I think the flexibility offered by nestedFind will make other similar problems easier. However, if you're interested, the first one might look like this:
const findById = (id) => (xs) =>
xs .reduce (
(res, x) => res ? res : x.id === id ? x : findById (id) (x.children || []),
undefined
)
const data = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{
id: 4,
children: [{ id: 6 }, { id: 7, children: [{ id: 8 }, { id: 9 }] }]
},
{ id: 5 }
];
// use Array.flatMap() and Optional chaining to find children
// then Filter undefined results
const findById = (id) => (arr) => {
if (!arr.length) return null;
return (
arr.find((obj) => obj.id === id) ||
findById(id)(arr.flatMap((el) => el?.children).filter(Boolean))
);
};
const findId = (id) => findById(id)(data);
console.log(findId(12)); /* null */
console.log(findId(8)); /* { id: 8 } */
Based on Purkhalo Alex solution,
I have made a modification to his function to be able to find the ID recursively based on a given dynamic property and returning whether the value you want to find or an array of indexes to recursively reach to the object or property afterwards.
This is like find and findIndex together through arrays of objects with nested arrays of objects in a given property.
findByIdRecursive(tree, nodeId, prop = '', byIndex = false, arr = []) {
for (let [index, node] of tree.entries()) {
if (node.id === nodeId) return byIndex ? [...arr, index] : node;
if (prop.length && node[prop].length) {
let found = this.findByIdRecursive(node[prop], nodeId, prop, byIndex, [
...arr,
index
]);
if (found) return found;
}
}
return false;
}
Now you can control the property and the type of finding and get the proper result.
This can be solved with reduce.
const foundItem = data.reduce(findById(8), null)
function findById (id) {
const searchFunc = (found, item) => {
const children = item.children || []
return found || (item.id === id ? item : children.reduce(searchFunc, null))
}
return searchFunc
}
You can recursively use Array.prototype.find() in combination with Array.prototype.flatMap()
const findById = (a, id, p = "children", u) =>
a.length ? a.find(o => o.id === id) || findById(a.flatMap(o => o[p] || []), id) : u;
const tree = [{id:1}, {id:2}, {id:3}, {id:4, children:[{id: 6}, {id:7, children:[{id:8}, {id:9}]}]}, {id:5}];
console.log(findById(tree, 9)); // {id:9}
console.log(findById(tree, 10)); // undefined
If one wanted to use Array.prototype.find this is the option I chose:
findById( my_big_array, id ) {
var result;
function recursiveFind( haystack_array, needle_id ) {
return haystack_array.find( element => {
if ( !Array.isArray( element ) ) {
if( element.id === needle_id ) {
result = element;
return true;
}
} else {
return recursiveFind( element, needle_id );
}
} );
}
recursiveFind( my_big_array, id );
return result;
}
You need the result variable, because without it, the function would return the top level element in the array that contains the result, instead of a reference to the deeply nested object containing the matching id, meaning you would need to then filter it out further.
Upon looking through the other answers, my approach seems very similar to Nina Scholz's but instead uses find() instead of some().
Here is a solution that is not the shortest, but divides the problem into recursive iteration and finding an item in an iterable (not necessarily an array).
You could define two generic functions:
deepIterator: a generator that traverses a forest in pre-order fashion
iFind: a finder, like Array#find, but that works on an iterable
function * deepIterator(iterable, children="children") {
if (!iterable?.[Symbol.iterator]) return;
for (let item of iterable) {
yield item;
yield * deepIterator(item?.[children], children);
}
}
function iFind(iterator, callback, thisArg) {
for (let item of iterator) if (callback.call(thisArg, item)) return item;
}
// Demo
var data = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4, children: [{ id: 6 }, { id: 7, children: [{ id: 8 }, { id: 9 }] }] }, { id: 5 }];
console.log(iFind(deepIterator(data), ({id}) => id === 8));
In my opinion, if you want to search recursively by id, it is better to use an algorithm like this one:
function findById(data, id, prop = 'children', defaultValue = null) {
for (const item of data) {
if (item.id === id) {
return item;
}
if (Array.isArray(item[prop]) && item[prop].length) {
const element = this.findById(item[prop], id, prop, defaultValue);
if (element) {
return element;
}
}
}
return defaultValue;
}
findById(data, 2);
But I strongly suggest using a more flexible function, which can search by any key-value pair/pairs:
function findRecursive(data, keyvalues, prop = 'children', defaultValue = null, _keys = null) {
const keys = _keys || Object.keys(keyvalues);
for (const item of data) {
if (keys.every(key => item[key] === keyvalues[key])) {
return item;
}
if (Array.isArray(item[prop]) && item[prop].length) {
const element = this.findRecursive(item[prop], keyvalues, prop, defaultValue, keys);
if (element) {
return element;
}
}
}
return defaultValue;
}
findRecursive(data, {id: 2});
you can use this function:
If it finds the item so the item returns. But if it doesn't find the item, tries to find the item in sublist.
list: the main/root list
keyName: the key that you need to find the result up to it for example 'id'
keyValue: the value that must be searched
subListName: the name of 'child' array
callback: your callback function which you want to execute when item is found
function recursiveSearch(
list,
keyName = 'id',
keyValue,
subListName = 'children',
callback
) {
for (let i = 0; i < list.length; i++) {
const x = list[i]
if (x[keyName] === keyValue) {
if (callback) {
callback(list, keyName, keyValue, subListName, i)
}
return x
}
if (x[subListName] && x[subListName].length > 0) {
const item = this.recursiveSearch(
x[subListName],
keyName,
keyValue,
subListName,
callback
)
if (!item) continue
return item
}
}
},
Roko C. Buljan's solution, but more readable one:
function findById(data, id, prop = 'children', defaultValue = null) {
if (!data.length) {
return defaultValue;
}
return (
data.find(el => el.id === id) ||
findById(
data.flatMap(el => el[prop] || []),
id
)
);
}

Filter array of objects

I get an array of objects from a MongoDB through API.
I then need to filter the result furthermore (client side).
I'll work with long lists (could be some thousand of results), each object has about 10 properties with some arrays in it.
Example of an object:
{
_id: xxxxxxx,
foo: [
{ a: "b", c: "d" },
{ a: "b", c: "d" }
],
data: {
a: "b",
c: "d"
}
}
I loop the array async to improve speed:
async.filter(documents, function(value) {
// Search inside the object to check if it contains the given "value"
}, function(results) {
// Will do something with the result array
});
How can I search inside the current object to check if it contains the given value without know in which property I'll find the value?
Though I've not included the async part but I believe overall searching approach could be like this:
// Input Array
var inpArr = [{
id: 1,
foo: [{
a: "dog",
b: "cat"
}]
}, {
id: 2,
foo: [{
a: "kutta",
b: "billi"
}]
}];
var myFilter = function(val, item, index, array) {
var searchResult = scanProperties(item, val);
return searchResult;
};
// Note: pass additional argument to default filter.
// using Function.Prototype.Bind
var filterResult = inpArr.filter(myFilter.bind(null, "dog"));
alert(filterResult);
console.log(filterResult);
// Recursively scan all properties
function scanProperties(obj, val) {
var result = false;
for (var property in obj) {
if (obj.hasOwnProperty(property) && obj[property] != null) {
if (obj[property].constructor == Object) {
result = result || scanProperties(obj[property], val);
} else if (obj[property].constructor == Array) {
for (var i = 0; i < obj[property].length; i++) {
result = result || scanProperties(obj[property][i], val);
}
} else {
result = result || (obj[property] == val);
}
}
}
return result;
};
JS Fiddle Searching an Array of Objects
You can simply iterate through each and every item recursively, like this
var data = {
_id: 1243,
foo: [{
a: "b",
c: "d"
}, {
a: "b",
c: "d"
}],
data: {
a: "b",
c: "d"
}
};
function findValue(value) {
function findItems(document) {
var type = Object.prototype.toString.call(document);
if (type.indexOf("Array") + 1) {
return document.some(findItems);
} else if (type.indexOf("Object") + 1) {
return Object.keys(document).some(function(key) {
return findItems(document[key]);
});
} else {
return document === value;
}
}
return findItems;
}
console.log(findValue('dd')(data));
# false
console.log(findValue('d')(data));
# true

Categories

Resources