[SalesForce] JSON deserialize into wrapper class errors

I'm trying to set up integration to display quiz answers in a Visualforce page, but I'm having a lot of difficulty deserializing a JSON string (supplied via API call response) into a wrapper class which seems to validate perfectly when I run it through http://jsonlint.com/ or http://json2apexengine.somee.com/ . The JSON string is pretty massive so I'm hoping I can get help without having to supply the whole thing.

Errors I'm getting in particular include:

System.JSONException: Expected CanvasCourseController.event_data but
found [line:1, column:23954]

which I'm interpreting as there's some interruption to the expected json format

System.JSONException: Unexpected character ('f' (code 102)): was
expecting comma to separate OBJECT entries at [line:1, column:537]

which seems to be a result of html formatting in string values

System.JSONException: Illegal unquoted character ((CTRL-CHAR, code
10)): has to be escaped using backslash to be included in string value
at [line:1, column:507]

a line break issue I've resolved using string.replace('\n', ' ');

I created the wrapper class by running the json string through json2apexengine, it's as follows:

public class JsonParser{
        public list<quiz_submission_events> quiz_submission_events{get;set;}
    public class quiz_submission_events{
        public event_data event_data{get;set;}
        public String event_type{get;set;}
        public String created_at{get;set;}
        public String id{get;set;}
    }
    public class quiz_data{
        public String question_type{get;set;}
        public String question_name{get;set;}
        public String neutral_comments_html{get;set;}
        public String name{get;set;}
        public String incorrect_comments_html{get;set;}
        public String question_text{get;set;}
        public String correct_comments_html{get;set;}
        public list<answers> answers{get;set;}
        public String neutral_comments{get;set;}
        public String text_after_answers{get;set;}
        public String incorrect_comments{get;set;}
        public Integer assessment_question_id{get;set;}
        public String correct_comments{get;set;}
        public Integer position{get;set;}
        public Integer points_possible{get;set;}
        public String published_at{get;set;}
        public String regrade_option{get;set;}
        public Integer id{get;set;}
    }
    public class event_data{
        public list<quiz_data> quiz_data{get;set;}
        public Integer quiz_version{get;set;}
    }
    public class answers{
        public String answer_weight{get;set;}
        public String match_id{get;set;}
        public String answer_text{get;set;}
        public String id{get;set;}
        public String answer_match_left{get;set;}
        public String blank_id{get;set;}
        public String answer_match_right{get;set;}
        public String numerical_answer_type{get;set;}
        public String answer_comment{get;set;}
        public String answer_range_end{get;set;}
        public String answer_html{get;set;}
        public String answer_range_start{get;set;}
        public String answer_match_left_html{get;set;}
        public String answer_error_margin{get;set;}
        public String answer_comment_html{get;set;}
        public String answer_exact{get;set;}
    }
}

Here's an excerpt of the JSON:

