[SalesForce] Malformed JSON: Expected ‘{‘ at the beginning of object

I am getting the Malformed JSON error on the below class:

public with sharing class SettlementReports {

    /* variables */
    public Id settlementBatchExternalId;
    public Id settlementExternalId;
    public String recordId; // external object Id
    public String status = 'TO BE SPECIFIED';
    public String configurationName;
    public String EndPoint;
    public Map<String, String> DEFAULT_HTTP_REQUEST_HEADERS = new Map<String, String> {'Content-Type'=>'application/json', 'Accept'=>'application/json'};
    public String RequestBody;
    public HttpResponse response;
    public List<GetSettlementResponse> settlementsList;
    public Set<Id> accountIds;
    public Set<Id> bankAccountIds;
    public Map<Id,Account> accounts;
    public Map<Id,BankAccount__c> bankAccounts;
    public String csvString;
    public ContentVersion contVersionItem;
    public FeedItem feedPostItem;
    public FeedAttachment feedAttcItem;
    public SettlementBatch__x settlementBatchRecord; 
    /* constructors */
    public SettlementReports(){}
    public SettlementReports(String recordId, string objectName){
        if( objectName == 'settlementRecord'){
           this.recordId =  recordId;
           this.configurationName = 'Settlement';
        }else if ( objectName == 'settlementBatchRecord'){
           this.recordId = recordId;  
           this.configurationName = 'Settlements';
        }

    }



    /* support methods */
    public SettlementReports prepareForCallOut(){  

        system.debug('...### this.configurationName ' + this.configurationName);

        SObject Configuration = new CommunicationsSelector().getSObjectRecordsByField('Bank_Settlement_Configuration__mdt','Label', new List<String>{this.configurationName})
                                                            .getSObjectList()[0];

        this.DEFAULT_HTTP_REQUEST_HEADERS.put('Authorization',(String)Configuration.get('Authorization_Token__c'));
        this.DEFAULT_HTTP_REQUEST_HEADERS.put('x-api-key','7IObvQv6fi3pVCPQMW79n8hpsEiXBs3K2rQxbaom');

        /*end point */
        if ( configurationName == 'Settlements'){
           this.EndPoint =(String)Configuration.get('End_Point__c') + recordId; 
        } else if (configurationName == 'Settlement'){
           this.EndPoint =(String)Configuration.get('End_Point__c') + recordId;
        }

        system.debug('...### this.EndPoint ' + this.EndPoint);

        return this;
    }


    public SettlementReports performCallOut(){

       response = HTTPCallOut.sendRequest(EndPoint,'GET','', DEFAULT_HTTP_REQUEST_HEADERS,configurationName);

       return this; 
    }

    public SettlementReports processCallOutResponse(){

        if ( this.response.getStatusCode() != 200 ) { 
            this.status = 'There has been an error, please try again';
            return this;
        }

        system.debug('...### this.response.getBody() ' + this.response.getBody());

        formatSettlement resStruct = (formatSettlement)JSON.deserialize( refineResponseString(this.response.getBody()), formatSettlement.class );
         if(resStruct!=null){
            this.settlementsList = resStruct.GetSettlementResponse;
        }

        system.debug('...### settlementsList ' + settlementsList);

        return this;
    }

    public SettlementReports processSettlementsList(){
        accountIds = new Set<Id>();
        //bankAccountIds = new Set<Id>();

        for(GetSettlementResponse stm:this.settlementsList){
            accountIds.add(stm.related_organisation);
            //bankAccountIds.add(stm.bank_account);
        }

        this.accounts = new Map<Id,Account>([Select Id, Name, BillingStreet, BillingCity, BillingState, BillingCountry, BillingPostalCode, Finance_ClientId__c,OrganisationEmail__c, Parent.Name, Parent.Finance_ClientId__c, Phone, CurrencyIsoCode From Account Where Id IN:accountIds]);

        return this;
    }

    public String refineResponseString(String response){

        response = response.contains('currency') ? response.replace('currency','recordCurrency') : response;

        return response;
    }
    /* format date from '2017-06-09T00:42:13.330Z' to 'DD/MM/YY DD/MM/YY HH:MM:SS' */
    public String formatDate(String dateString){
       String bSlash = '/';
       return dateString.mid(8,2) + bSlash + dateString.mid(5,2) + bSlash + dateString.mid(2,2) + ' ' + dateString.mid(11,8);
    } 

    public SettlementReports generateCSVFile(){

        String csvHeader = new translatedSettlement().getCSVHeader();
        String csvRows;
        for(GetSettlementResponse stm:this.settlementsList){
            system.debug('...### stm ' + stm);
            Account tempAcc = this.accounts.get(stm.related_organisation);
            Decimal totalCollected = stm.total_amount + stm.charge_amount_total + stm.charge_tax_amount_total;
            Decimal totalFeeCollectedInclTax = stm.charge_amount_total + stm.charge_tax_amount_total;
            totalFeeCollectedInclTax = totalFeeCollectedInclTax.setScale(2);

            String tempCSVRow = new translatedSettlement( 
                                                  tempAcc.Finance_ClientId__c,
                                                  tempAcc.Name,
                                                  tempAcc.BillingStreet,
                                                  tempAcc.BillingCity,
                                                  tempAcc.BillingState,
                                                  tempAcc.BillingPostalCode,
                                                  tempAcc.BillingCountry,
                                                  tempAcc.CurrencyIsoCode,
                                                  tempAcc.Phone,
                                                  tempAcc.OrganisationEmail__c,
                                                  tempAcc.parent.Name,
                                                  tempAcc.parent.Finance_ClientId__c,
                                                  stm.recordCurrency,
                                                  formatDate(stm.start_date),
                                                  formatDate(stm.end_date),
                                                  stm.bankaccount_bank_code,
                                                  stm.bankaccount_account_number,
                                                  stm.bankaccount_name,
                                                  totalCollected,
                                                  stm.surcharge_amount_total,
                                                  stm.charge_amount_total,
                                                  stm.charge_tax_amount_total,
                                                  totalFeeCollectedInclTax,
                                                  stm.total_amount).getCSVRow();

           csvRows += tempCSVRow;
        }

        this.csvString = csvHeader + csvRows + '\n';
        system.debug('...### csvHeader '+csvHeader);
        system.debug('...### csvRows '+ csvRows);
        system.debug('...### csvString '+this.csvString);
        return this;

    }

    public SettlementReports generateFile(){

        Blob extractContentBlob = Blob.valueOf(this.csvString);

        ContentVersion contVersion = new ContentVersion();
        contVersion.VersionData = extractContentBlob;
        contVersion.Title = 'Netsuite Extract - Settlement Batch '+ recordId + '.csv'; 
        contVersion.pathOnClient = 'pathOnClient';

        this.contVersionItem  = contVersion;
        return this;
    }

    public SettlementReports generateChatterFile(Id BatchSettlementId){
          system.debug('...### createChatterFile ' + BatchSettlementId);

          FeedItem post = new FeedItem();
          post.Body = 'Netsuite Extract - Settlement Batch '+ recordId; 
          post.ParentId = BatchSettlementId;

          this.feedPostItem = post;

          FeedAttachment feedAtt = new FeedAttachment();
          //feedAtt.FeedEntityId = post.Id;
          //feedAtt.RecordId = this.contVersionItem.Id;
          feedAtt.Type = 'CONTENT';

          this.feedAttcItem = feedAtt; 

          return this;
    }

    public SettlementReports insertObjects(){

        Savepoint sPoint = Database.setSavepoint();

        try{
            /*insert file*/
            insert this.contVersionItem;

            /*insert feedPost*/
            insert this.feedPostItem;

            /*insert feedAttachment*/
            this.feedAttcItem.FeedEntityId = this.feedPostItem.Id;
            this.feedAttcItem.RecordId = this.contVersionItem.Id;
            insert this.feedAttcItem;
            this.status = 'Netsuite extract successfully created.  Download the file from chatter feed';

        } catch(Exception e){
            Database.rollback(sPoint);

            this.status = 'There has been a problem while creating the file.  Please contact your system administrator';
            return this;

        }
        return this;
    }

    public String getStatus(){
        return this.status;
    }

    public SettlementAccount getSettlementAccount(){

        GetSettlementResponse Stm = this.settlementsList.get(0);
        Account Acc = this.accounts.get(Stm.related_organisation);
        SettlementBatch__x setBatch = this.settlementBatchRecord;
        return new SettlementAccount(Stm, Acc, setBatch);
    }

    public SettlementReports getSettlementBatch(){  
      if (!Test.isRunningTest()) {
        GetSettlementResponse Stm = this.settlementsList.get(0);
          this.settlementBatchRecord = (SettlementBatch__x) new CommunicationsSelector().getSObjectRecordsByField('SettlementBatch__x', 'ExternalId' ,Stm.settlement_batch)
                                                            .getSObjectList()[0];
        } else {
          this.settlementBatchRecord =new SettlementBatch__x();
        }
        return this;
    }

    /* inner classes */

    public class Settlement{

        public String id;
        public String settlement_batch;
        public Decimal total_amount;
        public String start_date;
        public String end_date;
        public String status;
        public String related_organisation;
        public String bank_account;
        public String recordCurrency;
        public String bankaccount_bank_code;
        public String bankaccount_account_number;
        public String bankaccount_name;
        public Decimal charge_amount_total;
        public Decimal charge_tax_amount_total;
        public Decimal surcharge_amount_total;
        public Decimal transaction_amount_total;

    }
    public class formatSettlement{
        public List<GetSettlementResponse> GetSettlementResponse;
    }
        public class GetSettlementResponse {
        public String end_date;
        public Double total_amount;
        //public String currency;
        public Integer id;
        public String settlement_batch;
        public String start_date;
        public String status;
        public String related_organisation;
        public String bank_account;
                String bankaccount_account_number;
                public String recordCurrency;
                String bankaccount_bank_code;
                String bankaccount_name;
                public Decimal charge_amount_total;
                public Decimal charge_tax_amount_total;
                public Decimal surcharge_amount_total;
                public Decimal transaction_amount_total;
        }

    public class translatedSettlement{

        String clientId;
        String clientName;
        String addressLineOne;
        String suburbCity;
        String state;
        String postCode;
        String country;
        String primaryCurrency;
        String phoneNumber;
        String emailAddress;
        String parentCompanyName;
        String parentCompanyClientId;
        String paymentCurrency;
        String paymentPeriodStartDate;
        String paymentPeriodEndDate;
        String bankAccountCode;
        String bankAccountNumber;
        String bankAccountName;
        Decimal totalCollected;
        Decimal surcharge;
        Decimal feeCollectedExclTax;
        Decimal feeTaxCollected;
        Decimal totalFeeCollectedInclTax;
        Decimal totalPayOut;
        //Decimal 
        String csvRow;
        String comma = ',';
        String csvHeader;

        /*constructor*/
        public translatedSettlement(){}
        public translatedSettlement(String clientId , String clientName , String addressLineOne,
                                    String suburbCity, String state, String postCode, String country, String primaryCurrency, String phoneNumber,
                                    String emailAddress, String parentCompanyName, String parentCompanyClientId, String paymentCurrency, String paymentPeriodStartDate,
                                    String paymentPeriodEndDate, String bankAccountCode, String bankAccountNumber, String bankAccountName,
                                    Decimal totalCollected, Decimal surcharge, Decimal feeCollectedExclTax ,Decimal feeTaxCollected,Decimal totalFeeCollectedInclTax, Decimal totalPayOut){
           this.csvRow = clientId + comma
                       + clientName + comma
                       + AddressLineOne + comma
                       + suburbCity + comma
                       + state + comma
                       + postCode + comma
                       + country + comma
                       + primaryCurrency + comma
                       + phoneNumber + comma
                       + emailAddress + comma
                       + parentCompanyName + comma
                       + parentCompanyClientId + comma
                       + paymentCurrency + comma
                       + paymentPeriodStartDate + comma
                       + paymentPeriodEndDate + comma
                       + bankAccountCode + comma
                       + bankAccountNumber + comma
                       + bankAccountName + comma
                       + totalCollected + comma
                       + surcharge + comma
                       + feeCollectedExclTax + comma
                       + feeTaxCollected + comma
                       + totalFeeCollectedInclTax + comma
                       + totalPayOut + '\n';
        }
        /* methods */
        public String getCSVRow(){
            return this.csvRow;
        }

        public String getCSVHeader(){

            String tempHeader = 'Client ID'+ comma
                              + 'Name' + comma
                              + 'Address' + comma
                              + 'Suburb/City' + comma
                              + 'State' + comma
                              + 'PostCode' + comma
                              + 'Country' + comma
                              + 'Primary Currency' + comma
                              + 'Phone Number' + comma
                              + 'Email Address' + comma
                              + 'Parent Company Name' + comma
                              + 'Parent Company Client ID' + comma
                              + 'Payment Currency' + comma
                              + 'Payment Period Start' + comma
                              + 'Payment Period End' + comma
                              + 'Bank Account Bank Code' + comma
                              + 'Bank Account Account Number' + comma
                              + 'Bank Account Name' + comma
                              + 'Total Collected by SportsTG' + comma
                              + 'Surcharge Collected' + comma
                              + 'Fee Collected excl. Tax' + comma
                              + 'Fee Tax Collected' + comma
                              + 'Total Fee Collected incl. Tax' + comma
                              + 'Total Payout' + '\n';

             return tempHeader;                 
        }


    }

    public class SettlementAccount{
        public GetSettlementResponse Stm;
        public Account Acc;
        public SettlementBatch__x setBatch;
        /* constructor */
        public SettlementAccount(GetSettlementResponse Stm, Account Acc, SettlementBatch__x setBatch){
            this.Stm = Stm;
            this.Acc = Acc;
            this.setBatch = setBatch;
        }
    }

}

Best Answer

I think your JSON String(Webservice Response) consists of an Array of (Starts with [ and ends with ]) formatSettlement Object.

And you are passing only formatSettlement.class in the JSON.deserialize method. So the parser is expecting a single Object in the JSON and it should start with { which is not true in your case and throwing an Exception as 'Malformed JSON: Expected '{' at the beginning of object'.

You can try passing List<formatSettlement>.class instead of formatSettlement.class in the JSON.deserialize method because you are parsing a list of items not a single item.

Your update code should look like this.

List<formatSettlement> resStruct = (List<formatSettlement>)JSON.deserialize( refineResponseString(this.response.getBody()), List<formatSettlement>.class );
Related Topic