I have two separate arrays of objects that I need to merge based if a specific key value matches. Might make more sense after analyzing the data:
Array 1
let categories = [
{ id: 5, slug: 'category-5', items: [] },
{ id: 4, slug: 'category-4', items: [] },
{ id: 3, slug: 'category-3', items: [] },
]
Array 2
let items = [
{ id: 5, data: [{ title: 'item title', description: 'item description' }] },
{ id: 5, data: [{ title: 'item title 2', description: 'item description 2' }] },
{ id: 4, data: [{ title: 'item title 4', description: 'item description 4' }] },
]
Expected Output
let mergedOutput = [
{ id: 5, slug: 'category-5',
items: [
{ title: 'item title', description: 'item description' },
{ title: 'item title 2', description: 'item description 2' }
]
},
{ id: 4, slug: 'category-4',
items: [
{ title: 'item title 4', description: 'item description 4' },
]
},
{ id: 3, slug: 'category-3', items: [] },
]
So....I need to add Array 2 to Array 1 if their id's match.
Array 1 will stay the same, but if Array 2 matches, the items property of Array 1 (empty) will be replaced by the data property of Array 2
I know this is a pretty basic / and redundant question, but I can't find the resources for my use case / object structure.
I was able to easily group arrays with lodash -- so if there is a similar solution with that library -- that would good! Or just some direction would suffice.
Thanks in advance!
You can loop first array and then use filter to get objects with same id as current element and add that items to current object.
let categories = [
{ id: 5, slug: 'category-5', items: [] },
{ id: 4, slug: 'category-4', items: [] },
{ id: 3, slug: 'category-3', items: [] },
]
let items = [
{ id: 5, data: [{ title: 'item title', description: 'item description' }] },
{ id: 5, data: [{ title: 'item title 2', description: 'item description 2' }] },
{ id: 4, data: [{ title: 'item title 4', description: 'item description 4' }] },
]
categories.forEach(function(e) {
var i = items.filter(a => a.id == e.id).map(a => a.data);
e.items = i;
})
console.log(categories)
You could reduce the items into categories:
let res = items.reduce((a, b) => {
let it = a.find(e => e.id === b.id);
if (! it) return a;
it.items = it.items.concat(b.data);
return a;
}, categories);
let categories = [{
id: 5,
slug: 'category-5',
items: []
},
{
id: 4,
slug: 'category-4',
items: []
},
{
id: 3,
slug: 'category-3',
items: []
},
];
let items = [{
id: 5,
data: [{
title: 'item title',
description: 'item description'
}]
},
{
id: 5,
data: [{
title: 'item title 2',
description: 'item description 2'
}]
},
{
id: 4,
data: [{
title: 'item title 4',
description: 'item description 4'
}]
},
];
let res = items.reduce((a, b) => {
let it = a.find(e => e.id === b.id);
if (! it) return a;
it.items = it.items.concat(b.data);
return a;
}, categories);
console.log(res);
It might be faster to get the ids in an object first, so we don't have to use find on the same id many times:
function merge(result, toMerge, mergeInto) {
let i = 0, hm = {};
for (let {id} of categories) {
hm[id] = i;
i++;
}
return toMerge.reduce((a,b) => {
let it = a[hm[b.id]];
if (!it) return a;
it[mergeInto] = it[mergeInto].concat(b.data);
return a;
}, result);
}
let categories = [
{ id: 5, slug: 'category-5', items: [] },
{ id: 4, slug: 'category-4', items: [] },
{ id: 3, slug: 'category-3', items: [] },
];
let items = [
{ id: 5, data: [{ title: 'item title', description: 'item description' }] },
{ id: 5, data: [{ title: 'item title 2', description: 'item description 2' }] },
{ id: 4, data: [{ title: 'item title 4', description: 'item description 4' }] },
];
function merge(result, toMerge, mergeInto) {
let i = 0, hm = {};
for (let {id} of categories) {
hm[id] = i;
i++;
}
return toMerge.reduce((a,b) => {
let it = result[hm[b.id]];
if (!it) return a;
it[mergeInto] = it[mergeInto].concat(b.data);
return a;
}, result);
}
console.log(merge(categories, items, 'items'));
I would make the categories as hash map and the key would be the id and iterate over all the items only.
then you get O(N) solution.
Related
const array = [{
id: 1,
name: 'test',
attrs: [{
name: 'Attribute 1',
description: 'Description 1',
}],
values: [{
name: 'value 1',
attrs: [{
name: 'Attribute 1',
type: 'Type 1',
},
{
name: 'Attribute 2',
type: 'Type 2',
},
],
}, ],
}, ];
const newArray =
array.map((item) => {
return {
...item,
isNegative: true
};
});
console.log(newArray);
I receive data as displayed in the const array. I need to push a value 'isNegative' to array[].attrs and to each values.attrs. I'm only able to do it to the array[].attrs. How can I do it to the others?
You're only mapping over the top-level array. If you need to map over the arrays within each object, that's another call to .map(). For example:
const array = [{
id: 1,
name: 'test',
attrs: [{
name: 'Attribute 1',
description: 'Description 1',
}],
values: [{
name: 'value 1',
attrs: [{
name: 'Attribute 1',
type: 'Type 1',
},
{
name: 'Attribute 2',
type: 'Type 2',
},
],
}, ],
}, ];
const newArray =
array.map((item) => {
return {
...item,
isNegative: true,
attrs: item.attrs.map((attr) => {
return {
...attr,
isNegative: true
}
})
};
});
console.log(newArray);
Same with the values property, any array within objects in values, etc. Any array that you want to map to a new structure, call .map() on it.
Initial API has this list
const main = [
{
id: 1,
name: 'Folder 1',
list: [
{
listId: 3,
listName: 'List ID 3',
subList: [
{ id: 1, subListName: 'Sub List ID 1' },
{ id: 2, subListName: 'Sub List ID 2' },
],
},
],
},
{
id: 2,
name: 'Folder 2',
list: [
{
listId: 1,
listName: 'List ID 1',
subList: [
{ id: 1, subListName: 'Sub List ID 1' },
{ id: 2, subListName: 'Sub List ID 2' },
],
},
{
listId: 2,
listName: 'List ID 2',
subList: [{ id: 2, subListName: 'Sub List ID 2' }],
},
],
},
]
But I'm creating a different structure that looks like
const folders = [
{
id: 1,
name: 'Folder 1',
list: [
{ listId: 3, subListId: 1 },
{ listId: 3, subListId: 2 },
],
},
{
id: 2,
name: 'Folder 2',
list: [
{ listId: 1, subListId: 1 },
{ listId: 2, subListId: 3 },
],
},
]
I need to match those ID and add listName and subListName to those nested object.
Final version how it should look
const final = [
{
id: 1,
name: 'Folder 1',
list: [
{ listId: 3, listName: 'List ID 3', subListId: 1, subListName: 'Sub List ID 1' },
{ listId: 3, listName: 'List ID 3', subListId: 2, subListName: 'Sub List ID 2' },
],
},
{
id: 2,
name: 'Folder 2',
list: [
{ listId: 1, listName: 'List ID 1', subListId: 1, subListName: 'Sub List ID 1' },
{ listId: 2, listName: 'List ID 2', subListId: 2, subListName: 'Sub List ID 2' },
],
},
]
I have tried doing forloops and pushing to new array but i think this is getting messy so looking for a cleaner way
const t5 = []
for (const el of folders) {
const t4 = []
for (const _el of el.list) {
let listId
let subListId
let listname
let subListName
for (const i of main) {
for (const _i of i.list) {
if (_i.listId === _el.listId) {
for (const __i of _i.subList) {
if (__i.id === _el.subListId) {
console.log('🚀 ~ __i', _i)
}
}
}
}
}
if (_el.listId === listId && _el.subListId === 1) {
t4.push({
..._el,
listname: listname,
subListName: subListName,
})
}
}
t5.push({ ...el, list: t4 })
}
console.log('🚀 ~ t5', t5)
try this
const newArray = [];
for (let item of array) {
const object = {
id: item.id,
name: item.name,
list: []
}
for (let list of item.list) {
object.list.push(...list.subList.map(subList => ({
listId: list.listId,
listName: list.listName,
subListId: subList.id,
subListName: subList.subListName
})))
}
newArray.push(object);
}
Use Array.reduce for that.
const main = [
{
id: 1,
name: 'Folder 1',
list: [
{
listId: 3,
listName: 'List ID 3',
subList: [
{ id: 1, subListName: 'Sub List ID 1' },
{ id: 2, subListName: 'Sub List ID 2' },
],
},
],
},
{
id: 2,
name: 'Folder 2',
list: [
{
listId: 1,
listName: 'List ID 1',
subList: [
{ id: 1, subListName: 'Sub List ID 1' },
{ id: 2, subListName: 'Sub List ID 2' },
],
},
{
listId: 2,
listName: 'List ID 2',
subList: [{ id: 2, subListName: 'Sub List ID 2' }],
},
],
},
]
const folders = [
{
id: 1,
name: 'Folder 1',
list: [
{ listId: 3, subListId: 1 },
{ listId: 3, subListId: 2 },
],
},
{
id: 2,
name: 'Folder 2',
list: [
{ listId: 1, subListId: 1 },
{ listId: 2, subListId: 2 },
],
},
];
const final = folders.reduce((acc, curr) => {
const node = {
id: curr.id,
name: curr.name,
};
// Find the node from the main array with id from folders node id
const mainNode = main.find((item) => item.id === node.id);
node.list = curr.list.map((listNode) => {
// From the node found from main array, get the list node
const listItem = mainNode.list.find((item) => item.listId === listNode.listId);
// From the list node find the sublist node with id
const subListItem = listItem ? listItem.subList.find((item) => item.id === listNode.subListId) : null;
return {
listId: listItem.listId,
listName: listItem.listName,
subListId: subListItem.id,
subListName: subListItem.subListName,
}
});
acc.push(node);
return acc;
}, []);
console.log(final);
My Array of objects looks like this:
const categories = [{
id: 1,
name: 'level 1'
}, {
id: 2,
name: 'level 2'
}, {
id: 3,
name: 'level 3'
}];
I want to dynamically iterate over this categories array and output the name property as a string, separated with commas except the first object in the array
const output = 'level 2, level 3';
categories could potentially contain several objects.
const categories = [{
id: 1,
name: 'level 1'
}, {
id: 2,
name: 'level 2'
}, {
id: 3,
name: 'level 3'
}, ..., ..., ...];
however, it is important that the first object is not outputted
const categories = [{id: 1, name: 'level 1'}];
const output = '';
This is quite trivial
const getNames = arr => arr.map(item => item.name).slice(1).join(", "); // or slice first, map later
const categories1 = [{
id: 1,
name: 'level 1'
}, {
id: 2,
name: 'level 2'
}, {
id: 3,
name: 'level 3'
}];
const categories2 = [{
id: 1,
name: 'level 1'
}];
console.log("1:", getNames(categories1))
console.log("2:", getNames(categories2))
You could exclude first element by combining the use of destructing assignment and spread operator
const categories = [
{ id: 1, name: 'level 1' },
{ id: 2, name: 'level 2' },
{ id: 3, name: 'level 3' }
]
const [first, ...rest] = categories
const res = rest.map(cat => cat.name).join(', ')
console.log(res)
const categories = [{
id: 1,
name: 'level 1'
}, {
id: 2,
name: 'level 2'
}, {
id: 3,
name: 'level 3'
}];
const output = categories.reduce((ans, v, i) => {
return i === 1 ? v.name : (ans + ',' + v.name)
});
console.log(output)
This can be done in several ways. However, using Array.prototype.map() combined with Array.prototype.join() and Array.prototype.slice() is the easiest way.
const categories = [
{ id: 1, name: 'level 1' },
{ id: 2, name: 'level 2' },
{ id: 3, name: 'level 3' }
];
categories.slice(1).map(category => category.name).join(', ');
P.S.
using slice at first will make a new array starting from index 1 (second object)
using map will fill the new array with the value of "name" properties of all the objects in the array.
Finally, using join will convert the array to a string that consists of all the values in the array separated with the value you provide between the parentheses.
Edit:
I have an order with has many items.
The items have one order.
The goal is to have a query that returns previous values for a column of the item as an array with the length of a supplied argument.
So if I have:
{
orders: [
{
orderDate: 1,
items: [
{
itemName: 'Item 1',
orderAmount: 2,
itemId: '1a'
},
{
itemName: 'Item 2',
orderAmount: 7,
itemId: '1b'
}
]
},
{
orderDate: '2',
items: [
{
itemName: 'Item 1',
orderAmount: 3,
itemId: '1a'
},
{
itemName: 'Item 2',
orderAmount: 6,
itemId: '1b'
}
]
},
{
orderDate: '3',
items: [
{
itemName: 'Item 1',
orderAmount: 4,
itemId: '1a'
},
{
itemName: 'Item 2',
orderAmount: 5,
itemId: '1b'
}
]
}
]
}
What I would like is a query that includes returning previousOrders(count:2) to return...
orderDate: 3,
items: [
{
itemName: 'Item 1',
orderAmount: 4,
itemId: '1a'
previousOrders: [3, 2]
},
{
itemName: 'Item 2',
orderAmount: 5,
itemId: '1a'
previousOrders: [6, 7]
}
What I currently have working is...
previousOrders: async (item, { count }, { models }) => {
const previous = await models.Item.findAll({
attributes: ['id', 'orderAmount'],
where: {
itemId: item.itemId
},
include: [
{
attributes: [],
model: models.Order
}
],
order: [[models.Order, 'orderDate', 'desc']],
raw: true
})
const index = previous.findIndex(x => x.id === item.id)
const sliced = previous.slice(index + 1, index + count + 1)
const array = sliced.map(a => a.orderAmount)
while (array.length < count) {
array.push(0)
}
return array
This works but I feel like there is probably a much nicer way to do it with queries and just want to see other potentials.
I have this JavaScript object:
const object = {
categories: [
{
title: 'Category 1',
items: [
{ title: 'Item 1', image: 'path/to/file-1.png' },
{ title: 'Item 2', image: 'path/to/file-2.png' },
{ title: 'Item 3', image: 'path/to/file-3.png' }
]
},
{
title: 'Category 2',
items: [
{ title: 'Item 4', image: 'path/to/file-4.png' },
{ title: 'Item 5', image: 'path/to/file-5.png' },
{ title: 'Item 6', image: 'path/to/file-6.png' }
]
}
]
}
I was able to select only one item using Lodash's _.get method.
_.get(object, 'categories[0].items[0].image')
// => "path/to/file-1.png"
But I need an array with all occurrences, not just from position 0. Something like this:
_.get(object, 'categories[].items[].image')
// => ["path/to/file-1.png", "path/to/file-2.png", "path/to/file-3.png", "path/to/file-4.png", "path/to/file-5.png", "path/to/file-6.png"]
Is it possible to do it using Lodash?
If it's not possible, do you have any idea how to implement it?
EDIT: I'm looking for something like _.get from Lodash, where I can supply the "search schema" (path) as string. Obviously I know how to solve this with map, reduce, etc.
This is pretty easy to do with flatMap, no library required:
const object = {
categories: [
{
title: 'Category 1',
items: [
{ title: 'Item 1', image: 'path/to/file-1.png' },
{ title: 'Item 2', image: 'path/to/file-2.png' },
{ title: 'Item 3', image: 'path/to/file-3.png' }
]
},
{
title: 'Category 2',
items: [
{ title: 'Item 4', image: 'path/to/file-4.png' },
{ title: 'Item 5', image: 'path/to/file-5.png' },
{ title: 'Item 6', image: 'path/to/file-6.png' }
]
}
]
};
const images = object.categories.flatMap(({ items }) => items.map(({ image }) => image));
console.log(images);
Or, with reduce:
const object = {
categories: [
{
title: 'Category 1',
items: [
{ title: 'Item 1', image: 'path/to/file-1.png' },
{ title: 'Item 2', image: 'path/to/file-2.png' },
{ title: 'Item 3', image: 'path/to/file-3.png' }
]
},
{
title: 'Category 2',
items: [
{ title: 'Item 4', image: 'path/to/file-4.png' },
{ title: 'Item 5', image: 'path/to/file-5.png' },
{ title: 'Item 6', image: 'path/to/file-6.png' }
]
}
]
};
const images = object.categories.reduce((a, { items }) => {
items.forEach(({ image }) => {
a.push(image);
});
return a;
}, []);
console.log(images);
This is my implementation of a flatGet function that can handle arrays:
const flatGet = (object, path) => {
const p = path.match(/[^.\[\]]+/g)
const getItem = (item, [current, ...path]) => {
if(current === undefined) return item
if(typeof item !== 'object') return undefined
if(Array.isArray(item)) return item.flatMap(o => getItem(o[current], path))
return getItem(item[current], path)
}
return getItem(object, p)
}
const object = {"categories":[{"title":"Category 1","items":[{"title":"Item 1","image":"path/to/file-1.png"},{"title":"Item 2","image":"path/to/file-2.png"},{"title":"Item 3","image":"path/to/file-3.png"}]},{"title":"Category 2","items":[{"title":"Item 4","image":"path/to/file-4.png"},{"title":"Item 5","image":"path/to/file-5.png"},{"title":"Item 6","image":"path/to/file-6.png"}]}]}
var result = flatGet(object, 'categories.items.image')
console.log(result)
We use object-scan for these types of queries now. It's pretty powerful (once you wrap your head around it) and easy enough to use for basic stuff. Here is how you could solve your problem
// const objectScan = require('object-scan');
const object = { categories: [ { title: 'Category 1', items: [ { title: 'Item 1', image: 'path/to/file-1.png' }, { title: 'Item 2', image: 'path/to/file-2.png' }, { title: 'Item 3', image: 'path/to/file-3.png' } ] }, { title: 'Category 2', items: [ { title: 'Item 4', image: 'path/to/file-4.png' }, { title: 'Item 5', image: 'path/to/file-5.png' }, { title: 'Item 6', image: 'path/to/file-6.png' } ] } ] };
const r = objectScan(['categories[*].items[*].image'], { rtn: 'value' })(object);
console.log(r);
/* =>
[ 'path/to/file-6.png', 'path/to/file-5.png', 'path/to/file-4.png',
'path/to/file-3.png', 'path/to/file-2.png', 'path/to/file-1.png' ]
*/
.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
_([object]).flatMap("categories").flatMap("items").flatMap("image").value()
Full example:
const _ = lodash; // const _ = require("lodash");
const object = {
categories: [
{
title: 'Category 1',
items: [
{ title: 'Item 1', image: 'path/to/file-1.png' },
{ title: 'Item 2', image: 'path/to/file-2.png' },
{ title: 'Item 3', image: 'path/to/file-3.png' }
]
},
{
title: 'Category 2',
items: [
{ title: 'Item 4', image: 'path/to/file-4.png' },
{ title: 'Item 5', image: 'path/to/file-5.png' },
{ title: 'Item 6', image: 'path/to/file-6.png' }
]
}
]
};
const result = _([object]).flatMap("categories").flatMap("items").flatMap("image").value();
console.log(result);
<script src="https://bundle.run/lodash#4.17.21"></script>
Here is my implementation which can handle [] path:
const object = { categories: [ { title: "Category 1", items: [ { title: "Item 1", image: "path/to/file-1.png" }, { title: "Item 2", image: "path/to/file-2.png" }, { title: "Item 3", image: "path/to/file-3.png" } ] }, { title: "Category 2", items: [ { title: "Item 4", image: "path/to/file-4.png" }, { title: "Item 5", image: "path/to/file-5.png" }, { title: "Item 6", image: "path/to/file-6.png" } ] } ] };
const object2 = { categories: { title: "Category 1" } };
function flatGet(object, path) {
const pathArray = path.split(".");
while (pathArray.length > 0) {
let pos = pathArray.shift();
if (pos.includes("[]")) {
pos = pos.slice(0, -2);
}
if (Array.isArray(object)) {
object = object.reduce((acc, cur) => acc.concat(cur[pos]), []);
} else {
object = object[pos];
}
}
return object;
}
var res = flatGet(object, "categories[].items[].image"); // ["path/to/file-1.png", ...]
var res2 = flatGet(object2, "categories.title"); // "Category 1"
edited before:
Cause you need to get the images Array, map could help with it. Here is a simple one:
_.map(object.categories[0].items, o => _.get(o, 'image'))
// if categories[0].items is not always there, just use another get:
_.map(_.get(object, 'categories[0].items'), o => _.get(o, 'image'))