Using .map method to update the object that I'm evaluating - javascript

I am executing the map method on an array selectedItem where selectedItem has a string property nodeType and an object property items :
items = { computers: 0, drives: 0, files: 0, folders: 0 }
selectedItem?.map(item => {
switch (item.nodeType) {
case 'FileType':
return (items.files += 1);
case 'FolderType':
return (items.folders += 1);
case 'DriveType':
return (items.drives += 1);
case 'ComputerType':
return (items.computers += 1);
default:
return;
}
I know I should be able to replace the switch statement by using the built-in functionality of the map method (hasOwnProperty), but I've only seen examples where Object literals are used to match on in order to return the string, like this:
const itemTypes = {
file: 'file',
folder: 'folder',
drive: 'drive',
computer: 'computer'
}
However, upon a .map(expression) match I want to execute a simple incrementing logic to +=1 to a property within the object I'm evaluating.
I've looked at .filter() and .reduce() but of course don't apply since they return a different array. I'm sure it may have to deal with writing the correct function within the .map(expression) but not sure how to do that.
I've looked at these SO posts, but they either don't apply or don't get me all the way there. The second link is very close to what I'm looking for but not sure how to apply it to my code:
Update multiple objects using Javascript map() function
Update Map object property values
How would I do that using only map and not nesting a switch within the map function?

map() is used to create a new array. It is not useful when you want to update an object.
You should be using reduce to count up all the types
const selectedItem = [
{ nodeType: 'FolderType' },
{ nodeType: 'DriveType' },
{ nodeType: 'DriveType' },
{ nodeType: 'FolderType' },
{ nodeType: 'ComputerType' },
];
const types = {
FileType: 'files',
FolderType: 'folders',
DriveType: 'drives',
ComputerType: 'computers'
};
const items = {
computers: 0,
drives: 0,
files: 0,
folders: 0
};
selectedItem?.reduce((obj, item) => {
const type = types[item.nodeType];
if (type) obj[type]++;
return obj;
}, items);
console.log(items);
or forEach
const selectedItem = [
{ nodeType: 'FolderType' },
{ nodeType: 'DriveType' },
{ nodeType: 'DriveType' },
{ nodeType: 'FolderType' },
{ nodeType: 'ComputerType' },
];
const types = {
FileType: 'files',
FolderType: 'folders',
DriveType: 'drives',
ComputerType: 'computers'
};
const items = {
computers: 0,
drives: 0,
files: 0,
folders: 0
};
selectedItem?.forEach((item) => {
const type = types[item.nodeType];
if (type) items[type]++;
});
console.log(items);

You can use a map object to associate the node types to their items keys.
const itemsKeyByNodeType = {
FileType: 'files',
FolderType: 'folders',
DriveType: 'drives',
ComputerType: 'computers'
};
selectedItem?.forEach(item => {
if (itemsKeyByNodeType[item.nodeType]) {
items[itemsKeyByNodeType[item.nodeType]]++;
}
});

Related

Create new object from array

I'm trying to create new object with different properties name from Array.
Array is:
profiles: Array(1)
0:
column:
name: "profileName"
title: "Profile name"
status: "Active"
I want to create new function that return object with two properties:
id: 'profileName',
profileStatus: 'Active'
The function that I have create is returning only one property as undefined undefined=undefined.
function getProfile(profiles) {
if (!profiles.length) return undefined;
return profiles.reduce((obj, profile) => {
console.log('profiles', profile);
return ({
...obj,
id: profile.column.name,
profileStatus: profile.status,
});
}, {});
}
The function getProfile is taking as input array 'profiles' from outside,
I've just tested here and this seems to be working actually
const getProfile1 = (p) => p.reduce((obj, profile) =>({
...obj,
id: profile.column.name,
profileStatus: profile.status,
}), {});
You can use map as an alternative.
var profiles = [{"column":{"name": "profileName3","title": "3Profile name"},"status": "Active"},{"column":{"name": "profileName","title": "Profile name"},"status": "Active"}];
function getProfile(profiles) {
if (!profiles.length) return undefined;
return profiles.map(function(profile,v){
return {id:profile.column.name,profileStatus: profile.status};
});
}
console.log(getProfile(profiles));
Whenever I use reduce in this way, I usually index the final object by some sort of an id. As noted in another answer, you could use map in this situation as well. If you really want your final data structure to be an object, however, you could do something like this:
/**
* returns object indexed by profile id
*/
const formatProfiles = (profiles) => {
return profiles.reduce((obj, profile) => {
return {
...obj,
[profile.id]: {
id: profile.column.name,
profileStatus: profile.status,
}
};
}, {});
};
const profiles = [
{
id: 0,
status: 'active',
column: {
name: "profile_name_1",
title: "profile_title_1",
},
},
{
id: 1,
status: 'inactive',
column: {
name: "profile_name_2",
title: "profile_title_2",
}
}
];
const result = formatProfiles(profiles);
/**
* Result would look like this:
*/
// {
// '0': { id: 'profile_name_1', profileStatus: 'active' },
// '1': { id: 'profile_name_2', profileStatus: 'inactive' }
// }

JavaScript - Properly Extract Deep Object Properties and Construct New Object

Suppose the following array of objects is returned from an API:
const data = [
{ // first item
meta: {
stems: [
"serpentine",
"serpentinely"
]
},
hwi: {
hw: "sep*pen*tine",
prs: [
{
mw: "ˈsər-pən-ˌtēn",
sound: {
audio: "serpen02"
}
},
]
},
shortdef: [
"of or resembling a serpent (as in form or movement)",
"subtly wily or tempting",
"winding or turning one way and another"
]
},
{ // second item
meta: {
stems: [
"moribund",
"moribundities",
"moribundity"
]
},
hwi: {
hw: "mor*i*bund",
},
fl: "adjective"
}
]
I want to create a function that will generate a new array of objects. The objects in this new array will consist of data from the old objects, just rearranged. This is how I expect a new array to look, for example:
[
{
word: 'serpentine',
definitions: [
'of or resembling a serpent (as in form or movement)',
'subtly wily or tempting',
'winding or turning one way and another'
]
},
{
word: 'moribund',
definitions: [
'being in the state of dying : approaching death',
'being in a state of inactivity or obsolescence'
],
partOfSpeech: 'adjective'
}
]
I do this with the following function:
const buildNewData = arr => {
const newData = []
arr.forEach(item => {
newData.push({
...item.meta.stems[0] && { word: item.meta.stems[0]},
...item.shortdef && { definitions: item.shortdef },
...item.fl && { partOfSpeech: item.fl },
...item.hwi.prs[0].mw && { pronunciation: item.hwi.prs[0].mw}
})
})
return newData
}
buildNewData(data)
You may be curious as to why I use ...item.meta.stems[0] && { word: item.meta.stems[0]} in the creation of the new objects. This is to check if the property exists in the original object. If it doesn't exist, the expression will evaluate to false and therefore not be added to the new object. The first object in the original array does not have the fl property, so it evaluates to false when the new object is being constructed.
But this doesn't work when looking up a property that is an array. The code above fails with the error: TypeError: Cannot read property '0' of undefined. That's because the second item does not have a prs array under the hwi property, so the lookup fails.
Since I cannot control what data is returned from the API, how do I write a function that successfully creates a new array of objects in the format I've specified, without causing an error? I already have a solution to not add particular properties if they do not exist, but how do I take into account arrays?
More generally, I'm curious if there is a standardized way of extracting data from objects programmatically that prevents errors like this from occurring. Is there a better way to do this?
You need an additional guard so:
...item.hwi.prs[0].mw && { pronunciation: item.hwi.prs[0].mw}
becomes
...(Array.isArray(item.hwi.prs) && item.hwi.prs[0].mw) && { pronunciation: item.hwi.prs[0].mw}
which can be shortened to:
...(item.hwi.prs && item.hwi.prs[0].mw) && { pronunciation: item.hwi.prs[0].mw}
if you are confident that if item.hwi.prs exists its value will be an array that has a 0 value that can be spread.
const data = [
{ // first item
meta: {
stems: [
"serpentine",
"serpentinely"
]
},
hwi: {
hw: "sep*pen*tine",
prs: [
{
mw: "ˈsər-pən-ˌtēn",
sound: {
audio: "serpen02"
}
},
]
},
shortdef: [
"of or resembling a serpent (as in form or movement)",
"subtly wily or tempting",
"winding or turning one way and another"
]
},
{ // second item
meta: {
stems: [
"moribund",
"moribundities",
"moribundity"
]
},
hwi: {
hw: "mor*i*bund",
},
fl: "adjective"
}
];
const buildNewData = arr => {
const newData = []
arr.forEach(item => {
newData.push({
...item.meta.stems[0] && { word: item.meta.stems[0]},
...item.shortdef && { definitions: item.shortdef },
...item.fl && { partOfSpeech: item.fl },
...(Array.isArray(item.hwi.prs) && item.hwi.prs[0].mw) && { pronunciation: item.hwi.prs[0].mw}
})
})
return newData
}
let newData = buildNewData(data);
console.log(newData);
As you need to check existence of properties in an Object:
Use Optionnal chaining: https://javascript.info/optional-chaining
It returns a type undefined if the prop doesn't exist (but not string "undefined" ;) )
For desired order in new array, add numbers before the names of props.
let newData = [];
for (let i = 0; i < data.length; i++) {
newData[i] = {};
if (data[i]?.meta?.stems[i] != undefined)
newData[i].word = data[i].meta.stems[i];
if (data[i]?.shortdef != undefined) {
newData[i].definitions = data[i].shortdef.join(', ') + '.';
newData[i].definitions = newData[i].definitions.charAt(0).toUpperCase() + newData[i].definitions.substring(1); // Capitalize first letter
}
if (data[i]?.fl != undefined)
newData[i].partOfSpeech = data[i].fl;
}
console.log(...newData);

How do you move an object from one array to another array by feature?

I am trying to move an object from one array to another. Think of it like adding / moving a friend from non-friend to friend. I have two arrays, which can be seen below, and I am trying to move an object (i.e. a friend) from possible to current via it's 'id'. In the below example, I am trying to move Parker from possible to current with id = 2.
state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
}
function addFriend(state, action) {
const { current, possible } = state;
const addedFriend = Object.assign(
{},
state.possible.splice(action.payload.index, 1)
);
current.push(addedFriend);
const newState = { current, possible };
return newState;
}
Since you can remove multiple elements with splice(), it returns an array. Index the result to get the specific object. You don't need to use Object.assign(), that just copies the value (which just converts the array into an object whose properties are the array indexes).
var state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
};
function addFriend(state, action) {
const { current, possible } = state;
const addedFriend = state.possible.splice(action.payload.index, 1)[0];
current.push(addedFriend);
const newState = { current, possible };
return newState;
}
state = addFriend(state, {payload: { index: 0 }});
console.log(state);
I'm not sure why you're returning a new state object, since you're modifying the old state in place.
It is not that time-efficient if you want a fast running code. But it follows immutability.
We just ignore the item from possible, and add it to current.
state = {
current: [
{
id: 1,
name: 'peter'
}
],
possible: [
{
id: 2,
name: 'parker'
}
]
}
function addFriend(state, action) {
const { current, possible } = state;
return {
...state,
current: current.concat(possible[action.payload.index]),
possible: possible.filter((_, index) => index !== action.payload.index)
}
}
state = addFriend(state, {payload: {index: 0}})
console.log(state)

