Skip to main content

Notifications

Announcements

No record found.

Microsoft Dynamics AX forum
Unanswered

Serializing and deserializing Json in AX

Posted on by 299

Hi all,

I recently had a project that involved getting data into and out of AX 2012 using Json strings and thought I'd blog it here. I know D365 is being used more and more, but thought it might still be useful for someone.

The requirement was to integrate various transactions with a 3rd party Retail package, so we exposed some methods to a web service hosted in AX (Inbound ports form). Reading elsewhere about AX 2012 not having lots of Json support built-in, we decided to use the free NewtonSoft Json.NET library.

Add the DLL as a reference in AX (after copying it to both server\bin and client\bin directories, or using the GAC). 

Serialization

Serializing to a Json string works perfectly in AX by using a DataContractAttribute class to build the data. For example, if you need to send Customer information, you can create a class as follows:

[DataContractAttribute]
public class CustMaster
{
    CustAccount     accountNum;
    Name            firstName,
                    lastName;
    PhoneLocal      custPhone;
    Email           custMail;
}

[DataMemberAttribute("Customer code")]
public CustAccount parmCustAccountNum(CustAccount _accountNum = accountNum)
{
    accountNum = _accountNum;
    return accountNum;
}

[DataMemberAttribute("First name")]
public Name parmFirstName(Name _firstName = firstName)
{
    firstName = _firstName;
    return firstName;
}

[DataMemberAttribute("Last name")]
public Name parmLastName(Name _lastName = lastName)
{
    lastName = _lastName;
    return lastName;
}

[DataMemberAttribute("Phone 1")]
public PhoneLocal parmPhone(PhoneLocal _custPhone = custPhone)
{
    custPhone = _custPhone;
    return custPhone;
}

[DataMemberAttribute("Email")]
public Email parmEmail(Email _custMail = custMail)
{
    custMail = _custMail;
    return custMail;
}

The names corresponding to each variable, such as "Phone 1" are created as the tags in the Json string.

Using the above contract, we can create a Json string to send out by looping through the CustTable and adding each record to the string as follows:

[SysEntryPointAttribute]
public str getCustomers()
{
    CustTable                       cTable;
    str                             custStr = '';
    System.Exception                clrException;
    System.Object                   custObj;
    InteropPermission               permission;
    System.Collections.ArrayList    list = new System.Collections.ArrayList();
    CustMaster                      custContract;
    
    try
    {
        permission = new InteropPermission(InteropKind::ClrInterop);
        permission.assert();

        while select cTable
        {
            custContract = new CustMaster();
            custContract.parmCustAccountNum(cTable.AccountNum);
            custContract.parmFirstName(cTable.Name());
            custContract.parmLastName(cTable.Name());
            custContract.parmPhone(cTable.phoneLocal());
            custContract.parmEmail(cTable.email());

            custObj = CLRInterop::getObjectForAnyType(custContract);
            list.Add(custObj);
        }

        if (list.get_Count() > 0)
        {
            custStr = Newtonsoft.Json.JsonConvert::SerializeObject(list, Newtonsoft.Json.Formatting::Indented);
        }

        CodeAccessPermission::revertAssert();
    }
    catch (Exception::CLRError)
    {
        // BP deviation documented
        clrException = CLRInterop::getLastException();
        if (clrException != null)
        {
            //BP Deviation Documented
            info(CLRInterop::getAnyTypeForObject(clrException.get_Message()));
            while (clrException != null)
            {
                clrException = clrException.get_InnerException();
                if (clrException == null)
                    break;
                checkFailed(CLRInterop::getAnyTypeForObject(clrException.ToString()));
            }
        }
        throw error("Failed");
    }

The output will look like the following:

[
  {
    "Customer code": "CUS001",
    "Email": "",
    "First name": "TEST CUSTOMER ONE",
    "Last name": "TEST CUSTOMER ONE",
    "Phone 1": ""
  },
  {
    "Customer code": "CUS002",
    "Email": "",
    "First name": "TEST CUSTOMER TWO",
    "Last name": "TEST CUSTOMER TWO",
    "Phone 1": ""
  },
  {
    "Customer code": "CUS003",
    "Email": "",
    "First name": "TEST CUSTOMER THREE",
    "Last name": "TEST CUSTOMER THREE",
    "Phone 1": ""
  }
]

Deserialization

For deserializing, it works slightly different. It doesn't seem like you can cast to an AX DataContractAttribute class from a .NET object (or at least I wasn't able to), so you will need to create a C# class and add that DLL as a reference in your AX environment as well. You can then call the Get_<parameterName>() methods directly from the .NET class.

