Introduction
Recently I encountered the following situation in Business Central with Web Service that uses Pagination with Continuation Tokens:
–> When you get data from Web Service and it returns a lot of results, the data is paginated.
–> One method to handle pagination is using Continuation Tokens.
–> For example, look at the below response, it contains a data array with customer elements and after that, information about the Continuation Token:
–> This means that you first need to create a request to the API Endpoint and then check if there is a Continuation Token or not. If there is one, you need to make the same request again but include the Continuation Token in the query string in order to get the rest of the data.. And so on, until the last response will not contain a Continuation Token anymore.
–> Example of request URL with Continuation Token:
https://apitest.azure-api.net/int/customer?&continuationtoken=eyJ0b2tlbiI6IitSSUQ6fjBKNXhBTkQxdedewfrwfrwfreresibWluIjoiIiwibWF4IjoiRkYifX0=
Coding Pagination with Continuation Tokens in Business Central
–> I think it is obvious that Recursion is the solution for implementing the Pagination with Continuation Tokens. Let’s see my idea about how to accomplish this in D365 Business Central AL language.
–> You can see below the whole logic of calling recursively the function to retrieve Customer data until the last page of the Web Service response:
procedure GetCustomer() begin GetCustomerRecursive('', ''); end; local procedure GetCustomerRecursive(ContinuationToken: Text; PreviousContinuationToken: Text) var Token: Text; HttpClient: HttpClient; ReqHeaders: HttpHeaders; RequestHeader: HttpHeaders; Content: HttpContent; ResponseMessage: HttpResponseMessage; JObject: JsonObject; JToken: JsonToken; JArray: JsonArray; RString: Text; i: Integer; TestCustStag: Record "Test Customer Staging"; EntryNo: Integer; LineNo: Integer; DateTimeDotNetUTC: DotNet DateTime; ContinuationTokenMod: Text; GetUrl: Text; begin Token := GetOauthToken(); GetAPISetup(); TestAPISetup.TestField("Customer - INT URL"); TestAPISetup.TestField("Ocp-Apim-Subscription-Key Cust"); TestAPISetup.TestField("If-Modified-Since Cust"); PreviousContinuationToken := ContinuationToken; IF ContinuationToken = '' THEN begin HttpClient.SetBaseAddress(TestAPISetup."Customer - INT URL"); GetUrl := TestAPISetup."Customer - INT URL"; end Else begin ContinuationTokenMod := ReplaceString(ContinuationToken, '<originalUrl>', ''); GetUrl := TestAPISetup."Customer - INT URL" + '?' + ContinuationTokenMod; end; ReqHeaders := HttpClient.DefaultRequestHeaders; ReqHeaders.Add('Authorization', Token); ReqHeaders.Add('Ocp-Apim-Subscription-Key', TestAPISetup."Ocp-Apim-Subscription-Key Cust"); ReqHeaders.Add('If-Modified-Since', TestAPISetup."If-Modified-Since Cust"); DateTimeDotNetUTC := DateTImeDotNetUTC.UtcNow; HttpClient.Get(GetUrl, ResponseMessage); If not ResponseMessage.IsSuccessStatusCode() then Error('Web service returned error:\\' + 'Status code: %1\' + 'Description: %2', ResponseMessage.HttpStatusCode(), ResponseMessage.ReasonPhrase()); ResponseMessage.Content().ReadAs(RString); JToken.ReadFrom(RString); JObject := JToken.AsObject(); ContinuationToken := SelectJsonToken(JObject, '$.links.next'); // !!! JObject.SelectToken('data', JToken); JArray := JToken.AsArray(); EntryNo := GetTestCustStagingNextEntryNo; For i := 0 to JArray.Count - 1 do begin LineNo += 1000; TestCustStag.Init(); TestCustStag."Entry No" := EntryNo; TestCustStag."Line No." := LineNo; JArray.Get(i, JToken); HandleArrayCustomer(JToken, TestCustStag); //get data from each array element TestCustStag."Import Date Time" := CurrentDateTime; TestCustStag.Insert(TRUE); end; IF (ContinuationToken <> '') AND (ContinuationToken <> PreviousContinuationToken) then GetCustomerRecursive(ContinuationToken, PreviousContinuationToken); //for pagination... //do if no recursion occured OR only for the first call in case recursion occured! IF ((ContinuationToken = '') AND (PreviousContinuationToken = '')) OR ((ContinuationToken <> '') AND (PreviousContinuationToken = '')) THEN BEGIN UpdateIfModifiedSinceCustTestSetup(DateTimeDotNetUTC); END; end;
–> The Web Service used in this example returns JSON data. I leave here also the function “HandleArrayCustomer” which reads different data from a complicated JSON Response Array element.. I think this is a good example of how to handle any type of Json File in D365 Business Central:
local procedure HandleArrayCustomer(JsonToken: JsonToken; var TestCustStag: Record "Test Customer Staging") var JsonObject: JsonObject; JsonObjectCustomer: JsonObject; JTokenCustomer: JsonToken; JTokenAffiliationArr: JsonToken; Txt: Text; JArrayAffiliation: JsonArray; JTokenAffiliationElement: JsonToken; i: Integer; j: integer; JObjectAffiliationElement: JsonObject; AffiliationType: text; JTokenEntityInfo: JsonToken; JArrayEntityInfo: JsonArray; JObjectEntityInfo: JsonObject; PartyType: Text; JTokenOrganization: JsonToken; JArrayOrganization: JsonArray; JObjectOrganization: JsonObject; LegalEntity: Text; JTokenCustExtension: JsonToken; JObjectCustExtension: JsonObject; JTokenPaymentTerms: JsonToken; JTokenPaymentMethod: JsonToken; begin JsonObject := JsonToken.AsObject(); IF Evaluate(TestCustStag."Response Data Id", SelectJsonToken(JsonObject, '$.id')) THEN; JsonObject.SelectToken('$.Customers.Customer[0]', JTokenCustomer); JsonObjectCustomer := JTokenCustomer.AsObject(); JsonObjectCustomer.SelectToken('$.Affiliation', JTokenAffiliationArr); JArrayAffiliation := JTokenAffiliationArr.AsArray(); For i := 0 to JArrayAffiliation.Count - 1 do begin JArrayAffiliation.Get(i, JTokenAffiliationElement); JObjectAffiliationElement := JTokenAffiliationElement.AsObject(); AffiliationType := SelectJsonToken(JObjectAffiliationElement, '$.AffiliationType[0]'); IF AffiliationType = 'CustomerNumber' then TestCustStag.CustomerNumber_PartyID := SelectJsonToken(JObjectAffiliationElement, '$.PartyID'); IF AffiliationType = 'PayingCustomer' then TestCustStag.PayingCustomer_PartyID := SelectJsonToken(JObjectAffiliationElement, '$.PartyID'); end; JsonObjectCustomer.SelectToken('$.EntityInformation', JTokenEntityInfo); JArrayEntityInfo := JTokenEntityInfo.AsArray(); For i := 0 to JArrayEntityInfo.Count - 1 Do begin JArrayEntityInfo.Get(i, JTokenEntityInfo); JObjectEntityInfo := JTokenEntityInfo.AsObject(); PartyType := SelectJsonToken(JObjectEntityInfo, '$.PartyType'); IF PartyType = 'Organization' THEN begin JObjectEntityInfo.SelectToken('$.Organization', JTokenOrganization); JArrayOrganization := JTokenOrganization.AsArray(); for j := 0 to JArrayOrganization.Count - 1 Do begin JArrayOrganization.Get(j, JTokenOrganization); JObjectOrganization := JTokenOrganization.AsObject(); LegalEntity := SelectJsonToken(JObjectOrganization, '$.Classification.LegalEntity'); If LegalEntity <> 'Juridisk Eier' Then begin TestCustStag."Customer Name" := SelectJsonToken(JObjectOrganization, '$.Name[0]'); TestCustStag.TaxPayerID := SelectJsonToken(JObjectOrganization, '$.TaxpayerID'); TestCustStag.LegalEntity := LegalEntity; end; IF LegalEntity = 'Juridisk Eier' then TestCustStag."Customer Name 2" := SelectJsonToken(JObjectOrganization, '$.Name[0]'); end; end; end; JsonObjectCustomer.SelectToken('$.NGARTSCustomerCustomerExtension[0]', JTokenCustExtension); JObjectCustExtension := JTokenCustExtension.AsObject(); JObjectCustExtension.SelectToken('$.PaymentTerm', JTokenPaymentTerms); TestCustStag."Payment Term Code" := SelectJsonToken(JTokenPaymentTerms.AsObject(), '$.Code'); TestCustStag."Payment Term Name" := SelectJsonToken(JTokenPaymentTerms.AsObject(), '$.Name'); JObjectCustExtension.SelectToken('$.PaymentMethod', JTokenPaymentMethod); TestCustStag."Payment Method Code" := SelectJsonToken(JTokenPaymentMethod.AsObject(), '$.Code'); TestCustStag."Payment Method Name" := SelectJsonToken(JTokenPaymentMethod.AsObject(), '$.Name'); IF Evaluate(TestCustStag.CreditLimit, SelectJsonToken(JObjectCustExtension, '$.CreditLimit')) THEN; end; local procedure UpdateIfModifiedSinceCustTestSetup(var DotNetUTCTime: DotNet DateTime) var begin TestAPISetup."If-Modified-Since Cust" := DotNetUTCTime.ToString('r'); TestAPISetup.Modify(); end;
–> And Function “SelectToken”:
local procedure SelectJsonToken(JsonObject: JsonObject; Path: text): Text; var JsonToken: JsonToken; begin if not JsonObject.SelectToken(Path, JsonToken) then exit(''); if JsonToken.AsValue.IsNull then exit(''); exit(jsontoken.asvalue.astext); end;
–> This is, I think, almost all the “arsenal” you need to know in order to work with Json Data in Business Central.
I hope this helps.