Convert Array of objects to deep nested object depending on specific key - javascript

I have an array of objects.
{
c1 : ["a1", "c2"],
c2 : ["b1"],
c3: ["d1"],
b1: ["e"]
d1: ["k"]
}
I need the object to be arranged in a hierarchy. like this,
{
c1: [{a1: null}, {
c2: [{
b1: "e"
}]
}],
c3: [{ d1: "k" }]
}
Note that we can omit the array in last (deepest) key: value pair. This is what I have tried until now.
for (v in hash){
hash[v].forEach(function(ar){
if(hash[ar]){
if (new_hash[v] == undefined){
new_hash[v] = []
}
new_hash[v].push({[ar] : hash[ar]})
}
})
}
I think this problem requires dynamic programming (recursion with saving the state) in which I am not good. Please help.

You could take another hash table and store there the relation between all node and take out of this for the result only node which have no parents.
To overcom the problem of nodes without children, I added an empty array, because the original wanted structure has either null or no children at all, like this node
{ b1: "e" }
where as with a null marker it should be
{ b1: [{ e: null }] }
This solution features an empty array, which can be replaced by any other value.
{ b1: [{ e: [] }] }
var hash = { c1: ["a1", "c2"], c2: ["b1"], c3: ["d1"], b1: ["e"], d1: ["k"] },
keys = Object.keys(hash),
parents = new Set(keys),
temp = {},
tree ;
keys.forEach(k => hash[k].forEach(t => {
parents.delete(t);
temp[k] = temp[k] || [];
temp[t] = temp[t] || [];
if (!temp[k].some(o => t in o)) temp[k].push({ [t]: temp[t] });
}));
tree = Object.assign({}, ...Array.from(parents, k => ({ [k]: temp[k] })));
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can walk that parent-children list without recursion to get the tree.
I left out the code to actually transform the nodes into the format you're describing because it's pretty early in the morning for me, but the transformation should be pretty straightforward.
const data = {
c1: ["a1", "c2"],
c2: ["b1"],
c3: ["d1"],
b1: ["e"],
d1: ["k"],
};
// Generate a hash of nodes, mapping them to their children and parents.
const nodes = {};
Object.entries(data).forEach(([parentId, childIds]) => {
const parent = (nodes[parentId] = nodes[parentId] || {
id: parentId,
children: [],
});
childIds.forEach(childId => {
const child = (nodes[childId] = nodes[childId] || {
id: childId,
children: [],
});
parent.children.push(child);
child.parent = parent;
});
});
// Filter in only the nodes with no parents
const rootNodes = {};
Object.values(nodes).forEach(node => {
// TODO: transform the {id, children, parent} nodes to whichever format you require
if (!node.parent) rootNodes[node.id] = node;
});
rootNodes will look like
{
c1: {
id: 'c1',
children: [
{ id: 'a1', children: [], parent: ... },
{
id: 'c2',
children: [
{
id: 'b1',
children: [ { id: 'e', children: [], parent: ... } ],
parent: ...
}
],
parent: ...
}
]
},
c3: {
id: 'c3',
children: [
{
id: 'd1',
children: [ { id: 'k', children: [], parent: ... } ],
parent: ...
}
]
}
}

You could create one function with reduce method to loop Object.keys and build new object structure and one more function to check if current key is already in object and return it.
const data = {
c1: ["a1", "c2"],
c2: ["b1"],
c3: ["d1"],
b1: ["e"],
d1: ["k"]
}
function find(obj, key) {
let result = null
for (let i in obj) {
if (obj[i] === key || i === key) {
result = obj
}
if (!result && typeof obj[i] == 'object') {
result = find(obj[i], key)
}
}
return result
}
function nest(data) {
return Object.keys(data).reduce((r, e) => {
const match = find(r, e);
if (match) {
if (!match[e]) match[e] = []
match[e].push({
[data[e]]: null
})
} else {
data[e].forEach(el => {
if (!r[e]) r[e] = [];
r[e].push({
[el]: null
})
})
}
return r;
}, {})
}
const result = nest(data);
console.log(result)

Related

How to loop the object inside key's object in react js [duplicate]

How would I find all values by specific key in a deep nested object?
For example, if I have an object like this:
const myObj = {
id: 1,
children: [
{
id: 2,
children: [
{
id: 3
}
]
},
{
id: 4,
children: [
{
id: 5,
children: [
{
id: 6,
children: [
{
id: 7,
}
]
}
]
}
]
},
]
}
How would I get an array of all values throughout all nests of this obj by the key of id.
Note: children is a consistent name, and id's won't exist outside of a children object.
So from the obj, I would like to produce an array like this:
const idArray = [1, 2, 3, 4, 5, 6, 7]
This is a bit late but for anyone else finding this, here is a clean, generic recursive function:
function findAllByKey(obj, keyToFind) {
return Object.entries(obj)
.reduce((acc, [key, value]) => (key === keyToFind)
? acc.concat(value)
: (typeof value === 'object')
? acc.concat(findAllByKey(value, keyToFind))
: acc
, [])
}
// USAGE
findAllByKey(myObj, 'id')
You could make a recursive function like this:
idArray = []
function func(obj) {
idArray.push(obj.id)
if (!obj.children) {
return
}
obj.children.forEach(child => func(child))
}
Snippet for your sample:
const myObj = {
id: 1,
children: [{
id: 2,
children: [{
id: 3
}]
},
{
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{
id: 7,
}]
}]
}]
},
]
}
idArray = []
function func(obj) {
idArray.push(obj.id)
if (!obj.children) {
return
}
obj.children.forEach(child => func(child))
}
func(myObj)
console.log(idArray)
I found steve's answer to be most suited for my needs in extrapolating this out and creating a general recursive function. That said, I encountered issues when dealing with nulls and undefined values, so I extended the condition to accommodate for this. This approach uses:
Array.reduce() - It uses an accumulator function which appends the value's onto the result array. It also splits each object into it's key:value pair which allows you to take the following steps:
Have you've found the key? If so, add it to the array;
If not, have I found an object with values? If so, the key is possibly within there. Keep digging by calling the function on this object and append the result onto the result array; and
Finally, if this is not an object, return the result array unchanged.
Hope it helps!
const myObj = {
id: 1,
children: [{
id: 2,
children: [{
id: 3
}]
},
{
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{
id: 7,
}]
}]
}]
},
]
}
function findAllByKey(obj, keyToFind) {
return Object.entries(obj)
.reduce((acc, [key, value]) => (key === keyToFind)
? acc.concat(value)
: (typeof value === 'object' && value)
? acc.concat(findAllByKey(value, keyToFind))
: acc
, []) || [];
}
const ids = findAllByKey(myObj, 'id');
console.log(ids)
You can make a generic recursive function that works with any property and any object.
This uses Object.entries(), Object.keys(), Array.reduce(), Array.isArray(), Array.map() and Array.flat().
The stopping condition is when the object passed in is empty:
const myObj = {
id: 1,
anyProp: [{
id: 2,
thing: { a: 1, id: 10 },
children: [{ id: 3 }]
}, {
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{ id: 7 }]
}]
}]
}]
};
const getValues = prop => obj => {
if (!Object.keys(obj).length) { return []; }
return Object.entries(obj).reduce((acc, [key, val]) => {
if (key === prop) {
acc.push(val);
} else {
acc.push(Array.isArray(val) ? val.map(getIds).flat() : getIds(val));
}
return acc.flat();
}, []);
}
const getIds = getValues('id');
console.log(getIds(myObj));
Note: children is a consistent name, and id's wont exist outside
of a children object.
So from the obj, I would like to produce an array like this:
const idArray = [1, 2, 3, 4, 5, 6, 7]
Given that the question does not contain any restrictions on how the output is derived from the input and that the input is consistent, where the value of property "id" is a digit and id property is defined only within "children" property, save for case of the first "id" in the object, the input JavaScript plain object can be converted to a JSON string using JSON.stringify(), RegExp /"id":\d+/g matches the "id" property and one or more digit characters following the property name, which is then mapped to .match() the digit portion of the previous match using Regexp \d+ and convert the array value to a JavaScript number using addition operator +
const myObject = {"id":1,"children":[{"id":2,"children":[{"id":3}]},{"id":4,"children":[{"id":5,"children":[{"id":6,"children":[{"id":7}]}]}]}]};
let res = JSON.stringify(myObject).match(/"id":\d+/g).map(m => +m.match(/\d+/));
console.log(res);
JSON.stringify() replacer function can alternatively be used to .push() the value of every "id" property name within the object to an array
const myObject = {"id":1,"children":[{"id":2,"children":[{"id":3}]},{"id":4,"children":[{"id":5,"children":[{"id":6,"children":[{"id":7}]}]}]}]};
const getPropValues = (o, prop) =>
(res => (JSON.stringify(o, (key, value) =>
(key === prop && res.push(value), value)), res))([]);
let res = getPropValues(myObject, "id");
console.log(res);
Since the property values of the input to be matched are digits, all the JavaScript object can be converted to a string and RegExp \D can be used to replace all characters that are not digits, spread resulting string to array, and .map() digits to JavaScript numbers
let res = [...JSON.stringify(myObj).replace(/\D/g,"")].map(Number)
Using recursion.
const myObj = { id: 1, children: [ { id: 2, children: [ { id: 3 } ] }, { id: 4, children: [ { id: 5, children: [ { id: 6, children: [ { id: 7, } ] } ] } ] }, ]},
loop = (array, key, obj) => {
if (!obj.children) return;
obj.children.forEach(c => {
if (c[key]) array.push(c[key]); // is not present, skip!
loop(array, key, c);
});
},
arr = myObj["id"] ? [myObj["id"]] : [];
loop(arr, "id", myObj);
console.log(arr);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can make a recursive function with Object.entries like so:
const myObj = {
id: 1,
children: [{
id: 2,
children: [{
id: 3
}]
},
{
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{
id: 7,
}]
}]
}]
},
]
};
function findIds(obj) {
const entries = Object.entries(obj);
let result = entries.map(e => {
if (e[0] == "children") {
return e[1].map(child => findIds(child));
} else {
return e[1];
}
});
function flatten(arr, flat = []) {
for (let i = 0, length = arr.length; i < length; i++) {
const value = arr[i];
if (Array.isArray(value)) {
flatten(value, flat);
} else {
flat.push(value);
}
}
return flat;
}
return flatten(result);
}
var ids = findIds(myObj);
console.log(ids);
Flattening function from this answer
ES5 syntax:
var myObj = {
id: 1,
children: [{
id: 2,
children: [{
id: 3
}]
},
{
id: 4,
children: [{
id: 5,
children: [{
id: 6,
children: [{
id: 7,
}]
}]
}]
},
]
};
function findIds(obj) {
const entries = Object.entries(obj);
let result = entries.map(function(e) {
if (e[0] == "children") {
return e[1].map(function(child) {
return findIds(child)
});
} else {
return e[1];
}
});
function flatten(arr, flat = []) {
for (let i = 0, length = arr.length; i < length; i++) {
const value = arr[i];
if (Array.isArray(value)) {
flatten(value, flat);
} else {
flat.push(value);
}
}
return flat;
}
return flatten(result);
}
var ids = findIds(myObj);
console.log(ids);
let str = JSON.stringify(myObj);
let array = str.match(/\d+/g).map(v => v * 1);
console.log(array); // [1, 2, 3, 4, 5, 6, 7]
We use object-scan for a lot of our data processing needs now. It makes the code much more maintainable, but does take a moment to wrap your head around. Here is how you could use it to answer your question
// const objectScan = require('object-scan');
const find = (data, needle) => objectScan([needle], { rtn: 'value' })(data);
const myObj = { id: 1, children: [{ id: 2, children: [ { id: 3 } ] }, { id: 4, children: [ { id: 5, children: [ { id: 6, children: [ { id: 7 } ] } ] } ] }] };
console.log(find(myObj, '**.id'));
// => [ 7, 6, 5, 4, 3, 2, 1 ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
import {flattenDeep} from 'lodash';
/**
* Extracts all values from an object (also nested objects)
* into a single array
*
* #param obj
* #returns
*
* #example
* const test = {
* alpha: 'foo',
* beta: {
* gamma: 'bar',
* lambda: 'baz'
* }
* }
*
* objectFlatten(test) // ['foo', 'bar', 'baz']
*/
export function objectFlatten(obj: {}) {
const result = [];
for (const prop in obj) {
const value = obj[prop];
if (typeof value === 'object') {
result.push(objectFlatten(value));
} else {
result.push(value);
}
}
return flattenDeep(result);
}
Below solution is generic which will return all values by matching nested keys as well e.g for below json object
{
"a":1,
"b":{
"a":{
"a":"red"
}
},
"c":{
"d":2
}
}
to find all values matching key "a" output should be return
[1,{a:"red"},"red"]
const findkey = (obj, key) => {
let arr = [];
if (isPrimitive(obj)) return obj;
for (let [k, val] of Object.entries(obj)) {
if (k === key) arr.push(val);
if (!isPrimitive(val)) arr = [...arr, ...findkey(val, key)];
}
return arr;
};
const isPrimitive = (val) => {
return val !== Object(val);
};

How to remove empty arrays in a nested array/object?

I have an array of objects that have array within themselves. I want to loop through the object and delete any empty arrays that I have. The object is shown below:
let a=[{children:[{children:[1,2]},{children:[5,6]}]},
{children:[{children:[]},{children:[5,6]}]},
{children:[{children:[]},{children:[]}]},
{children:[]}]
I am trying to achieve the desired result by applying the code below but I am getting an error saying cannot read property 'children' of undefined.
function removeEmpty(array){
for(var i=array.length-1;i>=0;i--){
if(array[i].children){
if(array[i].children.length){
for(var j=array[i].children.length-1;j=>0;j--){
if(array[i].children[j].children){
removeEmpty(array[i].children[j])
}else{
array[i].splice[j,1]
}
}
if(!array[i].children.length){
array.splice(i,1)
}
}else{
array.splice(i,1)
}
}
}
}
removeEmpty(a)
Expected outcome:
expected outcome =[{children:[{children:[1,2]},{children:[5,6]}]},
{children:[{children:[5,6]}]}]
If there are adjustments I can make to the existing code or use a different approach please let me know. Thank you.
To achieve your goal you can use the .reduce() method.
const a = [{
children: [{
children: [1, 2]
}, {
children: [5, 6]
}]
},
{
children: [{
children: []
}, {
children: [5, 6]
}]
},
{
children: [{
children: []
}, {
children: []
}]
},
{
children: []
}
]
const b = a.reduce((previousValue, currentValue) => {
const data = []
if (currentValue.children.length > 0) {
currentValue.children.forEach((e) => {
if (e.children.length > 0) data.push(e);
});
}
if (data.length > 0) previousValue.push({children: data});
return previousValue;
}, []);
console.log(b);
Here is a prune function that reduces a node with children. Just make sure you wrap incoming data in a node with children.
For each of the nodes children, you filter the children based on the child count.
const data = [
{ children: [{ children: [1,2] }, { children: [5,6] }] },
{ children: [{ children: [] }, { children: [5,6] }] },
{ children: [{ children: [] }, { children: [] }] },
{ children: [] },
];
const prune = (node, key = 'children') =>
node[key].reduce((prev, curr) =>
(children => children.length
? { [key]: [...prev[key], { [key]: children }] }
: prev)
(curr[key].filter(child => child[key].length)),
{ [key]: [] });
const tree = prune({ children: data });
tree.children.forEach(child => console.log(JSON.stringify(child)));
.as-console-wrapper { top: 0; max-height: 100% !important; }
.as-console-row-code { font-size: smaller !important; }
var updatedArray = children.filter(item => item.children.length > 0)