Create the .NET class:

using System;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization;

namespace RetailClasses
{
    [DataContract]
    public class CustOrders
    {
        [DataMember(Name = "CustomerAccount")]
        public string AccountNum { get; set; }

        [DataMember(Name = "CustomerReference")]
        public string CustReference { get; set; }

        [DataMember]
        public DateTime ShipmentDate { get; set; }

        [DataMember(Name = "SalesOrderLines")]
        public ArrayList OrderLines { get; set; }
    }

    [DataContract]
    public class CustOrderLines
    {
        [DataMember(Name = "ItemNumber")]
        public string ItemId { get; set; }

        [DataMember(Name = "Qty")]
        public float Quantity { get; set; }

        [DataMember(Name = "Colour")]
        public string ItemColour { get; set; }

        [DataMember(Name = "Size")]
        public string ItemSize { get; set; }
    }
}

After adding this reference in AX, you can use the code as follows:

[SysEntryPointAttribute]
public boolean createOrder(str _orderStr)
{
    InteropPermission               permission;
    System.Exception                clrException;
    Newtonsoft.Json.Linq.JArray     list = new Newtonsoft.Json.Linq.JArray();
    Newtonsoft.Json.Linq.JObject    jObject;
    System.Collections.IEnumerator  lEnum;
    RetailClasses.CustOrders        orders = new RetailClasses.CustOrders();

    void processSalesQuote(RetailClasses.CustOrders _custOrders)
    {
        CustomerReference       custRef;
        CustAccount             custAccount;
        date                    shipDate;
        //SalesOrderLines;
        ItemId                  itemNumber;
        Qty                     qtyOrdered;
        EcoResItemColorName     colour;
        EcoResItemSizeName      size;
        RetailClasses.CustOrderLines     orderLines = new RetailClasses.CustOrderLines();
        System.Collections.ArrayList    linesList = new  System.Collections.ArrayList();
        System.Collections.IEnumerator  lEnum;
        Newtonsoft.Json.Linq.JObject    jObject;
    
        custRef = _custOrders.get_CustReference();
        custAccount = _custOrders.get_AccountNum();
        shipDate = _custOrders.get_ShipmentDate();

        linesList = _custOrders.get_OrderLines();
        lEnum = linesList.GetEnumerator();

        // Create Header
        ...
        // create lines
        while (lEnum.MoveNext())
        {
            jObject = lEnum.get_Current();
            orderLines = Newtonsoft.Json.JsonConvert::DeserializeObject(jObject.ToString(), orderLines.GetType());

            size = orderLines.get_ItemSize();
            colour = orderLines.get_ItemColour();
            itemNumber = orderLines.get_ItemId();
            qtyOrdered = orderLines.get_Quantity();
            ...
        }
    }

    try
    {
        permission = new InteropPermission(InteropKind::ClrInterop);
        permission.assert();

        list = Newtonsoft.Json.JsonConvert::DeserializeObject(_orderStr, list.GetType());
        lEnum = list.GetEnumerator();

        while (lEnum.MoveNext())
        {
            jObject = lEnum.get_Current();

            orders = Newtonsoft.Json.JsonConvert::DeserializeObject(jObject.ToString(), orders.GetType());

            processSalesQuote(orders);
        }
    }
    catch (Exception::CLRError)
    {
        // BP deviation documented
        clrException = CLRInterop::getLastException();
        if (clrException != null)
        {
            //BP Deviation Documented
            info(CLRInterop::getAnyTypeForObject(clrException.get_Message()));
            while (clrException != null)
            {
                clrException = clrException.get_InnerException();
                if (clrException == null)
                    break;
                checkFailed(CLRInterop::getAnyTypeForObject(clrException.ToString()));
            }
        }
        ret = checkFailed("Failed");
    }
}
  • Ankit Mehta Profile Picture
    Ankit Mehta 20 on at
    RE: Serializing and deserializing Json in AX

     Ammar Salah - I am not able to download the XPO. Could you please share again.

    I am also getting below error:

    Error executing code: List (object), method new called with invalid parameters.

  • Ankit Mehta Profile Picture
    Ankit Mehta 20 on at
    RE: Serializing and deserializing Json in AX

    Hi,  I am also getting below error. Could you please share the fix.

    Error executing code: List (object), method new called with invalid parameters.

  • Dynamics 365 Profile Picture
    Dynamics 365 on at
    RE: Serializing and deserializing Json in AX

    IS it resolved i am getting the same error please guide

  • Martin Dráb Profile Picture
    Martin Dráb 225,490 Super User on at
    RE: Serializing and deserializing Json in AX

    I'm sorry, but it's difficult to help you with the error unless you tell which statement is throwing it.

    NewtonSoft.Json can be downloaded from https://www.newtonsoft.com/json. But it seems that you already have it installed correctly, if your code compiles and run. The error you've mentioned is about some X++ code.

  • Dynamics 365 Profile Picture
    Dynamics 365 on at
    RE: Serializing and deserializing Json in AX

    Actually i am getting  

    Microsoft Dynamics anytype cannot be marshaled to CLR Object. 

    pastedimage1661361440796v1.png

    Please share the path from where newtonsoft downloaded. i Downloaded it copied to client and server bin and then added from reference in Ax2012 AOT, Written Data Contract Class and Serialization Class. Please help me .

  • Albert Akuamoah Profile Picture
    Albert Akuamoah 260 on at
    RE: Serializing and deserializing Json in AX

    I just came across a similar need to see the data based on the display order. I understand that the API will not care, however sometimes for debugging purposes, you might want to put to JSON strings side by side for comparison.

    I made a slight modification to get the result I wanted.

    You can add this to the declaration of serializeDataContract

    SysOperationDisplayOrderAttribute  displayOrderAttribute;

       Set dataElementSet   = new Set(Types::Container);

       int displayOrder;

    Then modify the code  in the loop a little

    dataMemberName = dataMemberAttribute.Name();

                   if (!dataMemberName)

                   {

                       dataMemberName = dictMethod.name();

                   }

                   displayOrderAttribute = dictMethod.getAttribute(classStr(SysOperationDisplayOrderAttribute));

                   if(displayOrderAttribute != null)

                   {

                       displayOrder = str2int(displayOrderAttribute.displayOrderKey());

                   }

                   else

                   {

                       displayOrder = 9999;

                   }

                   memberValues = [dataMemberName, dictMethod.name(), dictMethod.returnType()];

                   [ memberName, memberMethodName, memberMethodReturn] = memberValues;

                   dataElementSet.add([displayOrder,memberName, memberMethodName, memberMethodReturn]);

                   /*

                   jsonWriter.WritePropertyName(memberName);

                   if (memberMethodReturn == Types::Class)

                   {

                       this.serializeObject(dictClass.callObject(memberMethodName, _dataContract));

                   }

                   else

                   {

                       this.writePrimitiveValue(memberMethodReturn, dictClass.callObject(memberMethodName, _dataContract));

                   }

                   */

    Lastly after the loop is done , read from the set. It will sort the elements based on the display order attribute

    se = dataElementSet.getEnumerator();

       while(se.moveNext())

       {

           [displayOrder,memberName, memberMethodName, memberMethodReturn] = se.current();

           jsonWriter.WritePropertyName(memberName);

           if (memberMethodReturn == Types::Class)

           {

               this.serializeObject(dictClass.callObject(memberMethodName, _dataContract));

           }

           else

           {

               this.writePrimitiveValue(memberMethodReturn, dictClass.callObject(memberMethodName, _dataContract));

           }

       }

  • Martin Dráb Profile Picture
    Martin Dráb 225,490 Super User on at
    RE: Serializing and deserializing Json in AX

    Evan, what serialized data does the string contain? For example, is it an array of objects or what? Do you have any particular problem?

    Also, does it belong to this thread? You should probably create a new thread and explain your particular problem there.

  • Evan Adamson Profile Picture
    Evan Adamson 105 on at
    RE: Serializing and deserializing Json in AX

    Somesh, were you able to figure out how to deserialize the entire json string?

  • Somesh Profile Picture
    Somesh 55 on at
    RE: Serializing and deserializing Json in AX

    Hi Ammar,

    I have checked that out, and for json like following:

    {

      "success": true,

      "data": [

          {

              "CUST_GROUP_ID": 1,

              "CUST_GROUP_NAME": "Customer Group 1",

              "INSERT_FLAG": "Y",

              "RECORD_SENT_FLAG": "N",

              "ERROR_MESSAGE": null,

              "LAST_UPDATE_DATE": "2020-03-01T13:29:29.000Z",

              "LAST_UPDATED_BY": 0,

              "CREATION_DATE": "2020-03-01T13:29:29.000Z",

              "CREATED_BY": 0

          },

          {

              "CUST_GROUP_ID": 2,

              "CUST_GROUP_NAME": "Customer Group 2",

              "INSERT_FLAG": "Y",

              "RECORD_SENT_FLAG": "N",

              "ERROR_MESSAGE": null,

              "LAST_UPDATE_DATE": "2020-03-01T13:29:29.000Z",

              "LAST_UPDATED_BY": 0,

              "CREATION_DATE": "2020-03-01T13:29:29.000Z",

              "CREATED_BY": 0

          }

      ]

    }

    I am trying to create a job with below code

    .

    RetailWebRequest            request;

       RetailWebResponse           response;

       str                         rawResponse;

       RetailCommonWebAPI          webApi;

       System.IO.Stream            requestStream, responseStream;

       System.IO.StreamReader      reader;

       Object                      deserializedContract;

       List                        list;

       ListIterator                listIterator;

       CreateCustGroupContract  custGroupContract;

       CustGroupList            custGroupList;

       webApi      = RetailCommonWebAPI::construct();

       request     = RetailWebRequest::newUrl("URL");

       response    = webApi.getResponse(request);

       rawResponse = response.parmData();

       custGroupList   = new CustGroupList();

       listIterator = new ListIterator(custGroupList.parmDataDetail());

       while (listIterator.more())

       {

           custGroupContract    = SMCFormJsonSerializer::deserializeObject(classIdGet(custGroupContract), listIterator.value());

           info(strFmt('%1', custGroupContract.parmCustGroupId()));

           info(custGroupContract.parmCustGroupName());

           listIterator.next();

       }

    but its only return "CUST_GROUP_ID": 1, "CUST_GROUP_NAME": "Customer Group 1".

    How can we achieve all the json string like "CUST_GROUP_ID": 1, "CUST_GROUP_NAME": "Customer Group 1", "CUST_GROUP_ID": 2, "CUST_GROUP_NAME": "Customer Group 2".

    Can you please help me to achieve the requirement.

    Thanks,

    Somesh

  • Somesh Profile Picture
    Somesh 55 on at
    RE: Serializing and deserializing Json in AX

    How to get the info result for multiple record under JSON.

Helpful resources

Quick Links

Replay now available! Dynamics 365 Community Call (CRM Edition)

Catch up on the first D365 Community Call held on 7/10

Community Spotlight of the Month

Kudos to Saurav Dhyani!

Congratulations to the June Top 10 community leaders!

These stars go above and beyond . . .

Leaderboard

#1
André Arnaud de Calavon Profile Picture

André Arnaud de Cal... 287,696 Super User

#2
Martin Dráb Profile Picture

Martin Dráb 225,490 Super User

#3
nmaenpaa Profile Picture

nmaenpaa 101,148

Leaderboard

Featured topics

Product updates

Dynamics 365 release plans