Related
I currently am utilising Array.map to create a new object containing some data:
const bookList = [{
name: "Foo",
id: "1234",
quantity: 5,
}];
function mapBooks(bookList) {
return {
eventName: "ping",
data: {
list: {
books:
bookList.map(
({name, id, quantity }) => ({ name, id, quantity})
)
}
}
};
}
mapBooks(bookList);
// Result:
{
eventName: "ping",
data: {
list: {
books: {
name: "Foo",
id: "1234",
quantity: 5,
}
}
},
}
This is fine in this example, but what happens when one of the items is not in the provided data?
const bookList = [{
name: "Foo",
id: "1234",
}];
mapBooks(bookList);
// Result:
{
eventName: "ping",
data: {
list: {
books: {
name: "Foo",
id: "1234",
quantity: undefined,
}
}
},
}
How can I adjust my map function to simply not return any undefined values? For example I would prefer a result like this:
mapBooks(bookList);
// Result:
{
eventName: "ping",
data: {
list: {
books: {
name: "Foo",
id: "1234",
// quantity is simply not included
}
}
},
}
I don't know if i understand the question correctly but you could something like this:
const bookList = {
name: "Foo",
id: "1234",
quantity: 5,
};
function mapBooks(bookList) {
return {
eventName: "ping",
data: {
list: {
books:
bookList.map(
(book) => ({ ...book})
)
}
}
};
}
With the power of destructuring you will only fulled the present option of the object
I have an array of objects which presents tasks. These tasks are categorized (primary / secondary category).
let tasks = [
{
id: 1,
name: 'Cleanup desk',
primary_category: {
id: 1,
name: 'Indoor'
},
secondary_category: {
id: 2,
name: 'Surfaces'
}
},
{
id: 2,
name: 'Cleanup office floors',
primary_category: {
id: 1,
name: 'Indoor'
},
secondary_category: {
id: 3,
name: 'Ground'
}
},
{
id: 3,
name: 'Water plants',
primary_category: {
id: 2,
name: 'Outdoor'
},
secondary_category: {
id: 3,
name: 'Irrigation'
}
}
];
I now try to create a categories accordion in my frontend and therefore need to group my array differently. The structure should look like:
1) primary category
> secondary category
> tasks
> secondary category
> tasks
2) primary category
> secondary category
> tasks
Therefore I'm trying to achieve a structure similar to this:
let tasks_categorized = [
{
id: 1,
name: 'Indoor',
secondary_categories: [
{
id: 2,
name: 'Surfaces',
tasks: [
{
id: 1,
name: 'Cleanup desk'
}
]
},
{
id: 3,
name: 'Ground',
tasks: [
{
id: 2,
name: 'Cleanup office floors'
}
]
}
]
},
{
id: 2,
name: 'Outdoor',
secondary_categories: [
{
id: 3,
name: 'Irrigation',
tasks: [
{
id: 3,
name: 'Water plants'
}
]
}
]
}
];
I tried using groupBy by lodash but this does not allow grouping by multiple nested key-value pairs. Does anybody know an approach to solve this?
Thank you in advance!
The following provided approach is going to achieve the expected result within a single reduce cycle without any further nested loops.
It does so by implementing a reducer function which creates and/or aggregates at time a prioritized category task while iterating another task array. But most importantly it keeps track of a task item's related primary and secondary categories via a Map based lookup. This lookup reference together with a result array are properties of this function's return value which has to be partly provided as the reduce method's initial value as follows ... { result: [] }.
function createAndAggregatePrioritizedCategoryTask(
{ lookup = new Map, result }, item
) {
const { primary_category, secondary_category, ...taskRest } = item;
const { id: primaryId, name: primaryName } = primary_category;
const { id: secondaryId, name: secondaryName } = secondary_category;
const primaryKey = [primaryId, primaryName].join('###');
const secondaryKey = [primaryKey, secondaryId, secondaryName].join('###');
let primaryCategory = lookup.get(primaryKey);
if (!primaryCategory) {
// create new primary category item.
primaryCategory = {
id: primaryId,
name: primaryName,
secondary_categories: [],
};
// store newly created primary category reference in `lookup`.
lookup.set(primaryKey, primaryCategory);
// push newly created primary category reference to `result`.
result.push(primaryCategory);
}
let secondaryCategory = lookup.get(secondaryKey);
if (!secondaryCategory) {
// create new secondary category item.
secondaryCategory = {
id: secondaryId,
name: secondaryName,
tasks: [],
};
// store newly created secondary category reference in `lookup`.
lookup.set(secondaryKey, secondaryCategory);
// push newly created secondary category reference into the
// `secondary_categories` array of its related primary category.
primaryCategory
.secondary_categories
.push(secondaryCategory);
}
// push the currently processed task-item's rest-data as
// item into the related secondary category's `task` array.
secondaryCategory
.tasks
.push(taskRest);
return { lookup, result };
}
let tasks = [{
id: 1,
name: 'Cleanup desk',
primary_category: { id: 1, name: 'Indoor' },
secondary_category: { id: 2, name: 'Surfaces' },
}, {
id: 2,
name: 'Cleanup office floors',
primary_category: { id: 1, name: 'Indoor' },
secondary_category: { id: 3, name: 'Ground' },
}, {
id: 3,
name: 'Water plants',
primary_category: { id: 2, name: 'Outdoor' },
secondary_category: { id: 3, name: 'Irrigation' },
}];
const { result: tasks_categorized } = tasks
.reduce(createAndAggregatePrioritizedCategoryTask, { result: [] });
console.log({ tasks_categorized });
.as-console-wrapper { min-height: 100%!important; top: 0; }
You could take a dynamic approach with an array of arrays with functions and keys for the nested arrays.
const
tasks = [{ id: 1, name: 'Cleanup desk', primary_category: { id: 1, name: 'Indoor' }, secondary_category: { id: 2, name: 'Surfaces' } }, { id: 2, name: 'Cleanup office floors', primary_category: { id: 1, name: 'Indoor' }, secondary_category: { id: 3, name: 'Ground' } }, { id: 3, name: 'Water plants', primary_category: { id: 2, name: 'Outdoor' }, secondary_category: { id: 3, name: 'Irrigation' } }],
groups = [
[o => o, 'primary category'],
[o => o.primary_category, 'secondary category'],
[o => o.secondary_category, 'tasks']
],
result = tasks.reduce((r, o) => {
groups.reduce((parent, [fn, children]) => {
const { id, name } = fn(o);
let item = (parent[children] ??= []).find(q => q.id === id)
if (!item) parent[children].push(item = { id, name });
return item;
}, r);
return r;
}, {})[groups[0][1]];
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Given an array like this:
[
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com" },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"]
]
How could I use Javascript to reduce this to an array like this:
[
{ id: 1, emailAddress: "bill#test.com" },
{ id: 1, emailAddress: "bob#test.com" },
{ id: 2, emailAddress: "sarah#test.com" },
{ id: 3, emailAddress: "jane#test.com" },
{ id: 3, emailAddress: "laura#test.com" },
{ id: 3, emailAddress: "paul#test.com" }
]
I've read about the functions reduce, flat, map and so on and read lots of the questions on SO about using them but I can't find anything that's asking quite the same as this and I can't get my head around using those functions to do it.
You could use flatMap
const input = [
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com"] },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"] }
]
const output = input.flatMap(o =>
o.emailAddresses.map(e => ({ id: o.id, emailAddress: e }) )
)
console.log(output)
If flatMap is not supported, you could use a nested for...of loop:
const input = [{id:1,emailAddresses:["bill#test.com","bob#test.com"]},{id:2,emailAddresses:["sarah#test.com"]},{id:3,emailAddresses:["jane#test.com","laura#test.com","paul#test.com"]}];
const output = []
for (const { id, emailAddresses } of input)
for (const emailAddress of emailAddresses)
output.push({ id, emailAddress })
console.log(output)
You can map over your data and then use reduce to flatten the resulting array:
const result = data
.map(datum => {
return datum.emailAddresses.map(emailAddress => {
return { id: datum.id, emailAddress };
});
})
.reduce((result, current) => {
return [...result, ...current];
}, []);
We can use Array.prototype.reduce to go over each object in the array and take into consideration the multiple values in the emailAddress property array and create separate object for each one and finally accumulate the new objects in the new array (r):
const data = [
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com"] },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"]}
]
const flat = data.reduce((r, e) => {
e.emailAddresses.forEach((obj) => r.push({id: e.id, emailAddresses : obj }));
return r;
}, []);
console.log(flat);
You can use reduce and map
const data = [
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com"] },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"]}
]
const flat = (toFlatten) =>
toFlatten.reduce((r,c)=> {
r.push(...c.emailAddresses.map(email=>({id: c.id, emailAddress: email})))
return r
}, [])
console.log(flat(data))
Here is a solution that doesn't use any array prototype but does, instead, take advantage of function generators.
The script below iterates the array, acquire all keys of the element except emailAddresses, which is handled separately, and for each email address it yields an object filled with the single email address and the rest of the data.
This solution iterate the original array only once.
Because it uses function generators, this solution is widely supported, it just won't work on IE due it's lack of support for function generators, despite babel or TSC can easily add compatibility to that.
const input = [
{ id: 1, emailAddresses: ["bill#test.com", "bob#test.com"] },
{ id: 2, emailAddresses: ["sarah#test.com"] },
{ id: 3, emailAddresses: ["jane#test.com", "laura#test.com", "paul#test.com"] }
];
function* flattenEmailAddresses(arr) {
for (var {emailAddresses, ...keys} of arr) {
for (var emailAddress of emailAddresses) yield {...keys, emailAddress};
}
}
console.log([...flattenEmailAddresses(input)]);
How to convert this JS object:
users = {
1: { id: 1, name: "..." },
2: { id: 2, name: "..." },
// 3 is missing
4: { id: 4, name: "..." },
...
}
into an array:
usersArray = [
1: { id: 1, name: "..." },
2: { id: 2, name: "..." },
// 3 is missing -- I do not want undefined here
4: { id: 4, name: "..." },
...
]
The key is that I do not want undefined, blank spaces, the key must be the object Id. The reason I do not want blanks/undefined entries is because I use vue.js and I am pretty sure it would not iterate over an array with undefined values in v-for.
Something like: https://forum.vuejs.org/t/vuex-best-practices-for-complex-objects/10143/2 but without the additional array storing just ids.
Is this possible?
You can use Object.values()
const users = {
1: { id: 1, name: "..." },
2: { id: 2, name: "..." },
4: { id: 4, name: "..." },
};
console.log(Object.values(users))
You could create a sparse array with missing indices by assigning the object to an array. As you see, forEach or map does not visit sparse items.
var users = { 1: { id: 1, name: "..." }, 2: { id: 2, name: "..." }, 4: { id: 4, name: "..." } },
array = Object.assign([], users);
array.forEach((o, i) => console.log(i, o));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have an object in my state as follows:
Exercise: {
id: 1,
question: '',
type: '',
Groups: [
{
id: 1,
category: {
id: 1,
value: 'xxx',
color: 'xxx'
},
groupParts: [
{
id: 1,
Index: 7
},
{
id: 2,
Index: 11
}
]
}
]
}
How can I update the value of the Index in id:2 in the reducer?
this is my last try which does not update the value, but creates another section in the current state:
case CURRENT_WORD_INDEX_UPDATED:
const index=action.selectedWordIndex
return{...state,index:{...state.Groups[0].groupParts[1].index,index},}
you can make use of immutability-helper to update a nested state
import update from 'immutability-helper';
......
case CURRENT_WORD_INDEX_UPDATED:
const index=action.selectedWordIndex
return update(state, {
Groups: {
0: {
groupParts: {
0: {
Index: {
$set: index
}
}
}
}
}
})