How to find a object in a nested array using recursion in JS

Consider the following deeply nested array:
const array = [
{
id: 1,
name: "bla",
children: [
{
id: 23,
name: "bla",
children: [{ id: 88, name: "bla" }, { id: 99, name: "bla" }]
},
{ id: 43, name: "bla" },
{
id: 45,
name: "bla",
children: [{ id: 43, name: "bla" }, { id: 46, name: "bla" }]
}
]
},
{
id: 12,
name: "bla",
children: [
{
id: 232,
name: "bla",
children: [{ id: 848, name: "bla" }, { id: 959, name: "bla" }]
},
{ id: 433, name: "bla" },
{
id: 445,
name: "bla",
children: [
{ id: 443, name: "bla" },
{
id: 456,
name: "bla",
children: [
{
id: 97,
name: "bla"
},
{
id: 56,
name: "bla"
}
]
}
]
}
]
},
{
id: 15,
name: "bla",
children: [
{
id: 263,
name: "bla",
children: [{ id: 868, name: "bla" }, { id: 979, name: "bla" }]
},
{ id: 483, name: "bla" },
{
id: 445,
name: "bla",
children: [{ id: 423, name: "bla" }, { id: 436, name: "bla" }]
}
]
}
];
How would I grab a certain object by key that might be deeply nested, using recursion?
I have tried this, but this won't work for nesting deeper than 2 levels, it then just returns undefined:
const findItemNested = (arr, itemId, nestingKey) => {
for (const i of arr) {
console.log(i.id);
if (i.id === itemId) {
return i;
}
if (i[nestingKey]) {
findItemNested(i[nestingKey], itemId, nestingKey);
}
}
};
The result should be:
const res = findItemNested(array, 959, "children"); >> { id: 959, name: "bla" }
This can perhaps also be achieved using .find, or just to flatten the array (by the children key), but using recursion seems like the most logical solution to me. Does anybody have a solution to this?
Thanks in advance :).
You might use a recursive reduce:
const array=[{id:1,name:"bla",children:[{id:23,name:"bla",children:[{id:88,name:"bla"},{id:99,name:"bla"}]},{id:43,name:"bla"},{id:45,name:"bla",children:[{id:43,name:"bla"},{id:46,name:"bla"}]}]},{id:12,name:"bla",children:[{id:232,name:"bla",children:[{id:848,name:"bla"},{id:959,name:"bla"}]},{id:433,name:"bla"},{id:445,name:"bla",children:[{id:443,name:"bla"},{id:456,name:"bla",children:[{id:97,name:"bla"},{id:56,name:"bla"}]}]}]},{id:15,name:"bla",children:[{id:263,name:"bla",children:[{id:868,name:"bla"},{id:979,name:"bla"}]},{id:483,name:"bla"},{id:445,name:"bla",children:[{id:423,name:"bla"},{id:436,name:"bla"}]}]}];
const findItemNested = (arr, itemId, nestingKey) => (
arr.reduce((a, item) => {
if (a) return a;
if (item.id === itemId) return item;
if (item[nestingKey]) return findItemNested(item[nestingKey], itemId, nestingKey)
}, null)
);
const res = findItemNested(array, 959, "children");
console.log(res);
This should work:
function findByIdRecursive(array, id) {
for (let index = 0; index < array.length; index++) {
const element = array[index];
if (element.id === id) {
return element;
} else {
if (element.children) {
const found = findByIdRecursive(element.children, id);
if (found) {
return found;
}
}
}
}
}
You might also use recursion with Array.find like below
const array=[{id:1,name:"bla",children:[{id:23,name:"bla",children:[{id:88,name:"bla"},{id:99,name:"bla"}]},{id:43,name:"bla"},{id:45,name:"bla",children:[{id:43,name:"bla"},{id:46,name:"bla"}]}]},{id:12,name:"bla",children:[{id:232,name:"bla",children:[{id:848,name:"bla"},{id:959,name:"bla"}]},{id:433,name:"bla"},{id:445,name:"bla",children:[{id:443,name:"bla"},{id:456,name:"bla",children:[{id:97,name:"bla"},{id:56,name:"bla"}]}]}]},{id:15,name:"bla",children:[{id:263,name:"bla",children:[{id:868,name:"bla"},{id:979,name:"bla"}]},{id:483,name:"bla"},{id:445,name:"bla",children:[{id:423,name:"bla"},{id:436,name:"bla"}]}]}];
function findById(arr, id, nestingKey) {
// if empty array then return
if(arr.length == 0) return
// return element if found else collect all children(or other nestedKey) array and run this function
return arr.find(d => d.id == id)
|| findById(arr.flatMap(d => d[nestingKey] || []), id)
|| 'Not found'
}
console.log(findById(array, 12, 'children'))
console.log(findById(array, 483, 'children'))
console.log(findById(array, 1200, 'children'))
We use object-scan for most of our data processing. It's awesome for all sorts of things, but does take a while to wrap your head around. This is how one could answer your question:
// const objectScan = require('object-scan');
const find = (data, id) => objectScan(['**(^children$).id'], {
abort: true,
rtn: 'parent',
useArraySelector: false,
filterFn: ({ value }) => value === id
})(data);
const array=[{id:1,name:"bla",children:[{id:23,name:"bla",children:[{id:88,name:"bla"},{id:99,name:"bla"}]},{id:43,name:"bla"},{id:45,name:"bla",children:[{id:43,name:"bla"},{id:46,name:"bla"}]}]},{id:12,name:"bla",children:[{id:232,name:"bla",children:[{id:848,name:"bla"},{id:959,name:"bla"}]},{id:433,name:"bla"},{id:445,name:"bla",children:[{id:443,name:"bla"},{id:456,name:"bla",children:[{id:97,name:"bla"},{id:56,name:"bla"}]}]}]},{id:15,name:"bla",children:[{id:263,name:"bla",children:[{id:868,name:"bla"},{id:979,name:"bla"}]},{id:483,name:"bla"},{id:445,name:"bla",children:[{id:423,name:"bla"},{id:436,name:"bla"}]}]}];
console.log(find(array, 12));
// => { id: 12, name: 'bla', children: [ { id: 232, name: 'bla', children: [ { id: 848, name: 'bla' }, { id: 959, name: 'bla' } ] }, { id: 433, name: 'bla' }, { id: 445, name: 'bla', children: [ { id: 443, name: 'bla' }, { id: 456, name: 'bla', children: [ { id: 97, name: 'bla' }, { id: 56, name: 'bla' } ] } ] } ] }
console.log(find(array, 483));
// => { id: 483, name: 'bla' }
console.log(find(array, 959));
// => { id: 959, name: 'bla' }
console.log(find(array, 1200));
// => undefined
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
You can do:
const array=[{id:1,name:"bla",children:[{id:23,name:"bla",children:[{id:88,name:"bla"},{id:99,name:"bla"}]},{id:43,name:"bla"},{id:45,name:"bla",children:[{id:43,name:"bla"},{id:46,name:"bla"}]}]},{id:12,name:"bla",children:[{id:232,name:"bla",children:[{id:848,name:"bla"},{id:959,name:"bla"}]},{id:433,name:"bla"},{id:445,name:"bla",children:[{id:443,name:"bla"},{id:456,name:"bla",children:[{id:97,name:"bla"},{id:56,name:"bla"}]}]}]},{id:15,name:"bla",children:[{id:263,name:"bla",children:[{id:868,name:"bla"},{id:979,name:"bla"}]},{id:483,name:"bla"},{id:445,name:"bla",children:[{id:423,name:"bla"},{id:436,name:"bla"}]}]}];
const findItemNested = (arr, itemId, nestingKey) => arr.reduce((a, c) => {
return a.length
? a
: c.id === itemId
? a.concat(c)
: c[nestingKey]
? a.concat(findItemNested(c[nestingKey], itemId, nestingKey))
: a
}, []);
const res = findItemNested(array, 959, "children");
if (res.length) {
console.log(res[0]);
}
This will use recursive find by level, it'll try to find the item in array and then call itself with the children of each item in the array:
New browsers will have Array.prototype.flatten but in this case I've added the flatten function separately.
const array = [{"id":1,"name":"bla","children":[{"id":23,"name":"bla","children":[{"id":88,"name":"bla"},{"id":99,"name":"bla"}]},{"id":43,"name":"bla"},{"id":45,"name":"bla","children":[{"id":43,"name":"bla"},{"id":46,"name":"bla"}]}]},{"id":12,"name":"bla","children":[{"id":232,"name":"bla","children":[{"id":848,"name":"bla"},{"id":959,"name":"bla"}]},{"id":433,"name":"bla"},{"id":445,"name":"bla","children":[{"id":443,"name":"bla"},{"id":456,"name":"bla","children":[{"id":97,"name":"bla"},{"id":56,"name":"bla"}]}]}]},{"id":15,"name":"bla","children":[{"id":263,"name":"bla","children":[{"id":868,"name":"bla"},{"id":979,"name":"bla"}]},{"id":483,"name":"bla"},{"id":445,"name":"bla","children":[{"id":423,"name":"bla"},{"id":436,"name":"bla"}]}]}];
const flatten = (arr) =>
arr.reduce((result, item) => result.concat(item), []);
const findBy = (findFunction, subItemsKey) => (array) =>
//array is empty (can be when children of children of children does not exist)
array.length === 0
? undefined //return undefined when array is empty
: array.find(findFunction) || //return item if found
findBy(findFunction, subItemsKey)(//call itself when item is not found
flatten(
//take children from each item and flatten it
//([[child],[child,child]])=>[child,child,child]
array.map((item) => item[subItemsKey] || []),
),
);
const findChildrenById = (array) => (value) =>
findBy((item) => item.id === value, 'children')(array);
const findInArray = findChildrenById(array);
console.log('found', findInArray(99));
console.log('not found', findInArray({}));
You need to iterate through your objects and then need to be parse each object using recursion. Try the answer mentioned here: JavaScript recursive search in JSON object
code:
`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;
}
}`

