Creating duplicate item if subarray has multiple items - javascript

I have an array of objects with the following structure:
patents: [
{
id: 1,
patentTitle: 'cling'
p3sServices: [
{
action: 'file',
color: 'green'
}
]
},
{
id: 2,
patentTitle: 'mirror
p3sServices: [
{
action: 'renew',
color: 'amber'
},
{
action: 'give',
color: 'blue'
}
]
}
]
I have the following code which loops through the nested array p3sServices, and creates and pushes an object the length of the `p3sServices'. So if it is only one item, once, if it is two items, twice.
function sortPatents(patents)
var obj = {
Green: [],
Amber: [],
Red: [],
Blue: [],
Black: [],
Grey: []
}
patents.forEach(function(patent){
patent.p3sServices.forEach(function(action, idx){
var string = action.currentStageColour.toLowerCase(); //SELECT COLOUR
var capitlized = string.charAt(0).toUpperCase() + string.slice(1);
//BEFORE THIS PUSH I NEED TO SPLICE/FILTER P3SSERVICES ARRAY
obj[capitlized].push(patent); //PUSH PATENT TO APPROPRIATE ARRAY IN OBJ
})
})
}
However, on push, I need to filter out the other item in p3sServices. So the above object item with id 2,if I looped through p3sServices array an was currently on index 0 with action: 'renew' and color: 'amber, I'd want to filter out index 1 (action: 'give'). Then when the loop is on index 1, I want to filter out index 0 (action: 'renew' and color: 'amber). I want the array to look like this afterwards:
newPatents: [
{
id: 1,
patentTitle: 'cling'
p3sServices: [
{
action: 'file',
color: 'green'
}
]
},
{
id: 2,
patentTitle: 'mirror
p3sServices: [
{
action: 'renew',
color: 'amber'
},
]
},
{
id: 2,
patentTitle: 'mirror
p3sServices: [
{
action: 'give',
color: 'blue'
}
]
}
]
Question
How would I achieve creating duplicates of the objects based on the nested array p3sServices, and then deleting the other item in the array before pushing, and repeating this process for the next itemin the array? Apologies if this doesn't make sense.

You could create copies of each patent, one for each service:
const unpackedServices = newPatents.map(patent => patent.p3sServices.map(serv => ({
id: patent.id,
patentTitle: patent.patentTitle,
p3sServices: [serv]
})
This will give you an array of arrays. In each nested item you will have one patent/service pair.
If you want to flatten the array, you use concat:
const result = [].concat(...unpackedServices);
Hope it helps.

Related

Merging two arrays of objects with one being used as a master overwriting any duplicates

I have two arrays of objects. Each object within that array has an array of objects.
I'm trying to merge the two arrays with one being used as a master, overwriting any duplicates in both the first level and the second 'option' level. Almost like a union join.
I've tried the code, however this doesn't cater for duplicate in options within a material.
Running this code results in two id: 400 options for the second material. When there should only be 1 with the value of 100cm.
Is there any smart way of doing this please? I also had a look at using sets, but again this only worked on the top level.
const materials_list = [
{
id: 2,
options: [
{
id: 300,
value: '50cm'
},
{
id: 400,
value: '75cm'
}
]
}
]
const master_materials_list = [
{
id: 1,
options: [
{
id: 200,
value: '50cm'
}
]
},
{
id: 2,
options: [
{
id: 400,
value: '100cm'
}
]
}
]
master_materials_list.forEach(masterMaterial => {
const matchMaterial = materials_list.find(existingMaterial => existingMaterial.id === masterMaterial.id);
if(matchMaterial) {
masterMaterial.options = masterMaterial?.options.concat(matchMaterial.options);
}
});
console.log(master_materials_list);
This is the desired output
[
{
id: 1,
options: [
{
id: 200,
value: '50cm'
}
]
},
{
id: 2,
options: [
{
id: 300,
name: '50cm'
},
{
id: 400,
name: '100cm'
}
]
}
]
Different approach that first makes a Map of the material_list options for o(1) lookup
Then when mapping the master list use filter() to find options stored in the above Map that don't already exist in the master
const materials_list=[{id:2,options:[{id:300,value:"50cm"},{id:400,value:"75cm"}]}, {id:999, options:[]}],
master_materials_list=[{id:1,options:[{id:200,value:"50cm"}]},{id:2,options:[{id:400,value:"100cm"}]}];
// store material list options array in a Map keyed by option id
const listMap = new Map(materials_list.map(o=>[o.id, o]));
// used to track ids found in master list
const masterIDs = new Set()
// map material list and return new objects to prevent mutation of original
const res = master_materials_list.map(({id, options, ...rest})=>{
// track this id
masterIDs.add(id)
// no need to search if the material list Map doesn't have this id
if(listMap.has(id)){
// Set of ids in this options array in master
const opIds = new Set(options.map(({id}) => id));
// filter others in the Map for any that don't already exist
const newOpts = listMap.get(id).options.filter(({id})=> !opIds.has(id));
// and merge them
options = [...options, ...newOpts]
}
// return the new object
return {id, options, ...rest};
});
// add material list items not found in master to results
listMap.forEach((v,k) =>{
if(!masterIDs.has(k)){
res.push({...v})
}
})
console.log(res)
.as-console-wrapper {max-height: 100%!important;top:0}
You can do this with lodash:
const materials_list = [
{
id: 2,
options: [
{
id: 300,
value: '50cm',
},
{
id: 400,
value: '75cm',
},
],
},
];
const master_materials_list = [
{
id: 1,
options: [
{
id: 200,
value: '50cm',
},
],
},
{
id: 2,
options: [
{
id: 400,
value: '100cm',
},
],
},
];
const customizer = (objValue, srcValue, propertyName) => {
if (propertyName === 'options') {
return _(srcValue)
.keyBy('id')
.mergeWith(_.keyBy(objValue, 'id'))
.values()
.value();
}
};
const merged = _(master_materials_list)
.keyBy('id')
.mergeWith(_.keyBy(materials_list, 'id'), customizer)
.values()
.value();
console.log(merged);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.21/lodash.min.js"></script>
You’re going to have to filter matchMaterials.options before the concat. Something like:
matchMaterial.options = matchMaterial.options.filter(opt =>
masterMaterial.options.find(val => val.Id === opt.Id) == null;
);
This should remove any “duplicate” options from matchMaterial before the concat.
EDIT:
I did this on my phone so I’m sorry if the code is formatted weird like I’m seeing now

Get cumulative length of array inside array of objects

I have been searching for quite a while but cannot find an answer to my issue. The problem is pretty simple; I have an array of objects, each containing another array of objects. I want to get the cumulative length of all arrays inside all objects.
Here is some sample data:
const items = [
{
id: 1,
title: "Test 1",
data: [
{
...
},
{
...
},
]
},
{
id: 2,
title: "Test 2",
data: [
{
...
},
]
}
]
In this sample, the length should be 3 since there is 2 objects inside the first object's data property and 1 object inside the second object's data property.
pretty simple
const items =
[ { id: 1, title: "Test 1", data: [{a:1},{a:1} ] }
, { id: 2, title: "Test 2", data: [{a:1},{a:1},{a:1},{a:1}] }
]
console.log('cumulative length ', items.reduce((a,c)=>a+c.data.length,0) )
All you need to do is loop through all items you got, check the length of the data array of each item and add that length to a variable. Here is a working snippet:
const items = [
{
id: 1,
title: "Test 1",
data: [
{
},
{
},
]
},
{
id: 2,
title: "Test 2",
data: [
{
},
]
}
];
// Initialize the count variable as 0
var count = 0;
// Pass through each item
items.forEach((item)=>{
// Adding the count of data of each item
count += item.data.length;
});
// Outputting the count
console.log(count);
If you want to use for of loop that works too.
const countObjects = (arrayInput) => {
let totalCount = 0
for(let item of items) {
totalCount += item.data.length
}
return totalCount
}
console.log(countObjects(items))

Spread Array of Objects in JavaScript

I have a react-redux application, and I have a reducer named dataReducer that has a default state like this:
const defaultState = {
isLoading: false,
data: [{
id: 1,
label: 'abc',
elements: [{ color: 'red', id: 1}],
}],
};
One of the reducers adds elements to data in the defaultState. I need to test this reducer by passing the payload and then validating the new state. I want to use the spread operator to build the new state from the old defaultState, but I am having some trouble achieving it. I tried the following, but it's not working:
const newElement = {
colour: 'blue',
id: 1
};
const newState = [
{
...defaultState.data[0],
elements: [{
...defaultState.data[0].elements,
...newElement,
}]
}
];
expect(dataReducer(defaultState, action)).toEqual(newState); // returns false
It would be great if I could somehow avoid using array index (defaultState.data[0]) as there might be multiple objects in the defaultState array in the real application, though for the purpose of testing, I am keeping just one object to keep things simple.
If you're adding to the end, you spread out the other aspects of state in the new state object, then override data with the current contents of it followed by the new entry:
const newState = { // New state is an object, not an aray
...defaultState, // Get everything from defaultState
data: [ // Replace `data` array with a new array
{
...defaultState.data[0], // with the first item's contents
elements: [ // updating its `elements` array
...defaultState.data[0].elements,
newElement
]
},
...defaultState.data.slice(1) // include any after the first (none in your example)
]
};
Live Example:
const defaultState = {
isLoading: false,
data: [{
id: 1,
label: 'abc',
elements: [{ color: 'red', id: 1}],
}],
};
const newElement = {
colour: 'blue',
id: 1
};
const newState = { // New state is an object, not an aray
...defaultState, // Get everything from defaultState
data: [ // Replace `data` array with a new array
{
...defaultState.data[0], // with the first item's contents
elements: [ // updating its `elements` array
...defaultState.data[0].elements,
newElement
]
},
...defaultState.data.slice(1) // include any after the first (none in your example)
]
};
console.log(newState);
.as-console-wrapper {
max-height: 100% !important;
}
There's no getting around specifying the entry in data that you want (e.g., data[0]).
In a comment you've asked how to handle this:
Let's say data (present inside defaultState) has multiple objects entries in it. First object has id of 1, second one has id of 2. Now the newElement to be added has an id of 2. So the newElement should get added to the second object. Where in second object? Inside the elements property of the second object. The addition should not over-write existing entries in the elements array.
You'll need to find the index of the entry in data:
const index = defaultState.data.findIndex(({id}) => id === newElement.id);
I'm going to assume you know that will always find something (so it won't return -1). To then apply that index to the code above, you'd do this:
const newState = { // New state is an object, not an aray
...defaultState, // Get everything from defaultState
data: [ // Replace `data` array with a new array
...defaultState.data.slice(0, index), // Include all entries prior to the one we're modifying
{
...defaultState.data[index], // Include the entry we're modifying...
elements: [ // ...updating its `elements` array
...defaultState.data[index].elements,
newElement
]
},
...defaultState.data.slice(index + 1) // include any after the one we're updating
]
};
The only real change there is adding the ...defaultState.data.slice(0, index) at the beginning of the new data, and using index instead of 0.
Live Example:
const defaultState = {
isLoading: false,
data: [
{
id: 1,
label: 'abc',
elements: [{ color: 'red', id: 1}],
},
{
id: 2,
label: 'def',
elements: [{ color: 'green', id: 2}],
},
{
id: 3,
label: 'ghi',
elements: [{ color: 'yellow', id: 3}],
}
],
};
const newElement = {
colour: 'blue',
id: 2
};
const index = defaultState.data.findIndex(({id}) => id === newElement.id);
const newState = { // New state is an object, not an aray
...defaultState, // Get everything from defaultState
data: [ // Replace `data` array with a new array
...defaultState.data.slice(0, index), // Include all entries prior to the one we're modifying
{
...defaultState.data[index], // Include the entry we're modifying...
elements: [ // ...updating its `elements` array
...defaultState.data[index].elements,
newElement
]
},
...defaultState.data.slice(index + 1) // include any after the one we're updating
]
};
console.log(newState);
.as-console-wrapper {
max-height: 100% !important;
}

How to add a value to an array in a specific location with JS

I have an array of objects:
const array = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 }
];
and I need to add another entry to it, but it needs to be placeable within any location in the array. So for example:
array.push({ id: 5, after_id: 2 }); and this should place the new entry between ids 2 and 3. Is there some standard way of doing this?
#p.s.w.g Has posted what is probably the best solution in a comment already, but I thought I'd post my original solution here as an answer now this is reopened.
You can use some to iterate through the array until the correct index is found, then you can slice the array and insert the item at the relevant index:
const arrayTest = [{
id: 1
},
{
id: 2
},
{
id: 3
},
{
id: 4
}
];
const insertAfterId = (array, item, idAfter) => {
let index = 0;
array.some((item, i) => {
index = i + 1;
return item.id === idAfter
})
return [
...array.slice(0, index),
item,
...array.slice(index, array.length),
];
};
const result = insertAfterId(arrayTest, {
id: 6
}, 2)
console.dir(result)

Reduce array of objects to parameter with the same objects

I hope the title is enough self-explanatory but I will try to clarify with a fiddle I've created. There are two arrays, one called tags which is what I currently have and tags_ideal which is what I really want to achieve.
Current state:
var tags = [
{
id: 1,
color: 'red',
l10n: [
{
name: 'Something something',
lang: 'english'
},
{
name: 'Etwas etwas',
lang: 'deutsch'
}
]
},
{
id: 2,
color: 'blue',
l10n: ...
}
]
What I'm after:
var tags_ideal = [
{
id: 1,
color: 'red',
l10n: {
'english': {
name: 'Something something',
},
'deutsch': {
name: 'Etwas etwas',
}
}
},
{
id: 2,
color: 'blue',
l10n: ...
}
]
I have the first case and want to convert all the stuff from l10n so that they don't have a lang: english parameter but rather have an object called english and the name/title inside of that. Below both tags is what I tried to do and what works when I pass just the l10n object into it, but not the whole thing (I do understand why, and now I would like to know how to do what I am really after).
Also, please note that my arrays are not correct at this point. I have appended three dots at the start of the next object just to point out that there is more than one object in my array.
Here's the fiddle:
https://jsfiddle.net/cgvpuj70/
You could iterate the outer array and map new objects for the inner arrays.
var tags = [{ id: 1, color: 'red', l10n: [{ name: 'Something something', lang: 'english' }, { name: 'Etwas etwas', lang: 'deutsch' }] }, { id: 2, color: 'blue', l10n: [] }];
tags.forEach(a => a.l10n = a.l10n.map(b => ({ [b.lang]: { name: b.name } })));
console.log(tags);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use .reduce() on the l10n to make an object populated with the keys/values derived from each object in the current Array.
var tags = [
{
id: 1,
color: 'red',
l10n: [
{
name: 'Something something',
lang: 'english'
},
{
name: 'Etwas etwas',
lang: 'deutsch'
}
]
},
{
id: 2,
color: 'blue'
}
]
tags.forEach(t => {
t.l10n = t.l10n && t.l10n.reduce((o, data) =>
Object.assign(o, {[data.lang]: {name: data.name}})
, {})
})
console.log(tags);
All you have to do is create an empty object and fill it up. First, create the new object. Then, for each element of the l10n array, set object[element.lang] equal to element.name. Now you've go the new json structure.
Here's a modified translate function:
function translate(title) {
var object = {};
for (var i=0; i<title.length; i++) {
var element = title[i];
object[element.lang] = element.name;
}
return object;
}
Edit: Here's how you can translate the entire object
var newTags = [];
for (var i=0; i<tags.length; i++) {
var edited = {};
edited.id = tags[i].id;
edited.color = tags[i].color;
edited.l10n = translate(tags[i].l10n);
newTags.push(edited);
}

Categories

Resources