{
    "quiz_submission_events": [
        {
            "id": "4629",
            "event_type": "submission_created",
            "event_data": {
                "quiz_version": 21,
                "quiz_data": [
                    {
                        "id": 220,
                        "regrade_option": "",
                        "points_possible": 0,
                        "correct_comments": "",
                        "incorrect_comments": "",
                        "neutral_comments": "",
                        "correct_comments_html": "",
                        "incorrect_comments_html": "",
                        "neutral_comments_html": "",
                        "question_type": "text_only_question",
                        "question_name": "Spacer",
                        "name": "Spacer",
                        "question_text": "<p><strong>Read the advertisement below and answer the questions that follow.</strong></p>\n<p> </p>\n<p><em><span style=\"font-size: 14pt;\"><strong>Grand Opening this Saturday 27<sup>th</sup> June!</strong></span></em></p>\n<p><strong>769 Asperity Road, Mt. Falls</strong></p>\n<p><strong>Gates Open 9:00am to 4:00pm</strong></p>\n<p> </p>\n<p><strong><img src=\"/courses/34/files/2325/preview\" alt=\"Koala.png\"><span style=\"font-size: 18pt;\">Salvation Animal Sanctuary</span></strong></p>\n<p><strong>                        Come to our opening and support the </strong><strong>rescue of native animals.</strong></p>\n<p><strong>                        Visit our Educational Display Centre, </strong><strong>Souvenir Shop and Café.</strong></p>\n<p><strong> </strong></p>\n<p><strong>The grand opening will have rides for children </strong><strong>and games to play.</strong></p>\n<p><strong> </strong></p>\n<p><strong>As bad weather can cause some of the  </strong><strong>animals to be reclusive, the Sanctuary staff will </strong><strong>be at many of the enclosures to bring the animals </strong><strong>closer and answer any questions.</strong></p>\n<p>                                                                                                                                                                                                                                       </p>\n<table style=\"height: 140px;\" width=\"328\"><tbody><tr>\n<td>\n<p><strong>Sanctuary Entry Prices</strong></p>\n<p>Adults $10.00              Children (Under 16) $5.00</p>\nDonations to support the society can be made at the zoo office or by calling 1300 555                      </td>\n</tr></tbody></table><p>                                                                                </p>\n<p> <img src=\"/courses/34/files/2326/preview\" alt=\"Kangaroo.png\">           </p>\n<p><strong>About us </strong>Salvation Animals Sanctuary is the brainchild of David Wright, founder of the Native Animal Rescue Society The self-sustaining Sanctuary was established help support the society in its rescue efforts. The Sanctuary is open to the public every Friday to Tuesday and welcomes all native animals who are in need of care and a home.</p>",
                        "answers": [
                            {
                                "answer_exact": "",
                                "answer_error_margin": "",
                                "answer_range_start": "",
                                "answer_range_end": "",
                                "answer_weight": "0",
                                "numerical_answer_type": "exact_answer",
                                "blank_id": "",
                                "id": "",
                                "match_id": "",
                                "answer_text": "",
                                "answer_match_left": "",
                                "answer_match_right": "",
                                "answer_comment": "",
                                "answer_html": "",
                                "answer_match_left_html": "",
                                "answer_comment_html": ""
                            },
                            {
                                "answer_exact": "",
                                "answer_error_margin": "",
                                "answer_range_start": "",
                                "answer_range_end": "",
                                "answer_weight": "0",
                                "numerical_answer_type": "exact_answer",
                                "blank_id": "",
                                "id": "",
                                "match_id": "",
                                "answer_text": "",
                                "answer_match_left": "",
                                "answer_match_right": "",
                                "answer_comment": "",
                                "answer_html": "",
                                "answer_match_left_html": "",
                                "answer_comment_html": ""
                            },
                            {
                                "answer_exact": "",
                                "answer_error_margin": "",
                                "answer_range_start": "",
                                "answer_range_end": "",
                                "answer_weight": "0",
                                "numerical_answer_type": "exact_answer",
                                "blank_id": "",
                                "id": "",
                                "match_id": "",
                                "answer_text": "",
                                "answer_match_left": "",
                                "answer_match_right": "",
                                "answer_comment": "",
                                "answer_html": "",
                                "answer_match_left_html": "",
                                "answer_comment_html": ""
                            },
                            {
                                "answer_exact": "",
                                "answer_error_margin": "",
                                "answer_range_start": "",
                                "answer_range_end": "",
                                "answer_weight": "0",
                                "numerical_answer_type": "exact_answer",
                                "blank_id": "",
                                "id": "",
                                "match_id": "",
                                "answer_text": "",
                                "answer_match_left": "",
                                "answer_match_right": "",
                                "answer_comment": "",
                                "answer_html": "",
                                "answer_match_left_html": "",
                                "answer_comment_html": ""
                            }
                        ],
                        "text_after_answers": "",
                        "assessment_question_id": 264,
                        "position": 0,
                        "published_at": "2015-09-08T00:44:05Z"
                    },
                    {
                        "id": 221,
                        "regrade_option": "",
                        "points_possible": 0,
                        "correct_comments": "",
                        "incorrect_comments": "",
                        "neutral_comments": "",
                        "correct_comments_html": "",
                        "incorrect_comments_html": "",
                        "neutral_comments_html": "",
                        "question_type": "multiple_answers_question",
                        "question_name": "Question 1",
                        "name": "Question 1",
                        "question_text": "blah",
                        "answers": [
                            {
                                "id": "878",
                                "text": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 100,
                                "html": "<p>Cafe</p>"
                            },
                            {
                                "id": "959",
                                "text": "Office",
                                "comments": "",
                                "comments_html": "",
                                "weight": 100
                            },
                            {
                                "id": "9381",
                                "text": "Rides",
                                "comments": "",
                                "comments_html": "",
                                "weight": 0
                            },
                            {
                                "id": "812",
                                "text": "Games",
                                "comments": "",
                                "comments_html": "",
                                "weight": 0
                            },
                            {
                                "id": "3712",
                                "text": "Souvenir Shop",
                                "comments": "",
                                "comments_html": "",
                                "weight": 100
                            },
                            {
                                "id": "7992",
                                "text": "Educational Centre",
                                "comments": "",
                                "comments_html": "",
                                "weight": 100
                            }
                        ],
                        "text_after_answers": "",
                        "assessment_question_id": 265,
                        "position": 1,
                        "published_at": "2015-09-08T00:44:05Z"
                    },
                    {
                        "id": 224,
                        "regrade_option": "",
                        "points_possible": 0,
                        "correct_comments": "",
                        "incorrect_comments": "",
                        "neutral_comments": "",
                        "correct_comments_html": "",
                        "incorrect_comments_html": "",
                        "neutral_comments_html": "",
                        "question_type": "multiple_choice_question",
                        "question_name": "Question 2",
                        "name": "Question 2",
                        "question_text": "blah",
                        "answers": [
                            {
                                "id": 6044,
                                "text": "To promote native animal rescue",
                                "html": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 0
                            },
                            {
                                "id": 7455,
                                "text": "To ask for donations for the sanctuary",
                                "html": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 0
                            },
                            {
                                "id": 9010,
                                "text": "To inform the public of the cost of entry",
                                "html": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 0
                            },
                            {
                                "id": 9910,
                                "text": "To advertise the opening of the sanctuary",
                                "html": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 100
                            }
                        ],
                        "text_after_answers": "",
                        "assessment_question_id": 268,
                        "position": 2,
                        "published_at": "2015-09-08T00:44:05Z"
                    },
                    {
                        "id": 225,
                        "regrade_option": "",
                        "points_possible": 0,
                        "correct_comments": "",
                        "incorrect_comments": "",
                        "neutral_comments": "",
                        "correct_comments_html": "",
                        "incorrect_comments_html": "",
                        "neutral_comments_html": "",
                        "question_type": "multiple_choice_question",
                        "question_name": "Question 3",
                        "name": "Question 3",
                        "question_text": "blah",
                        "answers": [
                            {
                                "id": 1462,
                                "text": "Withdrawn",
                                "html": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 100
                            },
                            {
                                "id": 4914,
                                "text": "Sociable",
                                "html": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 0
                            },
                            {
                                "id": 5670,
                                "text": "Unfriendly",
                                "html": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 0
                            },
                            {
                                "id": 454,
                                "text": "Drenched",
                                "html": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 0
                            }
                        ],
                        "text_after_answers": "",
                        "assessment_question_id": 269,
                        "position": 3,
                        "published_at": "2015-09-08T00:44:05Z"
                    },
                    {
                        "id": 226,
                        "regrade_option": "",
                        "points_possible": 0,
                        "correct_comments": "",
                        "incorrect_comments": "",
                        "neutral_comments": "",
                        "correct_comments_html": "",
                        "incorrect_comments_html": "",
                        "neutral_comments_html": "",
                        "question_type": "multiple_choice_question",
                        "question_name": "Question 4",
                        "name": "Question 4",
                        "question_text": "<p>What is the purpose of David Wright’s brainchild?</p>",
                        "answers": [
                            {
                                "id": 6557,
                                "text": "To support the Native Animal Rescue Zoo",
                                "html": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 0
                            },
                            {
                                "id": 1937,
                                "text": "To support the Native Animal Rescue Society",
                                "html": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 100
                            },
                            {
                                "id": 8553,
                                "text": "To give the rescued animals a permanent home",
                                "html": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 0
                            },
                            {
                                "id": 3563,
                                "text": "To create a self-sustainable tourist attraction for Mt Falls",
                                "html": "",
                                "comments": "",
                                "comments_html": "",
                                "weight": 0
                            }
                        ],
                        "text_after_answers": "",
                        "assessment_question_id": 270,
                        "position": 4,
                        "published_at": "2015-09-08T00:44:05Z"
                    },
                    {
                        "id": 227,
                        "regrade_option": "",
                        "points_possible": 0,
                        "correct_comments": "",
                        "incorrect_comments": "",
                        "neutral_comments": "",
                        "correct_comments_html": "",
                        "incorrect_comments_html": "",
                        "neutral_comments_html": "",
                        "question_type": "text_only_question",
                        "question_name": "Spacer",
                        "name": "Spacer",
                        "question_text": "blah",
                        "answers": [],
                        "text_after_answers": "",
                        "assessment_question_id": null,
                        "position": 4,
                        "published_at": "2015-09-08T00:44:05Z"
                    }
                ]
            },
            "created_at": "2015-09-30T01:02:34Z"
        },
        {
            "id": "4630",
            "event_type": "session_started",
            "event_data": {
                "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"
            },
            "created_at": "2015-09-30T01:02:37Z"
        },
        {
            "id": "4631",
            "event_type": "question_answered",
            "event_data": [
                {
                    "quiz_question_id": "220",
                    "answer": null
                },
                {
                    "quiz_question_id": "221",
                    "answer": []
                },
                {
                    "quiz_question_id": "224",
                    "answer": null
                },
                {
                    "quiz_question_id": "225",
                    "answer": null
                },
                {
                    "quiz_question_id": "226",
                    "answer": null
                },
                {
                    "quiz_question_id": "227",
                    "answer": null
                },
                {
                    "quiz_question_id": "228",
                    "answer": null
                },
                {
                    "quiz_question_id": "229",
                    "answer": null
                },
                {
                    "quiz_question_id": "231",
                    "answer": null
                },
                {
                    "quiz_question_id": "235",
                    "answer": null
                },
                {
                    "quiz_question_id": "236",
                    "answer": null
                },
                {
                    "quiz_question_id": "237",
                    "answer": null
                },
                {
                    "quiz_question_id": "238",
                    "answer": null
                },
                {
                    "quiz_question_id": "239",
                    "answer": null
                },
                {
                    "quiz_question_id": "242",
                    "answer": null
                },
                {
                    "quiz_question_id": "243",
                    "answer": null
                },
                {
                    "quiz_question_id": "245",
                    "answer": null
                },
                {
                    "quiz_question_id": "246",
                    "answer": null
                }
            ],
            "created_at": "2015-09-30T01:02:48Z"
        }
    ]
}