ESlint Error when using map - workaround?

What is the workaround to update the dataLine when using data.Items.map()
I am getting eslint error:
Assignment to property of function parameter 'dataLine'
You can see I am deleting Other property and modifying dataLine.Config
const data = {
Type: "API",
Items: [{
State: [{Name: "Pending"}],
Config: {
Size: "M"
},
Other: "string.."
}]
}
const newItems = data.Items.map(({State,...dataLine}) => {
if (data.Type == "API") {
dataLine.Config = {
Size: "L"
};
delete dataLine.Other;
}
return dataLine;
});
console.log(JSON.stringify(newItems, null, 2));
About eslint, I think it's a missing piece, because if you write your function in an equivalent way:
data.Items.map((dataLine) => {
if (data.Type == "API") {
dataLine.Config = {
Size: "L"
};
delete dataLine.Other;
}
return dataLine;
});
you won't receive any warning. Maybe it's the case of open an issue there.
You could pass {props : true}, like GProst said, but this will enforce you to not make the assignment of any property of the parameter, which is a good thing, for example:
const newItems = data.Items.map(({State,...dataLine}) => {
if (data.Type == "API") {
dataLine.Config = { // not allowed with props : true
Size: "L"
};
delete dataLine.Other; // not allowed with props : true
}
return dataLine;
});
Why eslint have such a rule?
You are modifying the properties of data.Items, this will cause side effects on the external environment of the callback function on map. In some cases this will put you in bad situation, like not knowing which piece of code removed some property.
A suggestion about how you can deal with this safely is return an entire new object to make your data.Items immutable in your case:
const data = {
Type: "API",
Items: [{
State: [{Name: "Pending"}],
Config: {
Size: "M"
},
Other: "string.."
}]
}
const newItems = data.Items.map(({State,...dataLine}) => {
const dataLineCopy = JSON.parse(JSON.stringify(dataLine))
if (data.Type == "API") {
dataLineCopy.Config = {
Size: "L"
};
delete dataLineCopy.Other;
}
return dataLineCopy;
});
console.log(JSON.stringify(newItems, null, 2));
Edit no-param-reassign rule in eslint config, set option props to false:
"no-param-reassign": ["error", { "props": false }]

