How to combine 2 nested array of object values - javascript

How do I combine 2 nested arrays.
There are 2 Array A and B
const A = [
{
"id": 0,
"bp": true,
"ba": "value1",
"risk": [
{
"id": 0.1,
"rk": false,
"title": "risk1",
"control": [
{
"id": 0.12,
"ctl": "ctl1"
},
{
"id": 0.13,
"ctl": "ctl2"
}
]
},
{
"id": 0.1223,
"rk": false,
"title": "risk23"
}
],
"master": [
{
"id": 0.2,
"mk": false,
"title": "obli1",
"control": [
{
"id": 0.12,
"ctl": "ctl1"
}
]
}
]
},
{
"id": 2,
"bp": true,
"ba": "value2"
}
]
.
const B = [
{
"id": 0,
"bp": true,
"ba": "value1",
"risk": [
{
"id": 0.1,
"rk": false,
"title": "risk1",
"control": [
{
"id": 0.12,
"ctl": "ctl1"
},
{
"id": 0.13,
"ctl": "ctl2"
}
]
}
],
"master": [
{
"id": 0.2,
"mk": false,
"title": "obli1",
"control": [
{
"id": 0.12,
"ctl": "ctl1"
}
]
},
{
"id": 0.211,
"mk": true,
"title": "obli44",
"control": [
{
"id": 0.12,
"ctl": "ctl1"
}
]
}
]
},
{
"id": 3,
"bp": true,
"ba": "value3"
}
]
.
on combining A and B the output should be of below format.
We need to take risk from table A and we need to take master from table B and add it to table C.
The Final array looks like the following.
.
const c = [
{
"id": 0,
"bp": true,
"ba": "value1",
"risk": [
{
"id": 0.1,
"rk": false,
"title": "risk1",
"control": [
{
"id": 0.12,
"ctl": "ctl1"
},
{
"id": 0.13,
"ctl": "ctl2"
}
]
},
{
"id": 0.1223,
"rk": false,
"title": "risk23"
}
],
"master": [
{
"id": 0.2,
"mk": false,
"title": "obli1",
"control": [
{
"id": 0.12,
"ctl": "ctl1"
}
]
},
{
"id": 0.211,
"mk": true,
"title": "obli44",
"control": [
{
"id": 0.12,
"ctl": "ctl1"
}
]
}
]
},
{
"id": 2,
"bp": true,
"ba": "value2"
},
{
"id": 3,
"bp": true,
"ba": "value3"
}
]
The data can be more than 1000 records. please help me with which it will save time and complexity.
Thanks in Advance.

The first thing you need to do is to isolate the IDs from array A and B. You can also map over the two of them concatenated together, but then you may have duplicate values. This seems like a rational way to go about ensuring single entries for each ID.
const allIds = [...a.map(x => x.id), ...b.map(x => x.id)];
const uniqueIds = allIds.reduce((prev, curr) => {
return prev.includes(curr) ? prev : [...prev, curr]
}, []);
Then you need to take those uniqueIds, and map over each of them. For each ID, you need to return the matching object in A and the matching object in B, but overwrite the risk property with the value from the object in A and overwrite the master property with the value from the object in B.
That can be done by just finding the matching objects in A and B, and then use the spread operator to assign their entries to the new object.
Note: If the properties outside of risk and master (e.g. bp or ba) have different values, then the element listed last will overwrite it. In the below case, that means if the bp value in A is true and the bp value in B is false, then the bp value in C will be false.
const c = uniqueIds.map(id => {
const elementInA = a.find(x => x.id === id);
const elementInB = b.find(x => x.id === id);
return {
...elementInA,
...elementInB,
risk: [
...elementInA?.risk ?? []
],
master: [
...elementInB?.master ?? []
]
};
});

Instead of looping through multiple times both arrays, I would suggest something that will loop through each array, not more than needed.
The following approach will loop through twice the first array, and once the B array. (not considering the indexOf method).
const ids = A.map(({id}) => id), C = A.map(a => a);
for(let b of B){
let index = ids.indexOf(b.id);
if(index < 0){
C.push(b);
} else {
C[index] = {
...A[index],
...b,
risk: [
...A[index]?.risk ?? []
],
master: [
...b?.master ?? []
]
}
}
}

Related

How can I return an array of values from an array of grouped objects in js?

I have an array of subscriptions group by the type (basic,medium ...)
`
[
[
"Basic",
[
{ "id": 2, "name": "Basic", "started_at": "2022-01-24", "count": 4 },
{ "id": 2, "name": "Basic", "started_at": "2022-03-16", "count": 2 },
{ "id": 2, "name": "Basic", "started_at": "2022-05-16", "count": 1 }
]
],
[
"Medium",
[
{ "id": 3, "name": "Medium", "started_at": "2022-02-21", "count": 1 },
{ "id": 3, "name": "Medium", "started_at": "2022-05-28", "count": 1 }
]
],
[
"Premium",
[{ "id": 4, "name": "Premium", "started_at": "2022-04-21", "count": 1 }]
],
[
"Master",
[
{ "id": 7, "name": "Master", "started_at": "2022-07-28", "count": 1 },
{ "id": 7, "name": "Master", "started_at": "2022-08-02", "count": 1 }
]
],
[
"Jedi",
[{ "id": 6, "name": "Jedi", "started_at": "2022-09-28", "count": 1 }]
]
]
`
What I want to do is return an array containing objects foreach sub with the following data(get the count value by month):
`
[
{
label: "Basic",
data: [4, 0, 2, 0, 1,0],
},
{
label: "Medium",
data: [0, 1, 0, 0, 1,0],
},
...
]
`
The data field should contain the count field foreach subscription corresponding to the month. For example for with count 4 in January and count 2 in March it will return [4,0,1] with 0 for February.
How can I achieve that ?
I did this but it's returning only the existing month values so there is no way to know which month that value is for.
subscriptions.map((item) => {
return {
label: item[0],
data: item[1].map((value, index) => {
return value.count;
}),
};
})
You could reduce the array and create a mapper object which maps each plan with month specifc count. Something like this:
{
"Basic": {
"1": 4,
"3": 2,
"5": 1
},
"Medium": {
"2": 1,
"5": 1
},
...
}
Then loop through the entries of the object and create objects with plan as label and an array of length: 12 and get the data for that specific month using the index
const input=[["Basic",[{id:2,name:"Basic",started_at:"2022-01-24",count:4},{id:2,name:"Basic",started_at:"2022-03-16",count:2},{id:2,name:"Basic",started_at:"2022-05-16",count:1}]],["Medium",[{id:3,name:"Medium",started_at:"2022-02-21",count:1},{id:3,name:"Medium",started_at:"2022-05-28",count:1}]],["Premium",[{id:4,name:"Premium",started_at:"2022-04-21",count:1}]],["Master",[{id:7,name:"Master",started_at:"2022-07-28",count:1},{id:7,name:"Master",started_at:"2022-08-02",count:1}]],["Jedi",[{id:6,name:"Jedi",started_at:"2022-09-28",count:1}]]];
const mapper = input.reduce((acc, [plan, subscriptions]) => {
acc[plan] ??= {}
for(const { started_at, count } of subscriptions)
acc[plan][+started_at.slice(5,7)] = count
return acc;
}, {})
const output =
Object.entries(mapper)
.map( ([label, subData]) => ({
label,
data: Array.from({ length: 12 }, (_, i) => subData[i+1] ?? 0)
}) )
console.log(output)
Note:
This assumes that the data is for a single year only. If it can be across years you'd have to create another level of nesting:
{
"Basic": {
"2022": {
"1": 3
}
}
}
started_at.slice(5,7) is used to get the month number. If the dates are not in the ISO 8601 format, you can use new Date(started_at).getMonth() + 1 to get the month part.

Remove data from my nested array of objects by matching values

Remove data from my nested array of objects by matching values. In my case I want to strip out the objects that are NOT active. So every object that contains active 0 needs to be removed.
[
{
"id" : 1,
"title" : 'list of...',
"goals": [
{
"id": 1569,
"active": 0
},
{
"id": 1570,
"active": 1
},
{
"id": 1571,
"active": 0
}
],
},
{
"id" : 2,
"title" : 'more goals',
"goals": [
{
"id": 1069,
"active": 0
},
{
"id": 1070,
"active": 1
},
],
},
]
The following will return the array in an unchanged status
public stripGoalsByInactiveGoals(clusters) {
return clusters.filter(cluster =>
cluster.goals.filter(goal => goal.active === 1)
);
}
array.filter wait a boolean to know if it has to filter data or not
in your case you have an array of array, you want to filter "sub" array by active goal
if you want to keep only active goals change your first filter by map to return a modify value of your array filtered by a condition
function stripGoalsByInactiveGoals(clusters) {
return clusters.map(cluster => {
return {
goals: cluster.goals.filter(goal => goal.active)
};
});
}
var data = [{
"goals": [{
"id": 1569,
"active": 0
},
{
"id": 1570,
"active": 1
},
{
"id": 1571,
"active": 0
}
],
},
{
"goals": [{
"id": 1069,
"active": 0
},
{
"id": 1070,
"active": 1
},
],
},
];
function stripGoalsByInactiveGoals(clusters) {
return clusters.map(cluster => {
return {
goals: cluster.goals.filter(goal => goal.active)
};
});
}
console.log(stripGoalsByInactiveGoals(data));
You can create another array (for the case when you need the input unchanged as well) and loop the input, appending each member objects' filtered goals array. You could also avoid appending the item if goals is empty after the filter, but this example doesn't do this, because it was not specified as a requirement.
let input = [
{
"goals": [
{
"id": 1569,
"active": 0
},
{
"id": 1570,
"active": 1
},
{
"id": 1571,
"active": 0
}
],
},
{
"goals": [
{
"id": 1069,
"active": 0
},
{
"id": 1070,
"active": 1
},
],
},
]
let output = [];
for (let item of input) {
output.push({goals: item.goals.filter(element => (element.active))})
}
console.log(output);
You can follow this for a dynamic approach:
stripGoalsByInactiveGoals(clusters) {
var res = [];
this.data.forEach((item) => {
let itemObj = {};
Object.keys(item).forEach((key) => {
itemObj[key] = item[key].filter(x => x.active != 0);
res.push(itemObj);
});
});
return res;
}
Stackbiltz Demo

JavaScript / typescript: unable to regenerate object

I have an object as specified below:
{
"player settings": [
{
"id": 1,
"labelName": "site language",
"labelValue": [
{
"id": 1,
"languageName": "ARABIC",
"language": "لغتك",
"languageCode": "AE"
},
{
"id": 2,
"languageName": "CHINESE",
"language": "你的语言",
"languageCode": "ZH"
},
],
"dataType": "DD",
"selectedData": "2"
},
{
"id": 2,
"labelName": "subtitle language",
"labelValue": [
{
"id": 1,
"languageName": "ARABIC",
"language": "لغتك",
"languageCode": "AE"
},
{
"id": 2,
"languageName": "CHINESE",
"language": "你的语言",
"languageCode": "ZH"
},
],
"dataType": "DD",
"selectedData": "1"
},
]
},
{
"channel": [
{
"id": 11,
"labelName": "channel label",
"dataType": "TX",
"selectedData": "jhfh"
}
]
},
{
"others": [
{
"id": 16,
"labelName": "others label",
"dataType": "TX",
"selectedData": "dhgdhg"
}
]
}
How can I modify and re-generate the object with the following conditions:
if dataType === 'DD' then convert selectedData into number.
I wrote the below code but stuck here:
for (var j = 0; j < this.myobject.length; j++){
this.myobject.forEach(obj => {
console.log(obj)
});
}
You can use for..in
let data = {"player settings": [{"id": 1,"labelName": "site language","labelValue": [{"id": 1,"languageName": "ARABIC","language": "لغتك","languageCode": "AE"},{"id": 2,"languageName": "CHINESE","language": "你的语言","languageCode":"ZH"},],"dataType": "DD","selectedData": "2"},],"player settings2": [{"id": 1,"labelName": "site language","labelValue": [{"id": 1,"languageName": "ARABIC","language": "لغتك","languageCode": "AE"},{"id": 2,"languageName": "CHINESE","language": "你的语言","languageCode":"ZH"},],"dataType": "NO DD","selectedData": "2"},]}
for (let key in data) {
data[key].forEach(obj => {
if (obj.dataType === "DD") {
obj.selectedData = +(obj.selectedData || 0)
}
})
}
console.log(data)
Immutable approach
let data = {"player settings": [{"id": 1,"labelName": "site language","labelValue": [{"id": 1,"languageName": "ARABIC","language": "لغتك","languageCode": "AE"},{"id": 2,"languageName": "CHINESE","language": "你的语言","languageCode":"ZH"},],"dataType": "DD","selectedData": "2"},],"player settings2": [{"id": 1,"labelName": "site language","labelValue": [{"id": 1,"languageName": "ARABIC","language": "لغتك","languageCode": "AE"},{"id": 2,"languageName": "CHINESE","language": "你的语言","languageCode":"ZH"},],"dataType": "NO DD","selectedData": "2"},]}
let newObj = {}
for (let key in data) {
newObj[key] = data[key]
data[key].forEach(obj => {
if (obj.dataType === "DD") {
newObj.selectedData = +(obj.selectedData || 0)
}
})
}
console.log(newObj)
We can use filter on the main obj and then proceed modifying the object.
function modifyDataToNumber(){
let myObject = jsonObj['player settings'];
let ddMyObject = myObject.filter((row)=>(row["dataType"]==="DD"));
console.log(ddMyObject[0]["selectedData"]);
ddMyObject.forEach((row,index)=>{
ddMyObject[index]["selectedData"] = +ddMyObject[index]["selectedData"];
})
console.log(jsonObj);
}
modifyDataToNumber();
I would do something like this
const json = {
"player settings": [
{
"id": 1,
"labelName": "site language",
"labelValue": [
{
"id": 1,
"languageName": "ARABIC",
"language": "لغتك",
"languageCode": "AE"
},
{
"id": 2,
"languageName": "CHINESE",
"language": "你的语言",
"languageCode": "ZH"
},
],
"dataType": "DD",
"selectedData": "2"
},
]
};
json['player settings'] = json['player settings'].map(setting => {
if (setting.dataType === 'DD') {
const updatedSetting = {
...setting,
selectedData: parseInt(setting.selectedData)
};
return updatedSetting;
}
return setting;
});
console.log('Result', json);
Since you say "re-generate", I assume you want an immutable approach to this (that is, generate a copy of the data with the desired changes, rather than changing the original object).
To that, you can use spread syntax and Array#map:
let convertSetting = setting => ({
...setting,
selectedData: setting.dataType === "DD"
? parseInt(setting.selectedData)
: setting.selectedData
});
let convert = x => ({
...x,
["player settings"]: x["player settings"].map(convertSetting)
});
Then you can use that function as convert(yourOriginalObject).

How to sort a nested array using lodash

I have a collection of objects in this array and I need to order them by the 'order' key (asc). Is there a way to sort the objects inside the array and then return the whole array? I am relying on the order as I'm using it in a v-for with a :key.
[
{
"id":0,
"type":"Header",
"order":1,
"props":{
"order":0,
"id":0,
"section_id":0
},
"data":{
"header":""
},
"component":"header-block"
},
{
"id":1,
"type":"Header",
"order":0,
"props":{
"order":1,
"id":1,
"section_id":0
},
"data":{
"header":""
},
"component":"header-block"
}
],
[
//Another collection of objects
]
I am currently doing this -
getters: {
sorted: state => {
return _.orderBy(state.experience_sections, function(block) {
if(block.experience_blocks[0]) {
return block.experience_blocks[0].order;
}
});
}
}
The solution above does not seem to order the objects by 'asc' order. Am I on the right track?
Thanks!
P.S. Stack is telling me that is a possible duplicate question but I'm at a loss after hours of searching. My apologies if I missed an already answered question.
Just in case you want plain javascript solution.. using Array.forEach
I have also extended your array to contain more data
var arr = [[
{
"id":0,
"type":"Header",
"order":1,
"props":{
"order":0,
"id":0,
"section_id":0
},
"data":{
"header":""
},
"component":"header-block"
},
{
"id":1,
"type":"Header",
"order":0,
"props":{
"order":1,
"id":1,
"section_id":0
},
"data":{
"header":""
},
"component":"header-block"
}
], [
{
"id":0,
"type":"Header",
"order":2,
"props":{
"order":0,
"id":0,
"section_id":0
},
"data":{
"header":""
},
"component":"header-block"
},
{
"id":1,
"type":"Header",
"order":1,
"props":{
"order":1,
"id":1,
"section_id":0
},
"data":{
"header":""
},
"component":"header-block"
}
]]
arr.forEach(d => d.sort((a,b) => a.order - b.order))
console.log(arr)
You should also consider orderBy method from lodash since you could easily change from asc to desc sort order if you would want to at a later date or have it via a variable being passed through the UI etc:
const data = [ [{ "id": 0, "type": "Header", "order": 1, "props": { "order": 0, "id": 0, "section_id": 0 }, "data": { "header": "" }, "component": "header-block" }, { "id": 1, "type": "Header", "order": 0, "props": { "order": 1, "id": 1, "section_id": 0 }, "data": { "header": "" }, "component": "header-block" } ], [{ "id": 0, "type": "Header", "order": 2, "props": { "order": 0, "id": 0, "section_id": 0 }, "data": { "header": "" }, "component": "header-block" }, { "id": 1, "type": "Header", "order": 1, "props": { "order": 1, "id": 1, "section_id": 0 }, "data": { "header": "" }, "component": "header-block" } ] ]
console.log('asc:', _.map(data, x => _.orderBy(x, 'order'))) // asc order
console.log('desc:', _.map(data, x => _.orderBy(x, 'order', 'desc'))) // desc
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Will sort each subarray in an array
const sortedArr = _.map(arr, subArray => _.sortBy(subArray, "order"));
Deep sorting using lodash
const sortedArray = _.orderBy(items, [(item) => {
const nestedObj = _.get(item, 'props');
item['props'] = _.orderBy(nestedObj,'order','desc');
return item['order'];
}], 'desc');

Filter and clone object properties

