Dynamics.IS JSON Mgt codeunit question (generating well formed JSON string)

jeighsohnjeighsohn Member Posts: 14
Hello!
I am trying to implement the sample codeunit provided many years ago by Dynamics.is (https://dynamics.is/?p=2303) to modernize functionality we use in NAV2013. Currently, we are using a FedEx SOAP web service that is being deprecated by the host. They have created a RESTful API to replace the legacy SOAP connection, and since NAV2013 does not have native support for JSON, I used this forum (and google) to find the aforementioned codeunit.

I'm playing around with generating my JSON request first, and comparing it to the expected payload in the new API's documentation. I'm running into an issue I'm hoping someone can help me understand.

The expected payload features an array (streetLines shown below in both the shipper and recipient branches) within a branch within a branch.
  "requestedShipment": {
    "shipper": {
      "address": {
        "streetLines": [
          "8181 Darrow Rd"
        ],
        "city": "Twinsburg",
        "stateOrProvinceCode": "OH",
        "postalCode": "44087",
        "countryCode": "US",
        "residential": false
      }
    },
    "recipient": {
      "address": {
        "streetLines": [
          "1550 Union Blvd",
          "Suite 302"
        ],
        "city": "Beverly Hills",
        "stateOrProvinceCode": "TN",
        "postalCode": "65247",
        "countryCode": "US",
        "residential": false
      }
    },  

I have used Gunnar's code example to get me close, but I don't understand how to generate the values in the streetLines array (there is no name:value pair, it's just the value, as the name is the array name). As a result, I get a malformed JSON string (it's adding another object with a null name and known value).
  "requestedShipment": {
    "shipper": {
      "address": {
        "streetLines": [
          {
            "": "5543 Aliquet St"  //This is wrong
          }
        ],
        "city": "Fort Dodge",
        "stateOrProvinceCode": "GA",
        "postalCode": "20783",
        "countryCode": "US",
        "residential": "false"
      }
    },
    "recipient": {
      "address": {
        "streetLines": [
          {
            "": "7292 Dictum Ave"  //This is wrong
          }
        ],
        "city": "San Antonio",
        "stateOrProvinceCode": "MI",
        "postalCode": "47096",
        "countryCode": "US",
        "residential": "false"
      }
    }
  },

Can anyone direct me towards what I should be using here? This codeunit leverages the newtownsoft.dll and I'm wondering if I should be using a different class than WritePropertyName (null in my example above) and WriteValue found in the AddToJSon function within the custom codeunit?

Thanks!

Here is my C/AL code that I'm using to generate my JSON. It is very hard-coded for now as I get used to how the CU works.

WITH cduJSon DO BEGIN
  StartJSon;
  AddJSonBranch('accountNumber');
  AddtoJSon('value','XXXXXXXXX');
  EndJSonBranch;
  AddJSonBranch('rateRequestControlParameters');
  AddtoJSon('returnTransitTimes',TRUE);
  AddtoJSon('servicesNeededOnRateFailure',FALSE);
  AddtoJSon('rateSortOrder','SERVICENAMETRADITIONAL');
  ENDJSonBranch;
  AddJSonBranch('requestedShipment');
   //Origin
  AddJSonBranch('shipper');
  AddJSonBranch('address');
  StartJSonArray('streetLines');     //TODO this result is malformed
  StartJson;
  AddtoJSon('','5543 Aliquet St');  //function requires name and value parameters, but name is already defined in the Array
  EndJSon;
  EndJSonArray; //streetLines
  AddtoJSon('city','Fort Dodge');
  AddtoJSon('stateOrProvinceCode','GA');
  AddtoJSon('postalCode','20783');
  AddtoJSon('countryCode','US');
  AddtoJSon('residential',FALSE);
  EndJSonBranch; //address
  EndJSonBranch; //shipper
   //Destination
  AddJSonBranch('recipient');
  AddJSonBranch('address');
  StartJSonArray('streetLines');     //TODO this result is malformed
  StartJSon;
  AddtoJSon('','7292 Dictum Ave');  //function requires name and value parameters, but name is already defined in the Array
  EndJSon;
  EndJSonArray; //streetLines
  AddtoJSon('city','San Antonio');
  AddtoJSon('stateOrProvinceCode','MI');
  AddtoJSon('postalCode','47096');
  AddtoJSon('countryCode','US');
  AddtoJSon('residential',FALSE);
  EndJSonBranch; //address
  EndJSonBranch; //recipient 
  EndJSonBranch; //requestedShipment
  StartJSonArray('rateRequestType');     //TODO this result is malformed
  StartJSon;
  AddtoJSon('','ACCOUNT')  //function requires name and value parameters, but name is already defined in the Array
  EndJSon;
  EndJSonArray;
  AddtoJSon('pickupType','USE_SCHEDULED_PICKUP');
  StartJSonArray('requestedPackageLineItems');
  StartJSon;
  AddJSonBranch('weight');
  AddtoJSon('units','LB');
  AddtoJSon('value',22);
  EndJSonBranch;  //weight
  AddJSonBranch('dimensions');
  AddtoJSon('length',10);
  AddtoJSon('width',8);
  AddtoJSon('height',4);
  AddtoJSon('units','IN');
  EndJSonBranch;  //dimensions;
  EndJSon;
  EndJSonArray;
  AddtoJSon('packagingType','YOUR_PACKAGING');

  EndJSon;
  JSon := GetJSon;

Best Answers

  • ftorneroftornero Member Posts: 524
    Answer ✓
    Hello @jeighsohn,

    You can add these 2 functions to the codeunit "Dynamics.is JSon Mgt."
    AddJSonBranch2(BranchName : Text)
    JsonTextWriter.WritePropertyName(BranchName);
    

    The local variables in the AddArrayTxt2Json are::
    Name	DataType	Subtype	Length
    JsonArray	DotNet	Newtonsoft.Json.Linq.JArray.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    JsonSerialize	DotNet	Newtonsoft.Json.JsonSerializer.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    ArrayValue	Text		
    i	Integer		
    
    AddArrayTxt2Json(VariableName : Text;ArrayTxt : ARRAY [10] OF Text)
    ArrayValue := '[';
    FOR i := 1 TO ARRAYLEN(ArrayTxt) DO BEGIN
      IF ArrayTxt[i] <> '' THEN
        IF ArrayValue = '[' THEN
          ArrayValue := ArrayValue + '"' + ArrayTxt[i] + '"'
        ELSE
          ArrayValue := ArrayValue + ',' + '"' + ArrayTxt[i] + '"';
    END;
    ArrayValue := ArrayValue + ']';
    
    JsonArray := JsonArray.Parse(ArrayValue);
    JsonObj := JsonObj.JObject;
    JsonObj.Add(VariableName, JsonArray);
    JsonSerialize :=JsonSerialize.JsonSerializer;
    JsonSerialize.Serialize(JsonTextWriter, JsonObj);
    

    And build the json like this:
    qArray[1] := '1550 Union Blv';
    qArray[2] := 'Suite 302';
    
    FunJson.StartJSon();
      FunJson.AddJSonBranch('recipient');
        FunJson.AddJSonBranch2('address');
          FunJson.AddArrayTxt2Json('streetLines', qArray);
        // Not EndJSonBranch for address
      FunJson.EndJSonBranch(); // recipient
    FunJson.EndJSon;
    
    MESSAGE(FunJson.GetJSon());
    

    The variables for the last code are:
    Name	DataType	Subtype	Length
    FunJson	Codeunit	Dynamics.is JSon Mgt.	
    qArray	Text		
    

    Regards
  • ftorneroftornero Member Posts: 524
    Answer ✓
    Hello @jeighsohn,

    Assuming that the "rateRequestType" is like this
    "rateRequestType": ["ACCOUNT"],
    

    You need to add a new function to the codeunit:
    AddArrayTxtProperty2Json(VariableName : Text;ArrayTxt : ARRAY [10] OF Text)
    ArrayValue := '[';
    FOR i := 1 TO ARRAYLEN(ArrayTxt) DO BEGIN
      IF ArrayTxt[i] <> '' THEN
        IF ArrayValue = '[' THEN
          ArrayValue := ArrayValue + '"' + ArrayTxt[i] + '"'
        ELSE
          ArrayValue := ArrayValue + ',' + '"' + ArrayTxt[i] + '"';
    END;
    ArrayValue := ArrayValue + ']';
    
    JsonArray := JsonArray.Parse(ArrayValue);
    JsonProperty := JsonProperty.JProperty(VariableName, JsonArray);
    JsonSerialize :=JsonSerialize.JsonSerializer;
    JsonSerialize.Serialize(JsonTextWriter, JsonProperty);
    

    With these local variables
    Name	DataType	Subtype	Length
    JsonArray	DotNet	Newtonsoft.Json.Linq.JArray.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    JsonSerialize	DotNet	Newtonsoft.Json.JsonSerializer.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    ArrayValue	Text		
    i	Integer		
    JsonProperty	DotNet	Newtonsoft.Json.Linq.JProperty.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    

    By the way I forgot to list the local variable JsonObj in the AddArrayTxt2Json function, so the complete local variables for that function are:
    Name	DataType	Subtype	Length
    JsonObj	DotNet	Newtonsoft.Json.Linq.JObject.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    JsonArray	DotNet	Newtonsoft.Json.Linq.JArray.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    JsonSerialize	DotNet	Newtonsoft.Json.JsonSerializer.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    ArrayValue	Text		
    i	Integer		
    

    So the JSON part of "requestedShipment" is:
    
        AddJSonBranch('requestedShipment');
    
          //Origin
          AddJSonBranch('shipper');
            AddJSonBranch2('address');
              CLEAR(ArrayText);
              ArrayText[1] := '5543 Aliquet St';
              ArrayText[2] := 'Ste B';
              AddArrayTxt2Json('streetLines',ArrayText);
            AddToJSon('city','Fort Dodge');
            AddToJSon('stateOrProvinceCode','GA');
            AddToJSon('postalCode','20783');
            AddToJSon('countryCode','US');
            AddToJSon('residential',FALSE);
          EndJSonBranch; //shipper
    
          //Destination
          AddJSonBranch('recipient');
            AddJSonBranch2('address');
              CLEAR(ArrayText);
              ArrayText[1] := '7292 Dictum Ave';
              ArrayText[2] := 'Unit 400';
              AddArrayTxt2Json('streetLines',ArrayText);
            AddToJSon('city','San Antonio');
            AddToJSon('stateOrProvinceCode','MI');
            AddToJSon('postalCode','47096');
            AddToJSon('countryCode','US');
            AddToJSon('residential',FALSE);
          EndJSonBranch; //recipient
    
          CLEAR(ArrayText);
          ArrayText[1] := 'ACCOUNT';
          AddArrayTxtProperty2Json('rateRequestType',ArrayText);  //ERROR HAPPENS HERE
    
          AddToJSon('pickupType','USE_SCHEDULED_PICKUP');
    
        EndJSonBranch; //requestedShipment
    

    Regards

Answers

  • ftorneroftornero Member Posts: 524
    Answer ✓
    Hello @jeighsohn,

    You can add these 2 functions to the codeunit "Dynamics.is JSon Mgt."
    AddJSonBranch2(BranchName : Text)
    JsonTextWriter.WritePropertyName(BranchName);
    

    The local variables in the AddArrayTxt2Json are::
    Name	DataType	Subtype	Length
    JsonArray	DotNet	Newtonsoft.Json.Linq.JArray.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    JsonSerialize	DotNet	Newtonsoft.Json.JsonSerializer.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    ArrayValue	Text		
    i	Integer		
    
    AddArrayTxt2Json(VariableName : Text;ArrayTxt : ARRAY [10] OF Text)
    ArrayValue := '[';
    FOR i := 1 TO ARRAYLEN(ArrayTxt) DO BEGIN
      IF ArrayTxt[i] <> '' THEN
        IF ArrayValue = '[' THEN
          ArrayValue := ArrayValue + '"' + ArrayTxt[i] + '"'
        ELSE
          ArrayValue := ArrayValue + ',' + '"' + ArrayTxt[i] + '"';
    END;
    ArrayValue := ArrayValue + ']';
    
    JsonArray := JsonArray.Parse(ArrayValue);
    JsonObj := JsonObj.JObject;
    JsonObj.Add(VariableName, JsonArray);
    JsonSerialize :=JsonSerialize.JsonSerializer;
    JsonSerialize.Serialize(JsonTextWriter, JsonObj);
    

    And build the json like this:
    qArray[1] := '1550 Union Blv';
    qArray[2] := 'Suite 302';
    
    FunJson.StartJSon();
      FunJson.AddJSonBranch('recipient');
        FunJson.AddJSonBranch2('address');
          FunJson.AddArrayTxt2Json('streetLines', qArray);
        // Not EndJSonBranch for address
      FunJson.EndJSonBranch(); // recipient
    FunJson.EndJSon;
    
    MESSAGE(FunJson.GetJSon());
    

    The variables for the last code are:
    Name	DataType	Subtype	Length
    FunJson	Codeunit	Dynamics.is JSon Mgt.	
    qArray	Text		
    

    Regards
  • jeighsohnjeighsohn Member Posts: 14
    Thank you so much for the reply!

    I was able to implement the recommended functions and get the desired output for the address line details!

    Note, I also had this same problem later on in my payload, so I tried to implement the same type of solution there only this time I get an error.
    This message is for C/AL programmers: A call to Newtonsoft.Json.JsonSerializer.Serialize failed with this message: Token StartObject in state Object would result in an invalid JSON object. Path 'requestedShipment'.
    ---------------------------
    OK
    ---------------------------
    

    This part of the payload is structured a little differently. This branch exists within the same overall requested shipment branch, but at one level of indentation. In the address example, the streetlines array was for the address branch within the shipper branch (for example), within the requested shipment. This raterequesttype array is a branch directly within the requested shipment. I don't see how to use

    bkqxeysljdeb.png
    WITH cduJSon DO BEGIN
      StartJSon;
      AddJSonBranch('accountNumber');
      AddToJSon('value','XXXXXXXXX');
      EndJSonBranch;
    
      AddJSonBranch('rateRequestControlParameters');
      AddToJSon('returnTransitTimes',TRUE);
      AddToJSon('servicesNeededOnRateFailure',FALSE);
      AddToJSon('rateSortOrder','SERVICENAMETRADITIONAL');
      EndJSonBranch;
    
      AddJSonBranch('requestedShipment');
    
       //Origin
      AddJSonBranch('shipper');
      AddJSonBranch2('address');
      CLEAR(ArrayText);
      ArrayText[1] := '5543 Aliquet St';
      ArrayText[2] := 'Ste B';
      AddArrayTxt2Json('streetLines',ArrayText);
      AddToJSon('city','Fort Dodge');
      AddToJSon('stateOrProvinceCode','GA');
      AddToJSon('postalCode','20783');
      AddToJSon('countryCode','US');
      AddToJSon('residential',FALSE);
      EndJSonBranch; //shipper
    
       //Destination
      AddJSonBranch('recipient');
      AddJSonBranch2('address');
      CLEAR(ArrayText);
      ArrayText[1] := '7292 Dictum Ave';
      ArrayText[2] := 'Unit 400';
      AddArrayTxt2Json('streetLines',ArrayText);
      AddToJSon('city','San Antonio');
      AddToJSon('stateOrProvinceCode','MI');
      AddToJSon('postalCode','47096');
      AddToJSon('countryCode','US');
      AddToJSon('residential',FALSE);
      EndJSonBranch; //recipient 
    
      CLEAR(ArrayText);
      ArrayText[1] := 'ACCOUNT';
      AddArrayTxt2Json('rateRequestType',ArrayText);  //ERROR HAPPENS HERE
    
      AddToJSon('pickupType','USE_SCHEDULED_PICKUP');
      StartJSonArray('requestedPackageLineItems');
      StartJSon;
      AddJSonBranch('weight');
      AddToJSon('units','LB');
      AddToJSon('value',22);
      EndJSonBranch;  //weight
    
      AddJSonBranch('dimensions');
      AddToJSon('length',10);
      AddToJSon('width',8);
      AddToJSon('height',4);
      AddToJSon('units','IN');
      EndJSonBranch;  //dimensions;
    
      EndJSon;
      EndJSonArray;
      AddToJSon('packagingType','YOUR_PACKAGING');
      EndJSonBranch; //requestedShipment
      EndJSon;
    
      JSon := GetJSon; 
    END;
    
  • ftorneroftornero Member Posts: 524
    Hello @jeighsohn,

    Could you post a complete JSON example that you try to create or at least the "rateRequestType" part?

    Regards.
  • ftorneroftornero Member Posts: 524
    Answer ✓
    Hello @jeighsohn,

    Assuming that the "rateRequestType" is like this
    "rateRequestType": ["ACCOUNT"],
    

    You need to add a new function to the codeunit:
    AddArrayTxtProperty2Json(VariableName : Text;ArrayTxt : ARRAY [10] OF Text)
    ArrayValue := '[';
    FOR i := 1 TO ARRAYLEN(ArrayTxt) DO BEGIN
      IF ArrayTxt[i] <> '' THEN
        IF ArrayValue = '[' THEN
          ArrayValue := ArrayValue + '"' + ArrayTxt[i] + '"'
        ELSE
          ArrayValue := ArrayValue + ',' + '"' + ArrayTxt[i] + '"';
    END;
    ArrayValue := ArrayValue + ']';
    
    JsonArray := JsonArray.Parse(ArrayValue);
    JsonProperty := JsonProperty.JProperty(VariableName, JsonArray);
    JsonSerialize :=JsonSerialize.JsonSerializer;
    JsonSerialize.Serialize(JsonTextWriter, JsonProperty);
    

    With these local variables
    Name	DataType	Subtype	Length
    JsonArray	DotNet	Newtonsoft.Json.Linq.JArray.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    JsonSerialize	DotNet	Newtonsoft.Json.JsonSerializer.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    ArrayValue	Text		
    i	Integer		
    JsonProperty	DotNet	Newtonsoft.Json.Linq.JProperty.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    

    By the way I forgot to list the local variable JsonObj in the AddArrayTxt2Json function, so the complete local variables for that function are:
    Name	DataType	Subtype	Length
    JsonObj	DotNet	Newtonsoft.Json.Linq.JObject.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    JsonArray	DotNet	Newtonsoft.Json.Linq.JArray.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    JsonSerialize	DotNet	Newtonsoft.Json.JsonSerializer.'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'	
    ArrayValue	Text		
    i	Integer		
    

    So the JSON part of "requestedShipment" is:
    
        AddJSonBranch('requestedShipment');
    
          //Origin
          AddJSonBranch('shipper');
            AddJSonBranch2('address');
              CLEAR(ArrayText);
              ArrayText[1] := '5543 Aliquet St';
              ArrayText[2] := 'Ste B';
              AddArrayTxt2Json('streetLines',ArrayText);
            AddToJSon('city','Fort Dodge');
            AddToJSon('stateOrProvinceCode','GA');
            AddToJSon('postalCode','20783');
            AddToJSon('countryCode','US');
            AddToJSon('residential',FALSE);
          EndJSonBranch; //shipper
    
          //Destination
          AddJSonBranch('recipient');
            AddJSonBranch2('address');
              CLEAR(ArrayText);
              ArrayText[1] := '7292 Dictum Ave';
              ArrayText[2] := 'Unit 400';
              AddArrayTxt2Json('streetLines',ArrayText);
            AddToJSon('city','San Antonio');
            AddToJSon('stateOrProvinceCode','MI');
            AddToJSon('postalCode','47096');
            AddToJSon('countryCode','US');
            AddToJSon('residential',FALSE);
          EndJSonBranch; //recipient
    
          CLEAR(ArrayText);
          ArrayText[1] := 'ACCOUNT';
          AddArrayTxtProperty2Json('rateRequestType',ArrayText);  //ERROR HAPPENS HERE
    
          AddToJSon('pickupType','USE_SCHEDULED_PICKUP');
    
        EndJSonBranch; //requestedShipment
    

    Regards
  • ftorneroftornero Member Posts: 524
    Hello @jeighsohn,

    I think that you can use only that last function (AddArrayTxtProperty2Json) and the code should be this:
        AddJSonBranch('requestedShipment');
    
          //Origin
          AddJSonBranch('shipper');
            AddJSonBranch('address');
              CLEAR(ArrayText);
              ArrayText[1] := '5543 Aliquet St';
              ArrayText[2] := 'Ste B';
              AddArrayTxtProperty2Json('streetLines',ArrayText);
            EndJSonBranch; // address
            AddToJSon('city','Fort Dodge');
            AddToJSon('stateOrProvinceCode','GA');
            AddToJSon('postalCode','20783');
            AddToJSon('countryCode','US');
            AddToJSon('residential',FALSE);
          EndJSonBranch; //shipper
    
          //Destination
          AddJSonBranch('recipient');
            AddJSonBranch('address');
              CLEAR(ArrayText);
              ArrayText[1] := '7292 Dictum Ave';
              ArrayText[2] := 'Unit 400';
              AddArrayTxtProperty2Json('streetLines',ArrayText);
            EndJSonBranch; // address
            AddToJSon('city','San Antonio');
            AddToJSon('stateOrProvinceCode','MI');
            AddToJSon('postalCode','47096');
            AddToJSon('countryCode','US');
            AddToJSon('residential',FALSE);
          EndJSonBranch; //recipient
    
          CLEAR(ArrayText);
          ArrayText[1] := 'ACCOUNT';
          AddArrayTxtProperty2Json('rateRequestType',ArrayText);  //ERROR HAPPENS HERE
    
          AddToJSon('pickupType','USE_SCHEDULED_PICKUP');
    
        EndJSonBranch; //requestedShipment
    

    Regards
  • jeighsohnjeighsohn Member Posts: 14
    Thank you, once again. I never would have thought to do that since I'm unfamiliar with all these assembly types. It worked like a charm and my payload appears to match what FedEx is after.

    I was able to figure out the missing variable in the first reply, but I appreciate you updating here for future reference if anyone else has similar needs to those covered in this thread.

    Can you recommend any resources that I might be able to use to learn more about working with the newtonsoft.dll? I'm sure I'll encounter more issues when I get to the point of parsing the response and I'd love to be more self-reliant.
  • ftorneroftornero Member Posts: 524
    Hello @jeighsohn,

    Sorry but I don't have any documentation or resource about the newtonsoft.dll, is more about working with JSON, using Google and trial and error.

    Regards.
Sign In or Register to comment.