JS: Generate objects, which are connected dynamically via parent field

I need to generate some objects, which represents DB documents and I need to 'connect' them via parent reference. The objects are pretty simple.
The main problem for me is to define the input
The objects should be connected in a variable way: I would like to call a function with some parameters to get the result data.To make it a bit easier to understand here an example.
The structure of the needed result data could look like this:
- main
- group
- group
- item
- item
- item
- main
So for this the result output should be:
[
{ _id: 'main1ID' },
{ _id: 'group1ID', parent: 'main1ID', type: 'group' },
{ _id: 'group2ID', parent: 'main1ID', type: 'group' },
{ _id: 'item1ID', parent: 'group1ID', type: 'item' },
{ _id: 'item2ID', parent: 'group1ID', type: 'item' },
{ _id: 'item3ID', parent: 'main1ID', type: 'item' },
{ _id: 'main2ID' },
]
As you can see a main element can have group or items as children or even have no child.
Groups can also have no child at all or items as children.
So I tried to start with a basic function, but I even don't know how to define the parameters to get this function working dynamically :-(
generateContent(2, 2, 3) could create two main object, two groups and three items, but there is no information, how they should be connected to each other. And this is the main problem for me...
function generateContent (main = 0, group = 0, item = 0) {
const mainDocs = []
for (var i = 0; i <= main; i++) {
mainDocs.push({
_id: Random.id()
})
}
// do similiar thing for group and item
// return everything
}
You would need to encode in your input the relations. It is not enough to indicate the number of each type of node. There are several encodings you could think of. A very concise one would be a string pattern like the following:
m(gg(ii)i)m
Here each letter represents the type of node to produce, and the parentheses make clear how they should relate to each other.
Here is a function that would parse such a string into the final array:
function generateContent (pattern) {
const mainDocs = [],
parents = [],
count = { m:0, g:0, i:0 };
let obj, parent;
for (let ch of pattern) {
if (ch === '(') {
parents.push(parent);
parent = obj._id;
} else if (ch === ')') {
parent = parents.pop();
} else {
let type = { m: 'main', g: 'group', i: 'item' }[ch];
obj = {
_id: type + (++count[ch]) + 'ID'
};
if (parent) obj.parent = parent;
if (ch !== 'm') obj.type = type;
mainDocs.push(obj);
}
}
return mainDocs;
}
// Example:
const result = generateContent('m(gg(ii)i)m');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources