JavaScript merge (AJAX) array objects with the same keys - javascript

My application is MVC 5, I am using the following Ajax to generate an array:
$.ajax({
type: "Post",
url: '#Url.Action("Getthereport", "Surveys")',
async: false,
cache: false,
dataType: "json",
data: { 'test': "All" },
success: function (result) {
if (result && result.Grid.length > 0) {
for (let i = 0; i < result.Grid.length; i++) {
jsonData.push({
Question: result.Grid[i].Body,
QuestionId: result.Grid[i].QuestionId,
myData: { name: result.Grid[i].name, value: result.Grid[i].value }
});
};
}
},
complete: function () {
reduce();
},
error: function(err) {
alert(err.status + " : " + err.statusText);
}
});
I generates the following:
var jsonData = [
{
Question: "Was the training useful?",
QuestionId: 1,
myData: [{ name: 'No', value: 1 }] },
{
Question: "Was the training useful?",
QuestionId: 1 ,
myData: [{ name: 'Yes', value: 1 }]
}];
to merge the objects, I use:
const result = Object.values(jsonData.reduce((acc, obj) => {
if (!acc[obj.QuestionId]) {
acc[obj.QuestionId] = obj;
} else {
acc[obj.QuestionId].myData = acc[obj.QuestionId].myData.concat(obj.myData);
}
return acc;
Works great if the array is hardcoded and generates:
var jsonData = [
{
Question: "Was the training useful?",
QuestionId: 1,
myData: [{ name: 'No', value: 1 },
{ name: 'Yes', value: 1 }]
}];
However, if the array is generated by Ajax call, I get the following error:
acc[obj.QuestionId].myData.concat is not a function
I tried to run the reduce script on Ajax complete and directly both did not work.

In the success property of your ajax call options you're pushing myData as an object, not as an array
success: function (result) {
if (result && result.Grid.length > 0) {
for (let i = 0; i < result.Grid.length; i++) {
jsonData.push({
Question: result.Grid[i].Body,
QuestionId: result.Grid[i].QuestionId,
myData: { name: result.Grid[i].name, value: result.Grid[i].value }
});
};
}
},
Which means that the output is not as you stated but rather
var jsonData = [
{
Question: "Was the training useful?",
QuestionId: 1,
myData: { name: 'No', value: 1 } //<-- Objects not arrays
},
{
Question: "Was the training useful?",
QuestionId: 1,
myData: { name: 'Yes', value: 1 }
}
];
You can either declare it as an array at that stage to generate the output you originally posted,
for (let i = 0; i < result.Grid.length; i++) {
jsonData.push({
...
myData: [{ name: result.Grid[i].name, value: result.Grid[i].value }]
});
};
or adjust your reduce.
var jsonData = [
{
Question: "Was the training useful?",
QuestionId: 1,
myData: { name: 'No', value: 1 }
},
{
Question: "Was the training useful?",
QuestionId: 1,
myData: { name: 'Yes', value: 1 }
}
];
const result = Object.values(jsonData.reduce((acc, obj) => {
acc[obj.QuestionId] ??= { ...obj, myData: [] };
acc[obj.QuestionId].myData.push(obj.myData);
return acc;
}, {}));
console.log(JSON.stringify(result, null, 2))

Related

How to construct an array of object to unique array of object

I have an array of object like this
let data =
[
{
text: 'label'
},
{
text: 'username'
},
{
text: 'category'
},
{
text: 'book'
},
{
text: 'john'
},
{
text: 'education'
},
{
text: 'car'
},
{
text: 'doe'
},
{
text: 'automotive'
},
{
text: 'shoes'
},
{
text: 'cena'
},
{
text: 'fashion'
},
]
and my expect array of objects
let result =
[
{
label: 'book',
username: 'john',
category: 'education'
},
{
label: 'car',
username: 'doe',
category: 'automotive'
},
{
label: 'shoes',
username: 'cena',
category: 'fashion'
},
]
Just a simple for loop is probably the clearest. Here storing each object in a temp variable to avoid having to access the end of the result array every iteration, and abstracting the size into a variable.
let data = [{ text: 'label' }, { text: 'username' }, { text: 'category' }, { text: 'book' }, { text: 'john' }, { text: 'education' }, { text: 'car' }, { text: 'doe' }, { text: 'automotive' }, { text: 'shoes' }, { text: 'cena' }, { text: 'fashion' },];
const size = 3;
const result = [];
for (let temp, i = size; i < data.length; i++) {
if (i % size === 0) {
result.push(temp = {});
}
temp[data[i % size].text] = data[i].text;
}
console.log(result)
How about a switch-case with a modulo % operator to check for the current key:
const transformData = (data) => {
let result = [];
let tmpObj = {};
data.forEach((element, idx) => {
switch (idx % 3) {
case 0:
tmpObj["label"] = element.text;
break;
case 1:
tmpObj["username"] = element.text;
break;
case 2:
result.push({ ...tmpObj,
category: element.text
});
tmpObj = {};
break;
default:
break;
}
});
return result;
};
console.log(transformData(getSampleData()));
function getSampleData() {
return [{
text: 'label'
},
{
text: 'username'
},
{
text: 'category'
},
{
text: 'book'
},
{
text: 'john'
},
{
text: 'education'
},
{
text: 'car'
},
{
text: 'doe'
},
{
text: 'automotive'
},
{
text: 'shoes'
},
{
text: 'cena'
},
{
text: 'fashion'
},
];
}
According to your data,the top 3 records are property name,others are data,so we can use Array.slice() to get the property names
Then we can use Array.reduce() to convert the left data
let keys = data.slice(0,3).map(v => v.text)
let result = data.slice(3).reduce((a,c,i) =>{
let key = keys[i%3]
if(i%keys.length ==0){
let obj = {}
obj[key] = c.text
a.push(obj)
}else{
a.at(-1)[key]=c.text
}
return a
},[])
console.log(result)
let data =
[
{
text: 'label'
},
{
text: 'username'
},
{
text: 'category'
},
{
text: 'book'
},
{
text: 'john'
},
{
text: 'education'
},
{
text: 'car'
},
{
text: 'doe'
},
{
text: 'automotive'
},
{
text: 'shoes'
},
{
text: 'cena'
},
{
text: 'fashion'
},
]
let keys = Object.values(data.slice(0,3)).map(v => v.text)
let result = data.slice(3).reduce((a,c,i) =>{
let key = keys[i%3]
if(i%keys.length ==0){
let obj = {}
obj[key] = c.text
a.push(obj)
}else{
a.at(-1)[key]=c.text
}
return a
},[])
console.log(result)

Array data is getting replaced inside forEach nested with map in JavaScript/TypeScript

Why 1st iteration data is getting replaced in 2nd iteration?
Is there any other simpler method in ES6 to achieve this?
a = [
{ name: 'NameOne', weekName: 'WeekOne' },
{ name: 'NameTwo', weekName: 'WeekTwo' },
];
b = [
{ id: 'Name', type: 'text', data: '' },
{ id: 'Week', type: 'text', data: '' },
];
c = [];
showOutput() {
this.a.forEach((element) => {
this.b.map((item) => {
if (item.id == 'Name') {
item.data = element.name;
}
if (item.id == 'Week') {
item.data = element.weekName;
}
this.c.push(item);
console.log('c', this.c);
});
});
}
Current Output :
[{ id: 'Name', type: 'text', data: 'NameTwo' },
{ id: 'Week', type: 'text', data: 'WeekTwo' },
{ id: 'Name', type: 'text', data: 'NameTwo' },
{ id: 'Week', type: 'text', data: 'WeekTwo' }]
Desired Output:
[{ id: 'Name', type: 'text', data: 'NameOne' },
{ id: 'Week', type: 'text', data: 'WeekOne' },
{ id: 'Name', type: 'text', data: 'NameTwo' },
{ id: 'Week', type: 'text', data: 'WeekTwo' }]
Problem with your code is that this.c.push(item); here the same object is getting referenced so in 2nd iteration it's changing the data that modified by 1st iteration. In order to solve this, you will have to clone the object (dereference somehow)
I have used c.push(Object.assign({}, item)); or you can use c.push(JSON.parse(JSON.stringify(item))); or any other way to clone the object before pushing into array (c in your case)
Note: This is just to point out the root cause of the issue, and it may not be the perfect solution for your scenario.
e.g.
a = [
{ name: 'NameOne', weekName: 'WeekOne' },
{ name: 'NameTwo', weekName: 'WeekTwo' },
];
b = [
{ id: 'Name', type: 'text', data: '' },
{ id: 'Week', type: 'text', data: '' },
];
c = [];
function showOutput() {
a.forEach((element) => {
b.map((item) => {
if (item.id == 'Name') {
item.data = element.name;
}
if (item.id == 'Week') {
item.data = element.weekName;
}
c.push(Object.assign({}, item)); // clone object
});
});
}
showOutput();
console.log('c', c);
For more information: https://javascript.info/object-copy

Javascript Alphabetised Object Key Sorting

I'm wondering, I have the following data structure:
data = [
{
name: 'Alpha',
},
{
name: 'Alfa',
},
{
name: 'Bravo',
},
{
name: 'Brafo',
},
{
name: 'Charlie',
},
{
name: 'Charly',
},
...
{
name: 'Zulu',
},
{
name: 'Zulo',
},
]
I'm expecting there to be at least one, usually more, key for each letter of the alphabet. However, if there isn't a single data.name I would still like in the below data structure to have an empty domains array [].
I was wondering, how could this be manipulated into the following data structure:
data = {
a: {
domains: [
{
name: 'Alpha',
},
{
name: 'Alfa',
},
],
},
b: {
domains: [
...
]
},
...
z: {
domains: [
...
]
},
};
I have used a few methods, which involved a pre-constructed "alphbetised" key = object array, then filtered each on the first letter of the data.name value...but I was wondering if there was a standard and performant method to acheive this?
Using reduce()
const data = [{name:"Alpha"},{name:"Alfa"},{name:"Bravo"},{name:"Brafo"},{name:"Charlie"},{name:"Charly"},{name:"Zulu"},{name:"Zulo"}]
const res = data.reduce((a, v) => {
// prepare key
let key = v.name.substring(0,1).toLowerCase()
// check key in accumulator
if (!a[key]) {
// assign domain object
a[key] = {domains: []}
}
// push domain array
a[key].domains.push(v)
return a
}, {})
console.log(res)
Here is what you want:
data = [
{
name: 'Alpha',
},
{
name: 'Alfa',
},
{
name: 'Bravo',
},
{
name: 'Brafo',
},
{
name: 'Charlie',
},
{
name: 'Charly',
},
{
name: 'Zulu',
},
{
name: 'Zulo',
},
];
console.log(data.reduce((a, c) => {
const firstLetter = c.name[0].toLowerCase();
if (a[firstLetter]) {
a[firstLetter].domains.push(c);
} else {
a[firstLetter] = { domains: [c] };
}
return a;
}, {}));

Object.assign make duplicate when post a request

I create a ticket using zendesk but I didn't know why this happens
here is node js code:
config.js
baseTicketObject: {
'comment': {
'body': null,
},
'requester': {
'name': null,
'email': null,
},
'custom_fields': [],
},
create ticket api
function createTicketObjectFromRequest(req) {
const requestBody = req.body;
console.log('requestBody', requestBody);
console.log('config.baseTicketObject', config.baseTicketObject);
const ticket = Object.assign(config.baseTicketObject, {});
//console.log('ticket', ticket);
const {
messageBody, email, name, customFields,
} = requestBody;
//console.log('ticket.custom_fields', ticket.custom_fields);
// Request must contain a name, email and body
ticket.requester.name = name;
ticket.requester.email = email;
ticket.comment.body = messageBody;
if (req.user && req.user.id) {
ticket.custom_fields.push(createCustomFieldObject(config.customFieldNameToZendeskFieldIdMapping['userId'], Number(req.user.id)));
}
Object.keys(config.customFieldNameToZendeskFieldIdMapping).forEach((fieldName) => {
if (config.customFieldNameToZendeskFieldIdMapping[fieldName] === config.customFieldNameToZendeskFieldIdMapping.userId) {
return;
}
//console.log('fieldName', fieldName);
const mappedCustomFieldId = config.customFieldNameToZendeskFieldIdMapping[fieldName];
if (mappedCustomFieldId) {
ticket.custom_fields.push(createCustomFieldObject(mappedCustomFieldId, !customFields[fieldName] ? '' : customFields[fieldName]));
}
});
return { ticket: ticket };
}
whenever I post a request the config.baseTicketObject will keep all items i pushed before like this
config.baseTicketObject { comment: { body: null },
requester: { name: null, email: null },
custom_fields: [] }
-------------------------------------
config.baseTicketObject { comment: { body: 'dgfhdgfhdgfh dgfhdfghdfg' },
requester: { name: 'test other', email: 'tranthiphuonghue96#yopmail.com' },
custom_fields:
[ { id: 360010481051, value: '' },
{ id: 360010510411, value: '' },
{ id: 360010406792, value: '' },
{ id: 360010511011, value: '' },
{ id: 360010511191, value: '' },
{ id: 360010920852, value: 'contact_support' } ] }
---------------------------------------------------------
config.baseTicketObject { comment: { body: 'dgfhdgfhdgfh dgfhdfghdfg' },
requester: { name: 'test other', email: 'tranthiphuonghue96#yopmail.com' },
custom_fields:
[ { id: 360010481051, value: '' },
{ id: 360010510411, value: '' },
{ id: 360010406792, value: '' },
{ id: 360010511011, value: '' },
{ id: 360010511191, value: '' },
{ id: 360010920852, value: 'contact_support' },
{ id: 360010481051, value: '' },
{ id: 360010510411, value: '' },
{ id: 360010406792, value: '' },
{ id: 360010511011, value: '' },
{ id: 360010511191, value: '' },
{ id: 360010920852, value: 'contact_support' } ] }
I don't know why the config.baseTicketObject like that, please help.
Reverse parameters order in Object.assing.
You have
Object.assign(config.baseTicketObject, {});
but should be
Object.assign({}, config.baseTicketObject);
Object.assign syntax
Object.assign(target, ...sources)
In your case
const ticket = Object.assign({}, config.baseTicketObject);
Edit:
Add
ticket.custom_fields = [];
after
const ticket = Object.assign({}, config.baseTicketObject);
because Object.assign create shallow copy, witch mean that ticket.custom_fields still holds reference to original array object from config.baseTicketObject.custom_fields

Javascript - How to mix two arrays of objects with specific sorting order?

I have two array of objects. Something like this:
var arrayA = [
{
type: 'card',
id: 1
},
{
type: 'card',
id: 2
},
{
type: 'card',
id: 3
},
{
type: 'card',
id: 4
},
{
type: 'card',
id: 5
},
];
var arrayB = [
{
type: 'pro-tip',
id: 10
},
{
type: 'pro-tip',
id: 11
},
];
I want to merge these two arrays of objects, but on a specific order. Basically, what I want is that, after each N number of elements from arrayA, I want to add one element from arrayB. If N == 2 The final array would look like this:
var finalArray = [
{
type: 'card',
id: 1
},
{
type: 'card',
id: 2
},
{
type: 'pro-tip',
id: 10
},
{
type: 'card',
id: 3
},
{
type: 'card',
id: 4
},
{
type: 'pro-tip',
id: 11
},
{
type: 'card',
id: 5
},
];
Probably it is not difficult to do something like this, but I'm looking for the most elegant way to build a helper function to do that.
Edit:
Here's the function I created. It seems like it works, but there might be a simpler way to do it:
function mergeWithSteps( arrayA, arrayB, nSteps, nElementsToAdd ) {
var finalArray = [],
stepsCount = 0,
elementsToAddCount = 0,
arrayBNumberOfElements = arrayB.length;
arrayA.forEach( function( obj ) {
finalArray.push( obj );
stepsCount++;
if( stepsCount == nSteps && elementsToAddCount < arrayBNumberOfElements ) {
finalArray.push( arrayB[ elementsToAddCount ] );
elementsToAddCount++;
stepsCount = 0;
}
} );
return finalArray;
}
You could use Array#slice for inserting the wanted item and iterate arrayB from the end, because every splicing changes the indices after the insertation index.
var arrayA = [{ type: 'card', id: 1 }, { type: 'card', id: 2 }, { type: 'card', id: 3 }, { type: 'card', id: 4 }, { type: 'card', id: 5 }],
arrayB = [{ type: 'pro-tip', id: 10 }, { type: 'pro-tip', id: 11 }],
place = 2,
i = arrayB.length;
while (i) {
arrayA.splice(i * place, 0, arrayB[--i]);
}
console.log(arrayA);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's my iterative solution using for
countA = arrayA.length;
countB = arrayB.length;
merged = [];
for (var i = 0, j = 0, m = 0; i < countA; i ++, m ++) {
if (i > 0 && i%2 == 0 && typeof arrayB[j] !== 'undefined') {
merged[m] = arrayB[j];
j ++;
m ++;
}
merged[m] = arrayA[i];
}
// if you'd like to place all remaining arrayB elements after arrayA is exhausted
if (countB > j) {
for (i = j; i < countB; i++, m ++) {
merged[m] = arrayB[i];
}
}
You can use reduce if the initial array is not to modify.
const arrayA = [
{
type: 'card',
id: 1
},
{
type: 'card',
id: 2
},
{
type: 'card',
id: 3
},
{
type: 'card',
id: 4
},
{
type: 'card',
id: 5
},
];
const arrayB = [
{
type: 'pro-tip',
id: 10
},
{
type: 'pro-tip',
id: 11
},
];
const res = arrayA.reduce((acc, a, i) => {
const b = arrayB[(i + 1) / 2 - 1];
return [...acc, a, ...(b ? [b] : [])];
}, []);
console.log(res);

Categories