I would like to know how to change nested array object to object depending on key in javascript
I have objects obj1 and obj2, depending on key item type change the object.
function changeObj(obj){
let result = obj.reduce(function (acc, item) {
if(item.items.trim() !== "" && item.key.trim() !== ""){
acc[item.key] = item.items
return acc
}
return acc
}, {});
return result;
}
let result = this.changeObj(obj2)
var obj1 = [
{ id:0, items:["SG","AU"], count: 2, key:"countries"},
{ id:1, items:["finance"], count: 3 key:"info"}
]
var obj2 = [
{ id:0, items: "SG", key: "country"},
{ id:1, items: "details", key: "info"}
]
Expected Output:
// if items key is array
{
fields: {
countries: ["SG","AU",2],
info: ["finance",3]
}
}
//if items key is string
{
fields: {
country: "SG",
info: "details"
}
}
I think the reason your code is not running is because the wrong format of your objects (1 and 2). Your code is okay except the condition because trim() only works on string type so it errors on array. Try this code snippet
function changeObj(obj){
let result = obj.reduce(function (acc, item) {
acc[item.key] = item.items;
return acc;
}, {});
return result;
}
var obj1 = [
{ id:0, items:["SG","AU"], count: 2, key:"countries"},
{ id:1, items:["finance"], count: 3, key:"info"}
]
var obj2 = [
{ id:0, items: "SG", key: "country"},
{ id:1, items: "details", key: "info"}
]
console.log(changeObj(obj1));
const changeObj = obj =>
obj.reduce((acc, item) => {
if (Array.isArray(item.items)) {
acc[item.key] = [...item.items, item.count];
} else {
acc[item.key] = item.items;
}
return acc;
}, {});
var obj1 = [
{ id: 0, items: ['SG', 'AU'], count: 2, key: 'countries' },
{ id: 1, items: ['finance'], count: 3, key: 'info' }
];
var obj2 = [
{ id: 0, items: 'SG', key: 'country' },
{ id: 1, items: 'details', key: 'info' }
];
console.log(changeObj(obj1));
console.log(changeObj(obj2));
or cleaned up even more
const changeObj = obj =>
obj.reduce((acc, { items, key, count }) => {
Array.isArray(items) ? (acc[key] = [...items, count]) : (acc[key] = items);
return acc;
}, {});
var obj1 = [
{ id: 0, items: ['SG', 'AU'], count: 2, key: 'countries' },
{ id: 1, items: ['finance'], count: 3, key: 'info' }
];
var obj2 = [
{ id: 0, items: 'SG', key: 'country' },
{ id: 1, items: 'details', key: 'info' }
];
console.log(changeObj(obj1));
console.log(changeObj(obj2));
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;
}
}`
I would like to flatten any array values within an object e.g. like sample below. The solution should not just apply to ecommerce but literally anything that could be in the object as an array type. Example:
var sample = {
price: "999",
description: "...",
ecommerce: {
products: [
{
brand: "apple",
category: "phone"
},
{
brand: "google",
category: "services"
}
]
}
};
And I would like the output to be:
{
price: "999",
description: "..."
ecommerce: {
products_1: {
brand: "apple",
category: "phone"
},
products_2: {
brand: "google",
category: "services"
}
}
}
What is the most efficient way to do this in JavaScript (ES6/7)?
Thanks in advance!
Updated due to comment and several minuses!!! Would be nice if those who were so quick to click minuses when the question was initially asked would revoke it!
I've tried this its completely wrong and I'm also sure theres a better functional way of doing this:
function flattenArray(array) {
var obj = array.reduce((acc, cur, i) => {
acc[i] = cur;
return acc;
}, {});
return obj;
}
function cleanObject(object) {
for (let key in object) {
let testObject = object[key];
if (Array.isArray(testObject)) {
testObject = flattenArray(testObject)
} else if (typeof(testObject) === 'object') {
testObject = cleanObject(testObject);
}
return testObject;
}
return object;
}
var clean = cleanObject(sample);
UPDATE 2: seeing as both solutions were so fixated on ecommerce how would the solution work if the next object was:
var sample = {
price: "999",
description: "...",
differentArray: [
{
brand: "apple",
category: "phone"
},
{
brand: "google",
category: "services"
}
]
};
Notice that not only is this different key its also at a different nesting level too.
A recursively applied Array#reduce based approach does the job for this kind of key-value bags, ... a first generic solution then might look like this one ...
function recursivelyMapArrayItemsToGenericKeys(collector, key) {
var
source = collector.source,
target = collector.target,
type = source[key];
if (Array.isArray(type)) {
type.forEach(function (item, idx) {
var
keyList = Object.keys(item || ''),
genericKey = [key, idx].join('_');
if (keyList.length >= 1) {
target[genericKey] = keyList.reduce(recursivelyMapArrayItemsToGenericKeys, {
source: item,
target: {}
}).target;
} else {
target[genericKey] = item;
}
});
} else if (typeof type !== 'string') {
var keyList = Object.keys(type || '');
if (keyList.length >= 1) {
target[key] = keyList.reduce(recursivelyMapArrayItemsToGenericKeys, {
source: type,
target: {}
}).target;
} else {
target[key] = type;
}
} else {
target[key] = type;
}
return collector;
}
var sample = {
price: "999",
description: "...",
ecommerce: {
products: [{
brand: "apple",
category: "phone"
}, {
brand: "google",
category: "services"
}, {
foo: [{
brand: "bar",
category: "biz"
}, {
brand: "baz",
category: "biz"
}]
}]
}
};
var result = Object.keys(sample).reduce(recursivelyMapArrayItemsToGenericKeys, {
source: sample,
target: {}
}).target;
console.log('sample : ', sample);
console.log('result : ', result);
.as-console-wrapper { max-height: 100%!important; top: 0; }
... within a next code sanitizing step one might get rid of duplicated logic, thus ending up with two functions and an alternating/reciprocal recursion ...
function recursivelyAssignItemsFromTypeByKeys(target, type, keyList, key) {
if (keyList.length >= 1) {
target[key] = keyList.reduce(recursivelyMapArrayItemsToGenericKeys, {
source: type,
target: {}
}).target;
} else {
target[key] = type;
}
}
function recursivelyMapArrayItemsToGenericKeys(collector, key) {
var
source = collector.source,
target = collector.target,
type = source[key];
if (Array.isArray(type)) {
type.forEach(function (item, idx) {
var
keyList = Object.keys(item || ''),
genericKey = [key, idx].join('_');
recursivelyAssignItemsFromTypeByKeys(target, item, keyList, genericKey);
});
} else if (typeof type !== 'string') {
var keyList = Object.keys(type || '');
recursivelyAssignItemsFromTypeByKeys(target, type, keyList, key);
} else {
target[key] = type;
}
return collector;
}
var sample = {
price: "999",
description: "...",
ecommerce: {
products: [{
brand: "apple",
category: "phone"
}, {
brand: "google",
category: "services"
}, {
foo: [{
brand: "bar",
category: "biz"
}, {
brand: "baz",
category: "biz"
}]
}]
}
};
var result = Object.keys(sample).reduce(recursivelyMapArrayItemsToGenericKeys, {
source: sample,
target: {}
}).target;
console.log('sample : ', sample);
console.log('result : ', result);
.as-console-wrapper { max-height: 100%!important; top: 0; }
What I'd do :
const sample = {
price: "999",
description: "...",
ecommerce: {
products: [
{
brand: "apple",
category: "phone"
},
{
brand: "google",
category: "services"
}
]
}
}
sample.ecommerce.products.forEach((p, i) => {
sample.ecommerce['products_' + i] = p
})
delete sample.ecommerce.products
console.log(sample)
Just reduce ecommerce array and destruct each element as you wish.
const sample = {
price: "999",
description: "...",
ecommerce: {
products: [
{
brand: "apple",
category: "phone"
},
{
brand: "google",
category: "services"
}
]
}
}
const flattenArray = (arr, key) =>
arr.reduce((prev, curr, index) => {
return {
...prev,
[`${key}_${index + 1}`]: curr,
}
}, {})
const result = {
...sample,
ecommerce: flattenArray(sample.ecommerce.products, 'products'),
}
console.log(result)