How to reload table from ajax response when the json comes in varied sizes? INVALID JSON on reload()

How to reload table from ajax response when the json comes in varied sizes? INVALID JSON on reload()

jamcnorcjamcnorc Posts: 8Questions: 1Answers: 0
edited December 2022 in Free community support

Hi,
New here so please let me know if I need to update some things. First a bit of context, I am a front end dev and do not have a lot of control over the back end responses. (It can be done, but I'd like to handle this if possible). I am using the datatables plugin inside of a jsp page.

When I submit my table after a user edits it, an ajax call executes a stored procedure which returns the updated data in a json format. This format comes in a different order, and not all of the fields are consistent.

For example, if a record has an error, the corresponding json record will contain the field "error_desc". If a record does not have an error, then the field "error_desc" is omitted from the response.

I have a fiddle here, but my use case relies on on prem servers and is embedded in a complicated application using both struts 2 and dhtmlx. I'm not quite sure how to emulate this issue without going down a separate rabbit hole. This page at least shows how my original table is constructed, and then below you can see my response json object: https://jsfiddle.net/rzL4sja8/
Currently I populate the table from the database, allow the user to edit, the stored procedure runs and returns a json with the updated values, but when I try to call dt.ajax.reload(); I see the dreaded DataTables warning: table id=staged - Invalid JSON response.

I followed the steps from here: https://datatables.net/manual/tech-notes/1#:~:text=When%20DataTables%20shows%20the%20message,back%20was%20not%20valid%20JSON

My json is valid according to json.parser.online.fr but I like I said above I can have a different number of fields depending on the results of the stored procedure and I believe this is why DataTables rejects the response.

Here is an example of the json format, please note that the first record has 5 fields, while the rest have only 4:

'''

[
{
"comment":"Testing delete functionality on the first line1",
"su_id":"91200001",
"error":true,
"field_disp":"101a",
"error_desc":"invalid field_disp"
},
{
"comment":"CMS records indicate that the Respondent passed away on 10/01/2022. Please discuss this case with your FM if necessary.2",
"su_id":"88100002",
"error":false,
"field_disp":"72"
},
{
"comment":"Testing delete functionality a second time3",
"su_id":"91200003",
"error":false,
"field_disp":"103"
},
{
"comment":"this is the last comment4",
"su_id":"88100004",
"error":false,
"field_disp":"34"
}
]

'''

Here are a few other relevant javascript functions which are located on different pages in the app, but are relevant to this stored procedure. I've added a couple of descriptive comments, and you can see the commented out attempts I have made to get this working as well.

'''

function validateStaging(dt){
showLoading();
var params = dt.$('input, select').serialize() +
"&dispatch=validateRocStaging";

    new Ajax.Request("rocLoad.do", {
        method: 'post',
        parameters: params,
        success: function() {
  },
        onFailure: function (transport) {
            onAjaxError();
            alert("Validation call failed!");
        },
        onComplete: function (transport) {   
                    var output = transport.responseText;
        var data = JSON.parse(output); //this print to console the data I need nicely, 
                   albeit in the wrong order
        console.log(data);


        //dt.clear();
        //dt.ajax.reload(output).draw();
        //console.log('afterajaxoutput');
        dt.ajax.reload();
        //console.log('afterajaxplain');
        //dt.ajax.reload(data);
        //console.log('afterajaxdata');
        //dt.rows().add(JSON.parse(data[0])).draw();
        //dt.columns.adjust().draw();
        //dt.rows.add(data).draw();

        //dt.rows().remove().draw();
        //dt.rows.add(output).draw();
        //console.log(output);
        //output used to repaint table
        //stageTable.columns.adjust().draw();
        //stageTable.ajax.reload();

        //reload works here, commenting to debug return capture
        //location.reload(true);    
        } });}


public ActionForward validateRocStaging(ActionMapping mapping, ActionForm form,
        HttpServletRequest request, HttpServletResponse response)
        throws Exception {
    try {           
        RocACPService service = new RocACPService();

        updateStaging(mapping, form, request, response); //save data to db table

        List<Map<String,Object>> datalist = service.validateROC(); // capture values from stored procedure
        request.setAttribute("dataList", datalist);

        String dataString = new String();
        Gson gson = new Gson();
        dataString = gson.toJson(datalist); //convert stored procedure result to json and write to response object

        write(response, dataString);


    }catch(Exception e) {               
        log.error(e);
        return null;
    }            
    return null; //     
}

'''

Thank you for reading.

Answers

  • colincolin Posts: 15,237Questions: 1Answers: 2,598

    One option would be to either remove that error_desc field (or add it into all records) within the ajax.dataSrc. The final example on that reference page shows how you can manipulate the data sent from the server before loading it into the table,

    Colin

  • kthorngrenkthorngren Posts: 21,166Questions: 26Answers: 4,921
    edited December 2022

    Your problem description is confusing. Your fiddle doesn't have the ajax.option and it looks like you use a loop to populate the HTML table. Its unclear how you would get the Invalid JSON response error as that comes from using ajax.

    It doesn't look like you have columns.data configured which is used to define the columns when using object structured data when using Ajax or Javascript loaded data. If you are using columns.data you can use columns.defaultContent to set the column value for the error_desc column when that object is missing from the data.

    Usually you will get the Requested unknown parameter error when data is missing from the rows and columns.defaultContent is not configured. I don't think this would trigger the Invalid JSON error. Are you checking the JSON of XHR response that caused the error?

    Can you post a link to your page or a running test case replicating the issue so we can help debug?
    https://datatables.net/manual/tech-notes/10#How-to-provide-a-test-case

    Kevin

  • kthorngrenkthorngren Posts: 21,166Questions: 26Answers: 4,921

    It looks like you are using ajax.relaod() in your Ajax.Request(). You need to use ajax for ajax.relaod() to work. Since it looks like you aren't using ajax then this might be resulting in the Invalid JSON error.

    Maybe you need to use clear() followed by rows.add() instead. Use columns.defautlContent in columnsDefs for the error_desc column to handle the case when this is missing.

    Kevin

  • jamcnorcjamcnorc Posts: 8Questions: 1Answers: 0

    @colin @kthorngren
    Thank you both for the replies. I read the docs more and see what you are saying about using the ajax option in order for ajax.reload to work.

    I guess I'm a little confused about whether using the ajax api is appropriate or not. I can't publish a running test case without escalating this, so I'd like to explain how the application is set up and see if we can talk through this more before going there.

    Here is how the app is setup.

    A menu click to open the page triggers a Js method (showLoad) that has an ajax call which uses a:
    (java Action) that calls a service to get table data and attaches the data to the request using
    (java service) returns query result from database
    Jsp page has loop which uses data in request object to populate an html table

    A DataTables constructor is called on that html table.

    Later the datatable buttons trigger js ajax methods in the same class as the showLoad method. These ajax methods call the java actions, which use the java services.

    '''

            {
                text: 'Validate',
                action: function (e, dt, node, config) {
    
                    validateStaging(dt); 
                }
            },
    

    '''

    These methods are all passed dt as a parameter, and the serialized data is sent to the backend like this:

    (in the js ajax method in separate class)

    '''

    var params = dt.$('input, select').serialize() + "&dispatch=validatePreLoadStaging";

    '''

    All of that is working well, but I'm trying to avoid calling showLoad() again after any stored procedure is run and just capture the return data instead. Currently calling showLoad over and over works, but it is clunky and not following best practices.

    I'm not clear if I should be using ajax here since it seems like the way this app is set up the ajax calls are a step removed so to speak, and since everything works well with the html loop method.

    Here is the java action to perform this 'validation' from the above serialization, and how I am currently capturing the response.

    The aim here is to capture the return from the stored procedure as dataString like this:

    '''

    List<Map<String,Object>> datalist = service.validateROC(user);
    request.setAttribute("dataList", datalist);

    String dataString = new String();
    Gson gson = new Gson();
    dataString = gson.toJson(datalist);

    write(response, dataString);

    '''

    And then in onComplete for the ajax method I'd like to use the dataString in the response to redraw the table:
    (This is my latest attempt, but using ajax.reload() would be ok if that is better)

    '''

    var output = transport.responseText;
    var data = JSON.parse(output);
    dt.clear();
    dt.rows.add(data).draw().nodes();

    '''

    I'd like to know your thoughts here, I'm hoping to find a way to do this without having to reinvent the wheel on this one.

    Thanks!

  • kthorngrenkthorngren Posts: 21,166Questions: 26Answers: 4,921

    Not sure I totally follow. if the first initialization use the DOM table as the data source and you want to change to using JSON data for subsequent loads then you will need to destroy and reinitialize Datatables.

    See this example:
    http://live.datatables.net/rexokaju/1/edit

    It starts with an HTML sourced table. Reloading the data will use destroy() to allow for reconfiguring the Datatables options. Since the error_desc doesn't exist in each row we use columns.defaultContent to display something when the object is not there. In this case an empty string.

    HTH,
    Kevin

  • jamcnorcjamcnorc Posts: 8Questions: 1Answers: 0

    Thanks Kevin.

    I talked through the issue more and it looks like my team wants me to use .clear() and rows.add() as you mentioned above. I am successfully clearing the table and passing the new data to rows.add(), however the api is interpreting the data one character per cell, inserting the entire set into the first column only.
    I replaced my data and headers with dummy data from the documents and am having the same issue.

    dt.rows.add(testJSON).draw();
    It throws the same invalid json error and looks like this:

    These other methods result in a blank data table or an error:

    dt.rows.add(JSON.parse(testJSON)).draw();
    dt.rows.add($.parseJSON(testJSON)).draw()

    using a loop to access each record at a time like this
    dt.row.add(JSON.parse(testJSON)[i]).

    Here is how I initialize the table:

    '''

     var stageTable;
    stageTable = $jq(document).ready(function () {
    
        $jq('#staged').DataTable(
                {
                    "initComplete": function (settings, json) {                     
                        selectErrors(settings, json);           
    
                    },          
                           scrollY:'65vh',
                           "scrollCollapse": true,
                    scrollX: true,
                    scroller: true,
                            pageLength: 50,     
                    fixedColumns: true,
                    autoWidth: false,
                    dom: 'Bl<"toolbar">rtip',
                    search: false,
                    responsive: true,
                    "bSort": false, //true will show sort icons         
            columnDefs: [           
            { className: "dt-center", targets: [ 0, 1, 2, 3 ] },
            {  width: '200%', targets: 3 }
        ],  
                select: {
            style:    'os',
            selector: 'td:first-child'
        },
                    buttons: [                      
                                                {
                            text: 'Upload ROC File',
                            action: function (e, stageTable, node, config) {
                                setupRocUpload();   
                            }
                        },
    
                        {
                            text: 'Validate',
                            action: function (e, dt, node, config) {
    
                                validateStaging(dt); 
    
                            }
                        },
    
                        {
                        text: 'Remove and Export Selected to CSV',
                         action: function(e, dt, node, config){
    
                             exportErrors(dt);                   
                         }
                         }, 
    
                         {
                            text: 'Submit to Database',
                            action: function (e, dt, node, config) {
    
                                submit(dt);
                            }
                        },
                        {
                            text: 'Cancel',
                            action: function (e, dt, node, config) {
                                clearStaging();
                                removeAll(dt);
                            },
    
                        }                       
                    ]                       
            }  
        );  
    

    '''

    Any idea why this might not be able to parse json data correctly?

  • kthorngrenkthorngren Posts: 21,166Questions: 26Answers: 4,921

    rows.add() expects a Javascript array of rows. The first step is to determine what data type testJSON is. If its a string then use JSON.parse(testJSON) to turn it into a Javascript object. Then check again to see what it is. If its a string then you are double encapsulating it as JSON in your server. But it should be a Javascript array of rows. See the Data docs for details.

    If you still need help then please post the testJSON value or the XHR response using the browser's network inspector.

    Kevin

  • jamcnorcjamcnorc Posts: 8Questions: 1Answers: 0

    Thanks again Kevin

    Here are the two versions of testJSON I tried the above with:

    '''

    var testJson = '[{"su_id":"88100004", "field_disp":"34", "error_desc": "bla", "comment":"blahthis is the last comment4" }, {"su_id":"91200003", "field_disp":"103", "error_desc":" false", "comment":"Testing delete functionality a second time3"}]';

    var testJSON= '[{"su_id":"0.00","field_disp":"15.00","error_desc":"0.00","comment":"12"},{"su_id":"0.00","field_disp":"15.00","error_desc":"0.00","comment":"12"}]';

    '''

  • kthorngrenkthorngren Posts: 21,166Questions: 26Answers: 4,921
    edited December 2022

    You have single quotes around the array, ie '[...]'. This is a string not an array. Remove the single quotes, something like this:

    var testJSON= [{"su_id":"0.00","field_disp":"15.00","error_desc":"0.00","comment":"12"},{"su_id":"0.00","field_disp":"15.00","error_desc":"0.00","comment":"12"}];
    
    dt.rows.add(testJSON).draw();
    

    BTW, use triple backticks (```) for the markdown code formatting. Not single quotes.

    Kevin

  • jamcnorcjamcnorc Posts: 8Questions: 1Answers: 0
    edited December 2022

    Hmmm I thought that would be it. Unfortunately,
    When I try this I am getting this error and then a blank table

    "DataTables warning: table id=staged - Requested unknown parameter '0' for row 0, column 0. For more information about this error, please see http://datatables.net/tn/4"

    Thanks for the tip on the backticks

  • kthorngrenkthorngren Posts: 21,166Questions: 26Answers: 4,921

    Did you follow the troubleshooting steps at the link provided?
    http://datatables.net/tn/4

    Requested unknown parameter '0'

    Indicates that Datatables is configured for each row to be an array of data. But you have objects. You need to use columns.data to define your columns. Please see the Data Docs for details.

    Kevin

  • jamcnorcjamcnorc Posts: 8Questions: 1Answers: 0

    Yes I had followed those steps, you are correct about the array of objects vs array of arrays. I was able to convert directly and load the table like this:


    const testJson = [{"su_id":"88100004", "field_disp":"34", "error_desc": "bla", "comment":"blahthis is the last comment4" }, {"su_id":"91200010", "field_disp":"103", "error_desc":" false", "comment":"Testing delete functionality a second time3"}]; var ready = testJson.map(Object.values); dt.clear(); //clear table dt.rows.add(ready).draw();

    Thanks for the help again!

  • jamcnorcjamcnorc Posts: 8Questions: 1Answers: 0

    Progress but still issues, three things I am still stuck on.

    When I use the output object directly, the table fails to reload. However, if I copy paste output exactly as printed to console, (that is what testJson is) the same data populates the table just fine.

    When it does populate the table, the data is in a different order after serialization so the data is not going into the correct columns, and the table loses it's editable text boxes upon reload. Here is the code as well as a screenshot of the changes to the table:

                var output = transport.responseText;
    
                //const testJson = [{"su_id":"88100004", "field_disp":"34", "error_desc": "bla",  "comment":"blahthis is the last comment4" }, {"su_id":"91200003", "field_disp":"103", "error_desc":" false", "comment":"Testing delete functionality a second time3"}];
                
                const testJson = [{"comment":"Testing delete functionality on the first line1","su_id":"91200001","field_disp":"101a","error_desc":"invalid field_disp"},{"comment":"CMS records indicate that the Respondent passed away on 10/01/2022. Please discuss this case with your FM if necessary.2","su_id":"88100002","field_disp":"72a","error_desc":"invalid field_disp"},{"comment":"Testing delete functionality a second time3","su_id":"91200003","field_disp":"103a","error_desc":"invalid field_disp"},{"comment":"this is the last comment4","su_id":"88100004","field_disp":"34a","error_desc":"invalid field_disp"}];
                
                var ready = testJson.map(Object.values);
                console.log(output);
                dt.clear(); //clear table
                dt.rows.add(ready).draw();
    

    Thanks for all the help with this, I really appreciate it!

  • kthorngrenkthorngren Posts: 21,166Questions: 26Answers: 4,921

    When I use the output object directly, the table fails to reload.

    Where does the output object come from? What is the data type, ie, string or Javascript array?

    Use the browser's debugger or console.log(typeof myVariable); to find out the data type. Can you post an example of the output object?

    When it does populate the table, the data is in a different order after serialization so the data is not going into the correct columns

    Either use columns.data to define the columns for the object structured data or make sure the array you build is in the proper order. Using objects is much better as you access the data using the object's key instead of an array index.

    he table loses it's editable text boxes upon reload

    Looks like your initial table is directly placed into the DOM with the input elements, etc. You will need to use columns.render to build the same HTML when you use rows.add() to populate the table. My suggestion would be to use Datatables to populate the initial table. See this example showing how to render HTML elements.

    Kevin

  • jamcnorcjamcnorc Posts: 8Questions: 1Answers: 0
    edited December 2022

    output is created in the java action, the return from a stored procedure call:

                List<Map<String,Object>> datalist = service.validatePreLoad(); //sp call
                request.setAttribute("dataList", datalist);
                String dataString = new String();
                Gson gson = new Gson();
                dataString = gson.toJson(datalist);
                write(response, dataString); 
    // return is attached so it can be accessed as var output = transport.responseText in the ajax oncomplete
    

    typeof output comes back as a string, so I tried JSON.parse(output); which creates an object (testJson after copying from the console also shows as object when using typeof) But even using JSON.parse, the table does not reload.

    I appreciate you posting the example, I will add .data and .render to fix those issues.

Sign In or Register to comment.