My main questions are:

  1. Why is JSON.deserialize spitting the dummy on any of this if the
    above sites say it's valid?

  2. Is there a method or efficient bit of code I can use to remove the
    troublesome characters?

  3. What's causing the event_data parsing error?

Any advice appreciated.

Best Answer

Regarding the event_data issue: your JSON is inconsistent. In one case it's an object containing the user_agent string:

"event_data": {
                "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"
            },

And elsewhere it's a list of quiz_data:

"event_data": [
                {
                    "quiz_question_id": "220",
                    "answer": null
                },
                {
                    "quiz_question_id": "221",
                    "answer": []
                } [...]
            ],

This wouldn't be picked up by a JSON linter, because it's just checking that the data is valid - not that it's consistent.

I notice that the parser code generated by JSON2Apex is different from your parser code above, and e.g. creates different classes to handle the different types of answers and event_data:

//
// Generated by JSON2Apex http://json2apex.herokuapp.com/
//

public class JSON2Apex {

    public class Answers {
        public String answer_exact;
        public String answer_error_margin;
        public String answer_range_start;
        public String answer_range_end;
        public String answer_weight;
        public String numerical_answer_type;
        public String blank_id;
        public String id;
        public String match_id;
        public String answer_text;
        public String answer_match_left;
        public String answer_match_right;
        public String answer_comment;
        public String answer_html;
        public String answer_match_left_html;
        public String answer_comment_html;
    }

