Announcements
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"); } }
*This post is locked for comments
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.
Hi, I am also getting below error. Could you please share the fix.
Error executing code: List (object), method new called with invalid parameters.
IS it resolved i am getting the same error please guide
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.
Actually i am getting
Microsoft Dynamics anytype cannot be marshaled to CLR Object.
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 .
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));
}
}
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.
Somesh, were you able to figure out how to deserialize the entire json string?
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
How to get the info result for multiple record under JSON.
André Arnaud de Cal...
294,233
Super User 2025 Season 1
Martin Dráb
232,982
Most Valuable Professional
nmaenpaa
101,158
Moderator