Return multiple elements using Lodash _.get? - javascript

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'))

Related

Push value to multiple arrays

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.

How to use reduce to pass information to a base component? / Getting correct data passed with computed props in VueJS

I'm currently working on an inventorying app and I am trying to display the boxID and the amount of items inside of that box on a baseCard component that I have made.
The computed property that I made boxCards needs to spit the data out in this format
[{title: '', amount: null}] so it can be pushed onto each baseCard element.
Presently my computed property is giving me the title, but I cannot figure out how to get the amount of items inside of each box.
boxesInLocation returns this array: ["", "Box 1", "Box 4", "Box 4"]
Which is great, but now I need to count how many times each box shows up in that area and then push it to the reshapedItems function in the amount: spot.
Is this just a simple reduce method that I need to use? Because I've only be able to actually produce a number when calculating the length of the array.
Also, just reducing the array won't spit out the number to each individual instance of reshapedItem
Any ideas on what I can do here?
Cheers!
App.Vue Data:
data(){
userData: {
items: [
{
itemName: 'test book',
category: 'Books',
box: 'Box 3',
location: 'Kitchen',
},
{
itemName: 'test book 2',
category: 'Books',
box: 'Box 3',
location: 'Kitchen',
},
{
itemName: 'test book 3',
category: 'Books',
box: '',
location: 'Basement',
},
{
itemName: 'test record',
category: 'Records',
box: 'Box 1',
location: 'Basement',
},
{
itemName: 'test record 2',
category: 'Records',
box: 'Box 4',
location: 'Basement',
},
{
itemName: 'test furniture',
category: 'Furniture',
box: 'Box 2',
location: 'Garage',
},
{
itemName: 'test movie 1',
category: 'Movies',
box: 'Box 2',
location: 'Garage',
},
{
itemName: 'test movie 2',
category: 'Movies',
box: 'Box 2',
location: 'Garage',
},
{
itemName: 'test movie 3',
category: 'Movies',
box: 'Box 2',
location: 'Garage',
},
{
itemName: 'test Comicbook',
category: 'Movies',
box: 'Box 4',
location: 'Basement',
},
],
boxes: [
{ name: 'Box 1', location: 'Basement' },
{ name: 'Box 2', location: 'Garage' },
{ name: 'Box 3', location: 'Kitchen' },
{ name: 'Box 4', location: 'Basement' },
],
}
Page Component
data() {
return {
items: this.userData.items,
boxes: this.userData.boxes,
}
},
computed: {
boxCards() {
const filteredItems = this.items.filter((item) => item.location === 'Basement')
const itemsInBoxes = filteredItems.map((filteredItem) => {
return filteredItem.box
})
const filteredBoxes = this.boxes.filter((box) => box.location === 'Basement')
const reshapedBoxes = filteredBoxes.map((filteredBox) => {
return { boxID: `${filteredBox.name}`, amount: 100 }
})
return reshapedBoxes
},
You can count the items in the this.items array that have the same box name as the box in question:
return this.boxes
.filter((box) => box.location === 'Basement')
.map((box) => ({
boxId: `${box.name}`,
amount: this.items.filter(item => item.box === box.name).length
}));
You need to save the number of occurrences of each box in a Map, then, get the amount of each one in the second loop you're doing:
const userData = {
items: [
{ itemName: 'test book', category: 'Books', box: 'Box 3', location: 'Kitchen' },
{ itemName: 'test book 2', category: 'Books', box: 'Box 3', location: 'Kitchen' },
{ itemName: 'test book 3', category: 'Books', box: '', location: 'Basement' },
{ itemName: 'test record', category: 'Records', box: 'Box 1', location: 'Basement' },
{ itemName: 'test record 2', category: 'Records', box: 'Box 4', location: 'Basement' },
{ itemName: 'test furniture', category: 'Furniture', box: 'Box 2', location: 'Garage' },
{ itemName: 'test movie 1', category: 'Movies', box: 'Box 2', location: 'Garage' },
{ itemName: 'test movie 2', category: 'Movies', box: 'Box 2', location: 'Garage' },
{ itemName: 'test movie 3', category: 'Movies', box: 'Box 2', location: 'Garage' },
{ itemName: 'test Comicbook', category: 'Movies', box: 'Box 4', location: 'Basement' },
],
boxes: [
{ name: 'Box 1', location: 'Basement' },
{ name: 'Box 2', location: 'Garage' },
{ name: 'Box 3', location: 'Kitchen' },
{ name: 'Box 4', location: 'Basement' },
]
};
function boxCards() {
const boxesCount = userData.items
.reduce((quantity, {box}) =>
quantity.set(box, 1 + (quantity.get(box) || 0))
, new Map);
return userData.boxes
.filter(box =>
box.location === 'Basement'
)
.map(filteredBox =>
({ boxID: filteredBox.name, amount: boxesCount.get(filteredBox.name) })
);
}
console.log( boxCards() );

How can I move an object from one array to another array with function?

Hello everyone and dear friends. While learning React, I noticed that I lacked javascript and I started to learn javascript carefully.
Let me try to tell you what I am trying to do. I have two arrays. I want to move one of the elements of the instance element in array1 to the instance in array2.
How can I do it? Which method would you recommend? Does it work to splice the element and push it down? How can a function be written for this? I would be glad if you help.
const array1 = [
{
id: '1',
instance: [
{ id: '34', title: 'Example 1' },
{ id: '35', title: 'Example 2' },
{ id: '36', title: 'Example 3' }, // delete this object from here
},
{
id: '2',
instance: [
{ id: '37', title: 'Example 4' }
],
},
];
I want to move the element I deleted into this array.
const array2 = [
{
id: '1',
instance: [
{ id: '34', title: 'Example 1' },
{ id: '35', title: 'Example 2' },
},
{
id: '2',
instance: [
{ id: '37', title: 'Example 4' },
// { id: '36', title: 'Example 3' }, // i want to move here
],
},
];
You can use pop() to remove last element from array.
const array1 = [{
id: '1',
instance: [{
id: '34',
title: 'Example 1'
},
{
id: '35',
title: 'Example 2'
},
{
id: '36',
title: 'Example 3'
}
],
}, {
id: '2',
instance: [{
id: '37',
title: 'Example 4'
}],
}, ];
let tmp = array1[0].instance[2];
array1[0].instance.pop();
array1[1].instance.push(tmp);
console.log(array1);
const array1 = [
{
id: '1',
instance: [
{ id: '34', title: 'Example 1' },
{ id: '35', title: 'Example 2' },
{ id: '36', title: 'Example 3' }, // delete this object from here
]
},
{
id: '2',
instance: [
{ id: '37', title: 'Example 4' }
]
}
];
const array2 = [
{
id: '1',
instance: [
{ id: '34', title: 'Example 1' },
{ id: '35', title: 'Example 2' },
]
},
{
id: '2',
instance: [
{ id: '37', title: 'Example 4' },
// { id: '36', title: 'Example 3' }, // i want to move here
]
}
];
//answer
function transfer(fromArr,index,toArr){
toArr.push(fromArr.splice(index,1))
}
transfer(array1[0].instance,2,array2[1].instance)
console.log("first array",array1)
console.log("second array",array2)
const array1 = [
{
id: '1',
instance: [
{ id: '34', title: 'Example 1' },
{ id: '35', title: 'Example 2' },
{ id: '36', title: 'Example 3' },] // delete this object from here
},
{
id: '2',
instance: [
{ id: '37', title: 'Example 4' }
],
},
];
var temp = array1[0]
array1.splice(0,1);
const array2 = [
{
id: '1',
instance: [
{ id: '34', title: 'Example 1' },
{ id: '35', title: 'Example 2' },]
},
{
id: '2',
instance: [
{ id: '37', title: 'Example 4' },
// { id: '36', title: 'Example 3' }, // i want to move here
],
},
];
array2.push(temp)
console.log(array2)
First thing, your object has wrong brackets. Here is a fixed version for array1
const array1 = [
{
id: '1',
instance: [
{ id: '34', title: 'Example 1' },
{ id: '35', title: 'Example 2' },
{ id: '36', title: 'Example 3' }, // delete this object from here
]
},
{
id: '2',
instance: [
{ id: '37', title: 'Example 4' }
],
},
];
Now remove one element, IDK how you're getting the key for this, so I ill just declare it as constant, up to you to modify
let key = 0;
let Nested_key = 2;
let removed = array1[key].instance.splice(Nested_key,1) //1st param= 0: the position, 2nd param = 1 number of elements to rm starting from param1 position
Now we removed the element, and it is stored in removed (an array of length=1), just insert it now
array2[key].instance.push(removed[0]);
Judging by your post, it Seems like you knew already what splice and push are and what they do (the name are quite clear and intuitive). Feel free to ask for clarifications if there is any part that you don't understand in my answer

Get single property array from an array of objects

I have this array of objects and I want to get all the controls from this to another array:
this.formModel = {
sections: [
{
title: 'Section 01',
controls: [
new FormControlInput({
key: 'name 01',
label: 'Name 01'
}),
new FormControlSelect({
key: 'abc',
label: 'Abc'
})
]
},
{
title: 'Section 02',
controls: [
new FormControlInput({
key: 'name 02',
label: 'Name 02'
})
]
}
]
};
I am using map for this but I am not getting single array, I am getting array of arrays:
this.formModel.sections.map(function (x) { return x.controls; })
Getting this:
[
{
[{
key: 'name 01',
label: 'Name 01'
},
{
key: 'abc',
label: 'Abc'
}]
},
{
[{
key: 'name 02',
label: 'Name 02'
}]
}
]
What I want is this:
[
{
key: 'name 01',
label: 'Name 01'
},
{
key: 'abc',
label: 'Abc'
},
{
key: 'name 02',
label: 'Name 02'
}
]
You just need to flatten your array after mapping:
var obj = {
sections: [{
title: 'Section 01',
controls: [
{ key: 'name 01', label: 'Name 01' },
{ key: 'abc', label: 'Abc' }
]
}, {
title: 'Section 02',
controls: [
{ key: 'name 02', label: 'Name 02' }
]
}
]
};
var mapped = obj.sections.map(function (x) { return x.controls; });
var flattened = [].concat.apply([], mapped);
console.log(flattened);
To simplify your example:
// This is your structure:
var sections= [{
controls: [{}, {}] // C1
}, {
controls: [{}] // C2
}
];
// With the map, grabbing each `controls` property, and using that as an entry in your array:
var mapped = sections.map(function (x) { return x.controls; });
console.log(mapped);
// [[{},{}],[{}]]
// ^ C1 ^ C2
// We need to remove that extra layer of arrays:
var flattened = [].concat.apply([], mapped);
console.log(flattened);
You can use reduce to flatten the hierarchy
formModel.sections
.map(x => x.controls)
.reduce((prev, current) => prev.concat(current), [])
Use reduce instead of map:
let formModel = {
sections: [
{
title: 'Section 01',
controls: [
{
key: 'name 01',
label: 'Name 01'
},
{
key: 'abc',
label: 'Abc'
}
]
},
{
title: 'Section 02',
controls: [
{
key: 'name 02',
label: 'Name 02'
}
]
}
]
};
let result = formModel.sections.reduce((res, section) => {
return res = res.concat(section.controls);
}, []);
console.log(result);

Merging Arrays of Objects by Key/Values

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.

Categories

Resources