    public class Quiz_data {
        public Integer id;
        public String regrade_option;
        public Integer points_possible;
        public String correct_comments;
        public String incorrect_comments;
        public String neutral_comments;
        public String correct_comments_html;
        public String incorrect_comments_html;
        public String neutral_comments_html;
        public String question_type;
        public String question_name;
        public String name;
        public String question_text;
        public List<Answers> answers;
        public String text_after_answers;
        public Integer assessment_question_id;
        public Integer position;
        public String published_at;
    }

    public class Answers_Z {
        public String id;
        public String text;
        public String comments;
        public String comments_html;
        public Integer weight;
        public String html;
    }

    public class Quiz_submission_events {
        public String id;
        public String event_type;
        public Event_data event_data;
        public String created_at;
    }

    public List<Quiz_submission_events> quiz_submission_events;

    public class Event_data {
        public Integer quiz_version;
        public List<Quiz_data> quiz_data;
    }

    public class Answers_Y {
        public Integer id;
        public String text;
        public String html;
        public String comments;
        public String comments_html;
        public Integer weight;
    }

    public class Answers_X {
    }

    public class Event_data_Z {
        public String user_agent;
    }

    public class Event_data_Y {
        public String quiz_question_id;
        public Object answer;
    }


    public static JSON2Apex parse(String json) {
        return (JSON2Apex) System.JSON.deserialize(json, JSON2Apex.class);
    }
}

This is an attempt to handle the inconsistencies in the JSON. I'm not sure whether it'll actually work in practice, but it's worth trying.

If it doesn't work, you have a few options.

  • Ideally you would change the JSON to be consistent, but that may not be an option if you're receiving it from a third party.
  • Alternatively, you could use JSON.deserializeUntyped() and check the type of each event_data at runtime, but you'd lose the advantages of typed deserialization.
  • Or, you could write custom parser code to manually check the value of event_data and parse it appropriately. If you check the Create explicit parse code box on JSON2Apex, you can take the parser code as a starting point.
Related Topic