Filter array of objects whose any properties contains a value

I'm wondering what is the cleanest way, better way to filter an array of objects depending on a string keyword. The search has to be made in any properties of the object.
When I type lea I want to go trough all the objects and all their properties to return the objects that contain lea
When I type italy I want to go trough all the objects and all their properties to return the objects that contain italy.
I know there are lot of solutions but so far I just saw some for which you need to specify the property you want to match.
ES6 and lodash are welcome!
const arrayOfObject = [{
name: 'Paul',
country: 'Canada',
}, {
name: 'Lea',
country: 'Italy',
}, {
name: 'John',
country: 'Italy',
}, ];
filterByValue(arrayOfObject, 'lea') // => [{name: 'Lea',country: 'Italy'}]
filterByValue(arrayOfObject, 'ita') // => [{name: 'Lea',country: 'Italy'}, {name: 'John',country: 'Italy'}]
You could filter it and search just for one occurence of the search string.
Methods used:
Array#filter, just for filtering an array with conditions,
Object.keys for getting all property names of the object,
Array#some for iterating the keys and exit loop if found,
String#toLowerCase for getting comparable values,
String#includes for checking two string, if one contains the other.
function filterByValue(array, string) {
return array.filter(o =>
Object.keys(o).some(k => o[k].toLowerCase().includes(string.toLowerCase())));
}
const arrayOfObject = [{ name: 'Paul', country: 'Canada', }, { name: 'Lea', country: 'Italy', }, { name: 'John', country: 'Italy' }];
console.log(filterByValue(arrayOfObject, 'lea')); // [{name: 'Lea', country: 'Italy'}]
console.log(filterByValue(arrayOfObject, 'ita')); // [{name: 'Lea', country: 'Italy'}, {name: 'John', country: 'Italy'}]
.as-console-wrapper { max-height: 100% !important; top: 0; }
Well when we already know that its not going to be a search on an object with methods, we can do the following for saving bit on time complexity :
function filterByValue(array, value) {
return array.filter((data) => JSON.stringify(data).toLowerCase().indexOf(value.toLowerCase()) !== -1);
}
Use Object.keys to loop through the properties of the object. Use reduce and filter to make the code more efficient:
const results = arrayOfObject.filter((obj)=>{
return Object.keys(obj).reduce((acc, curr)=>{
return acc || obj[curr].toLowerCase().includes(term);
}, false);
});
Where term is your search term.
You can always use array.filter() and then loop through each object and if any of the values match the value you are looking for, return that object.
const arrayOfObject = [{
name: 'Paul',
country: 'Canada',
}, {
name: 'Lea',
country: 'Italy',
}, {
name: 'John',
country: 'Italy',
}, ];
let lea = arrayOfObject.filter(function(obj){
//loop through each object
for(key in obj){
//check if object value contains value you are looking for
if(obj[key].includes('Lea')){
//add this object to the filtered array
return obj;
}
}
});
console.log(lea);
This code checks all the nested values until it finds what it's looking for, then returns true to the "array.filter" for the object it was searching inside(unless it can't find anything - returns false). When true is returned, the object is added to the array that the "array.filter" method returns. When multiple keywords are entered(spaced out by a comma and a space), the search is narrowed further, making it easier for the user to search for something.
Stackblitz example
const data = [
{
a: 'aaaaaa',
b: {
c: 'c',
d: {
e: 'e',
f: [
'g',
{
i: 'iaaaaaa',
j: {},
k: [],
},
],
},
},
},
{
a: 'a',
b: {
c: 'cccccc',
d: {
e: 'e',
f: [
'g',
{
i: 'icccccc',
j: {},
k: [],
},
],
},
},
},
{
a: 'a',
b: {
c: 'c',
d: {
e: 'eeeeee',
f: [
'g',
{
i: 'ieeeeee',
j: {},
k: [],
},
],
},
},
},
];
function filterData(data, filterValues) {
return data.filter((value) => {
return filterValues.trim().split(', ').every((filterValue) => checkValue(value, filterValue));
});
}
function checkValue(value, filterValue) {
if (typeof value === 'string') {
return value.toLowerCase().includes(filterValue.toLowerCase());
} else if (typeof value === 'object' && value !== null && Object.keys(value).length > 0) {
if (Array.isArray(value)) {
return value.some((v) => checkValue(v, filterValue));
} else {
return Object.values(value).some((v) => checkValue(v, filterValue));
}
} else {
return false;
}
}
console.log(filterData(data, 'a, c'));
console.log(filterData(data, 'a, c, ic'));
One way would be to use Array#filter, String#toLowerCase and String#indexOf like below.
const arrayOfObject = [{
name: 'Paul',
country: 'Canada',
}, {
name: 'Lea',
country: 'Italy',
}, {
name: 'John',
country: 'Italy',
}];
function filterByValue(arrayOfObject, term) {
var ans = arrayOfObject.filter(function(v,i) {
if(v.name.toLowerCase().indexOf(term) >=0 || v.country.toLowerCase().indexOf(term) >=0) {
return true;
} else false;
});
console.log( ans);
}
filterByValue(arrayOfObject, 'ita');
function filterByValue(arrayOfObject,words){
let reg = new RegExp(words,'i');
return arrayOfObject.filter((item)=>{
let flag = false;
for(prop in item){
if(reg.test(prop)){
flag = true;
}
}
return flag;
});
}
Here's how I would do it using lodash:
const filterByValue = (coll, value) =>
_.filter(coll, _.flow(
_.values,
_.partialRight(_.some, _.method('match', new RegExp(value, 'i')))
));
filterByValue(arrayOfObject, 'lea');
filterByValue(arrayOfObject, 'ita');
Here is a version of the above which filters by a value which is derived from an array of object's property. The function takes in the array of objects and the specified array of object's property key.
// fake ads list with id and img properties
const ads = [{
adImg: 'https://test.com/test.png',
adId: '1'
}, {
adImg: 'https://test.com/test.png',
adId: '2'
}, {
adImg: 'https://test.com/test.png',
adId: '3'
}, {
adImg: 'https://test.com/test-2.png',
adId: '4'
}, {
adImg: 'https://test.com/test-2.png',
adId: '5'
}, {
adImg: 'https://test.com/test-3.png',
adId: '6'
}, {
adImg: 'https://test.com/test.png',
adId: '7'
}, {
adImg: 'https://test.com/test-6.png',
adId: '1'
}];
// function takes arr of objects and object property
// convert arr of objects to arr of filter prop values
const filterUniqueItemsByProp = (arrOfObjects, objPropFilter) => {
return arrOfObjects.filter((item, i, arr) => {
return arr.map(prop => prop[objPropFilter]).indexOf(item[objPropFilter]) === i;
});
};
const filteredUniqueItemsByProp = filterUniqueItemsByProp(ads, 'adImg');
console.log(filteredUniqueItemsByProp);