I have 2 arrays of objects: itemsList and itemsFetched. All of the objects inside each array have the same structure (nr of key/values). One of those keys has the same 'meaning' but a different name (item_id on itemsList, id on itemsFetched ). Their values are the same.
I need to filter the itemsList array and leave only the objects that have the item_id value equal to the id value on itemsFetched. Then copy(add) the key/value count from each object on the itemsFetched array (which matches the item_id=id) to the filtered array.
I've a working code but I'm sure it isnt the best way to solve this problem. I've already asked something similar before (regarding the 'filter' part) which solved my problem, but since I had to add the 'count' part after the filtering, I ended up refactoring the whole thing.
itemsList (sample)
[
{
"id": 0,
"name": "Egg",
"img": "http://www.serebii.net/pokemongo/items/egg.png"
},
{
"id": 1,
"name": "Pokeball",
"img": "http://www.serebii.net/pokemongo/items/20pokeballs.png"
},
{
"id": 2,
"name": "Greatball",
"img": "http://www.serebii.net/pokemongo/items/greatball.png"
},
{
"id": 401,
"name": "Incense",
"img": "http://www.serebii.net/pokemongo/items/incense.png"
},
{
"id": 901,
"name": "Incubator (Unlimited)",
"img": "http://www.serebii.net/pokemongo/items/eggincubator.png"
}
]
itemsFetched (sample)
[
{
"item_id": 1,
"count": 50,
"unseen": true
},
{
"item_id": 401,
"count": 2,
"unseen": true
},
{
"item_id": 901,
"count": 1,
"unseen": true
}
]
resultArray (what I want in the end)
[
{
"id": 1,
"name": "Pokeball",
"count": 50,
"img": "http://www.serebii.net/pokemongo/items/20pokeballs.png",
},
{
"id": 401,
"name": "Incense",
"count": 2,
"img": "http://www.serebii.net/pokemongo/items/incense.png"
},
{
"id": 901,
"name": "Incubator (Unlimited)",
"count": 1,
"img": "http://www.serebii.net/pokemongo/items/eggincubator.png"
}
]
my current code (working)
let arr = [];
itemsFetched.forEach((item) => {
itemsList.forEach((item2) => {
if (item.item_id === item2.id) {
arr.push({
"id": item.item_id,
"name": item2.name,
"count": item.count,
"img": item2.img
});
}
});
});
PS: I'm able to use ES6/7 syntax/features.
You can use hash map to reduce Time complexitly, your algorithm is O(m*n), The follow is O(m+n+r)
const itemsMap = itemsList.reduce((map, item) => {
map[item.id] = item
return map
}, {})
const results = itemsFetched
.filter((item) => itemsMap.hasOwnProperty(item.item_id))
.map((item) => ({
id: item.item_id,
name: itemsMap[item.item_id].name,
count: item.count,
img: itemsMap[item.item_id].img,
}))
Use a for ... of loop (an ES6 feature) in conjunction with Array#map.
This makes it much easier to return the merged object the first time you find a match, which is a logically optimization because neither list should contain more than one entry with a given id.
const result = itemsFetched.map(data => {
for (let item of itemsList) {
if (data.item_id === item.id) {
return {
id: item.id,
name: item.name,
count: data.count,
img: item.img
}
}
}
})
Snippet:
const itemsList = [{
"id": 0,
"name": "Egg",
"img": "http://www.serebii.net/pokemongo/items/egg.png"
}, {
"id": 1,
"name": "Pokeball",
"img": "http://www.serebii.net/pokemongo/items/20pokeballs.png"
}, {
"id": 2,
"name": "Greatball",
"img": "http://www.serebii.net/pokemongo/items/greatball.png"
}, {
"id": 401,
"name": "Incense",
"img": "http://www.serebii.net/pokemongo/items/incense.png"
}, {
"id": 901,
"name": "Incubator (Unlimited)",
"img": "http://www.serebii.net/pokemongo/items/eggincubator.png"
}]
const itemsFetched = [{
"item_id": 1,
"count": 50,
"unseen": true
}, {
"item_id": 401,
"count": 2,
"unseen": true
}, {
"item_id": 901,
"count": 1,
"unseen": true
}]
const result = itemsFetched.map(data => {
for (let item of itemsList) {
if (data.item_id === item.id) {
return {
id: item.id,
name: item.name,
count: data.count,
img: item.img
}
}
}
})
console.log(result)
One way to improve is to use for..of statement instead of forEach for the inner loop. This helps break from the loop once the id matches. There is no direct way to break from forEach method.
let arr = [];
itemsFetched.forEach((item) => {
for (let item2 of itemsList) {
if (itemsFetched.item_id === itemsList.id) {
arr.push({
"id": itemsFetched.item_id,
"name": itemsList.name,
"count": itemsFetched.count,
"img": itemsList.img
});
break;
}
}
});
Like this?
var itemsList = [
{
"id": 0,
"name": "Egg",
"img": "http://www.serebii.net/pokemongo/items/egg.png"
},
{
"id": 1,
"name": "Pokeball",
"img": "http://www.serebii.net/pokemongo/items/20pokeballs.png"
},
{
"id": 2,
"name": "Greatball",
"img": "http://www.serebii.net/pokemongo/items/greatball.png"
},
{
"id": 401,
"name": "Incense",
"img": "http://www.serebii.net/pokemongo/items/incense.png"
},
{
"id": 901,
"name": "Incubator (Unlimited)",
"img": "http://www.serebii.net/pokemongo/items/eggincubator.png"
}
];
var itemsFetched = [
{
"item_id": 1,
"count": 50,
"unseen": true
},
{
"item_id": 401,
"count": 2,
"unseen": true
},
{
"item_id": 901,
"count": 1,
"unseen": true
}
]
let arr = [];
itemsFetched.forEach((item) => {
itemsList.forEach((item2) => {
if (item.item_id == item2.id) {
arr.push({
"id": item.item_id,
"name": item2.name,
"count": item.count,
"img": item2.img
});
}
});
});
console.log(arr);

Categories

Resources