0

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.

hncl
  • 2,295
  • 7
  • 63
  • 129
  • What is `acc`? Can you add `console.log(acc)` and `console.log(typeof acc)`? – jabaa Apr 13 '22 at 01:55
  • You're checking `!acc[obj.QuestionId]` and if it's falsy, you're calling `acc[obj.QuestionId].myData.concat(obj.myData)`. How could it work? – jabaa Apr 13 '22 at 01:58
  • @jabaa, console.log(acc) is not defined. Regarding your second comment, the script works if the array is hardcoded. – hncl Apr 13 '22 at 02:05
  • 1
    Yes, probably because in one case `!acc[obj.QuestionId]` returns `true` and in the other case it returns `false`. The `else` block causes the error. How is _"console.log(acc) is not defined"_ possible?. Where do you run the code? I assume in a modern browser? Please provide a [mcve]. – jabaa Apr 13 '22 at 02:07
  • 1
    I see two possible reasons. Either [How to return the response from an asynchronous call](https://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call) or in one case you have a JSON string and in the other a JavaScript object. In your code snippet in the question `jsonData` doesn't contain JSON data, but a response contains JSON data. [What is the difference between JSON and Object Literal Notation?](https://stackoverflow.com/questions/2904131/what-is-the-difference-between-json-and-object-literal-notation) This is confusing for many people. – jabaa Apr 13 '22 at 02:10
  • You have to parse JSON data with `JSON.parse` to convert it to a JavaScript object. – jabaa Apr 13 '22 at 02:13
  • Unrelated: I would avoid jQuery for AJAX calls (and for everything else) and use [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) instead. `$.ajax` is callback-based and `fetch` is promise-based. Promise-based functions are much more convinient. – jabaa Apr 13 '22 at 02:18
  • Actually it looks like maybe some objects don't have `myData` properties? – pilchard Apr 13 '22 at 09:04
  • @pilchard The error confused me. I wasn't able to think of a scenario that would cause this exact error message. Wouldn't your case cause something like `Cannot read property concat of undefined`? – jabaa Apr 13 '22 at 09:11
  • 1
    true, but to get to `myData.concat` at all the data has to be valid, it could be just another type than Array? (if it's any type that doesn't implement a `concat` method it throws that error. ie. boolean, number, object ) – pilchard Apr 13 '22 at 09:16
  • @hncl check the data you're receiving and verify that all the elements of the array have a `myData` property, and that the property is always an Array. If not you simply need to ensure that `myData` is properly declared as an array in the accumulated object. – pilchard Apr 13 '22 at 09:22
  • 1
    @hncl I just read your code and you're pushing an object with `myData` as an object in your `ajax` call `jsonData.push({Question: result.Grid[i].Body, QuestionId: result.Grid[i].QuestionId, myData: { name: result.Grid[i].name, value: result.Grid[i].value }})` either wrap it in an array, or change your reduce to handle it as an object. – pilchard Apr 13 '22 at 09:50

1 Answers1

1

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))
pilchard
  • 12,414
  • 5
  • 11
  • 23
  • thank you; I used myData: [{ name: result.Grid[i].name, value: result.Grid[i].value }]. It works great. – hncl Apr 13 '22 at 10:31