Looping multilevel Object

Assuming we have the following Object, what would be the best way to iterate it up to it's end in order to get the name property for each Object?
Please notice, that the size of the Object may vary and the browsing should be done in this order: a, b, a1, a2, b1, a21, b11, b12 ...
var obj = {
a: {
name: 'a',
a1: {
name: 'a1'
},
a2: {
name: 'a2',
a21: {
name: 'a21'
}
}
},
b: {
name: 'b'
b1: {
name: 'b1',
b11: {
name: 'b11'
},
b12: {
name: 'b12'
}
}
}
};
You could use a breadth-first search. It is an algorithm which is iterating every level of the tree first and then the next level.
This implementation works with a queue of nodes, that means, to call the function breadthFirst, the object/single node must be wrapped in an array.
function breadthFirst(queue) {
var newQueue = [];
queue.forEach(function (node) {
('name' in node) && console.log(node.name);
Object.keys(node).forEach(function (k) {
node[k] && typeof node[k] === 'object' && newQueue.push(node[k]);
});
});
newQueue.length && breadthFirst(newQueue);
}
var object = { a: { name: 'a', a1: { name: 'a1' }, a2: { name: 'a2', a21: { name: 'a21' } } }, b: { name: 'b', b1: { name: 'b1', b11: { name: 'b11' }, b12: { name: 'b12' } } } };
breadthFirst([object]); // a b a1 a2 b1 a21 b11 b12
.as-console-wrapper { max-height: 100% !important; top: 0; }
What you are looking for is a breadth-first solution which Nina has rightly mentioned. Here is my implementation of it. In this solution, you can store the result in the array and then do console.log later.
var obj = {
a: {
name: 'a',
a1: {
name: 'a1'
},
a2: {
name: 'a2',
a21: {
name: 'a21'
}
}
},
b: {
name: 'b',
b1: {
name: 'b1',
b11: {
name: 'b11'
},
b12: {
name: 'b12'
}
}
}
};
var ans = [];
var q = [];
q.push(obj);
function getAllKeys() {
if (q.length == 0) {
return;
}
var obj = q.shift();
var keys = Object.keys(obj);
ans = ans.concat(keys);
var index = ans.indexOf('name');
if (index != -1) {
ans.splice(index, 1);
}
for (var i = 0; i < keys.length; i++) {
if (typeof obj[keys[i]] == 'object') {
q.push(obj[keys[i]]);
}
}
getAllKeys();
}
getAllKeys();
console.log(ans);
You need a recursive thing here. You are free to change the console.log to push somewhere or whatever...
var obj = {
a: {
name: 'a',
a1: {
name: 'a1'
},
a2: {
name: 'a2',
a21: {
name: 'a21'
}
}
},
b: {
name: 'b',
b1: {
name: 'b1',
b11: {
name: 'b11'
},
b12: {
name: 'b12'
}
}
}
};
var looping = function(obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
if(typeof obj[keys[i]] === 'string') console.log(obj[keys[i]]);
else looping(obj[keys[i]]);
}
}
looping(obj);
Here's a simple recursive function to get all the name properties in breadth-first order. I'm using a helper, pairs, that makes it easier to process the key-value pairs provided by each object. From there, it's a simple case analysis for how the recursive function should respond:
base case – return the accumulator
key is name, append value to the accumulator
value is an object, add pairs of value to the list of pairs to process
default case - do nothing and process the next pair
This answer differs from others in that there is no side effect from running it. Instead of hard coding some behavior in the loop function itself, loop returns an array of name property values that you can then do with whatever you wish.
const pairs = o =>
Object.keys(o).map(k => ({key: k, value: o[k]}))
const loop = o => {
const aux = (acc, [x,...xs]) => {
if (x === undefined)
return acc
else if (x.key === 'name')
return aux([...acc, x.value], xs)
else if (Object(x.value) === x.value)
return aux(acc, xs.concat(pairs(x.value)))
else
return aux(acc, xs)
}
return aux([], pairs(o))
}
const obj = { a: { name: 'a', a1: { name: 'a1' }, a2: { name: 'a2', a21: { name: 'a21' } } }, b: { name: 'b', b1: { name: 'b1', b11: { name: 'b11' }, b12: { name: 'b12' } } } }
console.log(loop(obj))
// [ 'a', 'b', 'a1', 'a2', 'b1', 'a21', 'b11', 'b12' ]
Alternatively, you could implement loop using a generator such that you could act on the values while iterating. Let me know if this interests you and I'll do a write up.
Edit
Original answer processed the object in the incorrect order. The above code now answers the question properly ^_^
You are looking for breadth-first traversal:
// Breadh-first object traversal:
function traverse(...objs) {
for (let obj of objs) {
let next = Object.values(obj).filter(val => val && typeof val === 'object');
objs.push(...next);
}
return objs;
}
// Example:
let obj = {
a:{name:"a",a1:{name:"a1"},a2:{name:"a2",a21:{name:"a21"}}},
b:{name:"b",b1:{name:"b1",b11:{name:"b11"},b12:{name:"b12"}}}
};
for (let child of traverse(obj)) {
if (child.name) console.log(child.name);
}

Categories

Resources