AXForum  
Вернуться   AXForum > Microsoft Dynamics AX > DAX Blogs
DAX
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск Все разделы прочитаны

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 23.12.2021, 05:30   #1  
Blog bot is offline
Blog bot
Участник
 
25,459 / 846 (79) +++++++
Регистрация: 28.10.2006
domhk: Taking JSON input with D365 service and deserialize into contract class
Источник: https://domhk.blogspot.com/2021/12/t...rvice-and.html
==============

Recently I wanted to create a custom serivce in D365 taking in a JSON input. I did my usual google search but ended up needing additional trial and error to complete the use case. Here I'll share the solution I have..
Firstly, create a contract class which represent the a Json object
///
/// The Student Information object contains the Student ID and Name
///
[DataContractAttribute]
class StudentInfoContract
{
str studentId;
str studentName;

[DataMemberAttribute("Student Id")]
public str parmStudentId(str _studentId = studentId)
{
studentId = _studentId;

return studentId;
}

[DataMemberAttribute("Student Name")]
public str parmStudentName(str _studentName = studentName)
{
studentName = _studentName;

return studentName;
}

}

Then, in the service contract class, define a List of Str which will take in the Json
///
/// Contains a list of students
///
[DataContractAttribute]
class StudentsContract
{
List studentList = new List(Types::String);

[
DataMemberAttribute("Student list"),
DataCollectionAttribute(Types::Class, classStr(StudentInfoContract))
]
public List parmStudentList(List _studentList = studentList)
{
if (!prmIsDefault(_studentList))
{
studentList = _studentList;
}

return studentList;
}

}

At run time, when calling code provides a list of Json objects, it'll be come a list of JObjects in X++. We'll loop through the list and use FormDeserializer to deserialize each JObject into a contract instance
List studentList = StudentsContract.parmStudentList();

ListEnumerator enumerator = studentList.getEnumerator();

while (enumerator.moveNext())
{
Newtonsoft.Json.Linq.JObject jObj = enumerator.current();

studentInfoContract = FormJsonSerializer::deserializeObject(classNum(StudentInfoContract),jObj.ToString());

str studentId, studentName;

studentId = studentInfoContract.parmStudentId();
studentName = studentInfoContract.parmStudentName();

Info(strFmt("Studnet # %1: %2", studentId, studentName));
}

In the end it's quite simple, and also no need to create C# class representing the data contract as well.

This posting is provided "AS IS" with no warranties, and confers no rights.


Источник: https://domhk.blogspot.com/2021/12/t...rvice-and.html
Старый 24.12.2021, 12:00   #2  
gl00mie is offline
gl00mie
Участник
MCBMSS
Most Valuable Professional
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
3,684 / 5788 (200) ++++++++++
Регистрация: 28.11.2005
Адрес: Москва
Записей в блоге: 3
Это называется "закат солнца вручную". В контрактах сервисов коллекции стоит "декорировать" атрибутом AifCollectionTypeAttribute - тогда и не придется их десериализовывать вручную.
X++:
[
  DataMemberAttribute("Student list"),
  AifCollectionTypeAttribute("return", Types::Class, classStr(StudentInfoContract)),
  AifCollectionTypeAttribute("_studentList", Types::Class, classStr(StudentInfoContract))
]
public List parmStudentList(List _studentList = studentList)
PS. Сама постановка задачи уже наводит на подозрения:
Цитата:
Recently I wanted to create a custom serivce in D365 taking in a JSON input
Зачем это, спрашивается, в D365FO делать кастомный сервис, который на входе получал бы JSON, а не нормальный типизированный контракт?..

Последний раз редактировалось gl00mie; 24.12.2021 в 12:03.
За это сообщение автора поблагодарили: mazzy (2).
Старый 24.12.2021, 13:41   #3  
mazzy is offline
mazzy
Участник
Аватар для mazzy
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
29,472 / 4494 (208) ++++++++++
Регистрация: 29.11.2001
Адрес: Москва
Записей в блоге: 10
Цитата:
Сообщение от Blog bot Посмотреть сообщение
X++:
    [
        DataMemberAttribute("Student list"),
    ]
пробелы в названии...
Какие заботливо разложенные грабли.
__________________
полезное на axForum, github, vk, coub.
Старый 29.12.2021, 15:20   #4  
DSPIC is offline
DSPIC
Боец
 
1,077 / 1234 (44) ++++++++
Регистрация: 11.04.2008
Давайте разбираться.

Цитата:
Сообщение от gl00mie Посмотреть сообщение
Это называется "закат солнца вручную".
...
PS. Сама постановка задачи уже наводит на подозрения:
Зачем это, спрашивается, в D365FO делать кастомный сервис, который на входе получал бы JSON, а не нормальный типизированный контракт?..
Про закат вручную:
Тут не вручную, а помощью стандартного класса FormJsonSerializer (D365).

Во-вторых - задача, когда на вход извне впаривают сырой JSON (или XML) стоит сплошь и рядом: "Мы вам будем передавать XML\JSON строку и точка. Мы не можем переделать нашу систему под вашу, чтобы завязаться на ваш веб сервис. Или можем, но за большие деньги, где-то через года пол. Наши клиенты по всему миру, мы работаем только так".
А какие есть альтернативы? Чаще всего для jSON встречается ручной парсинг строк strScan, strFind и т.д. Изредка попадаются продвинутые выринты - используют Newton.JSon.dll. C XML немного проще - стандартный класс пробега по XML с большего освоили, но это всё-равно абсолютный хардкод.

Про аттрибут:
Класс FormJsonSerializer понимает только DataCollectionAttribute. Более того, для дата-контрактов с их parm* методами этот аттрибут идеально подходит (на входе один параметр с таким же типом что и на выходе).

AifCollectionTypeAttribute пришел из AX2012. Он больше подходит для описания сервис-операций, где может быть несколько входных параметров, разных типов, а на выходе что-то третье. Но в AX2012 других выриантов не было.

Для сравнения:

X++:
// D365:
[
  DataMemberAttribute("Student list"),
  DataCollectionAttribute(Types::Class, classStr(StudentInfoContract))
]
public List parmStudentList(List _studentList = studentList)

// ИЛИ AX2012:
[
  DataMemberAttribute("Student list"),
  AifCollectionTypeAttribute("return", Types::Class, classStr(StudentInfoContract)),
  AifCollectionTypeAttribute("_studentList", Types::Class, classStr(StudentInfoContract))
]
public List parmStudentList(List _studentList = studentList)
Т.е.второй вариант избыточный для дата-контрактов имхо.
Единственное - не уверен, понимает ли сериализатор D365 этот атрибут при описании контрактов для веб-сервисов - не приходилось применять, но как бы must Have.



Цитата:
Сообщение от mazzy Посмотреть сообщение
пробелы в названии...
Какие заботливо разложенные грабли.
В XML так нельзя. В jSON можно.



Претензия к автору блога у меня только одна: корее всего куски кода он где-то вырвал и не тестировал. Или спешил, промазал и недопроверил
X++:
while (enumerator.moveNext())
        {
            Newtonsoft.Json.Linq.JObject    jObj = enumerator.current();

            studentInfoContract = FormJsonSerializer::deserializeObject(classNum(StudentInfoContract),jObj.ToString());

            str studentId, studentName;

            studentId   = studentInfoContract.parmStudentId();
            studentName = studentInfoContract.parmStudentName();

            Info(strFmt("Studnet # %1: %2", studentId, studentName));
        }
естественно, вот на этом получит моментальный взлёт, пытаясь закастить аксаптовский класс в CLR тип.

X++:
Newtonsoft.Json.Linq.JObject    jObj = enumerator.current();
Вообще не понятно, как это должно было работать. С другой стороны - юридически, он прикрылся "This posting is provided "AS IS" with no warranties, and confers no rights."

Наверное он так хотел сделать:
X++:
public static void main(Args _args)
    {
        StudentsContract        studentsContract;

        void addStudent(str _id, str _name)
        {
            StudentInfoContract student = new StudentInfoContract();

            student.parmStudentId(_id);
            student.parmStudentName(_name);

            studentsContract.parmStudentList().addEnd(student);
        }

        studentsContract = new StudentsContract();

        studentsContract.parmStudentList(new List(Types::Class));
        
        addStudent("1", "First");
        addStudent("2", "Second");

        // Serialize
        str jSon = FormJsonSerializer::serializeClass(studentsContract);

        // Deserialize
        studentsContract = FormJsonSerializer::deserializeObject(classNum(StudentsContract), jSon);

        ListEnumerator enumerator = studentsContract.parmStudentList().getEnumerator();

        while (enumerator.moveNext())
        {
            StudentInfoContract student = enumerator.current();

            info(strFmt("Studnet # %1: %2", student.parmStudentId(), student.parmStudentName()));
        }
    }

Последний раз редактировалось DSPIC; 29.12.2021 в 15:25.
За это сообщение автора поблагодарили: sukhanchik (4), vmoskalenko (6).
Старый 29.12.2021, 22:57   #5  
gl00mie is offline
gl00mie
Участник
MCBMSS
Most Valuable Professional
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
3,684 / 5788 (200) ++++++++++
Регистрация: 28.11.2005
Адрес: Москва
Записей в блоге: 3
Цитата:
Сообщение от DSPIC Посмотреть сообщение
Тут не вручную, а помощью стандартного класса FormJsonSerializer (D365).
Имя стандартного класса как бы намекает, что он задумывался больше для форм, чем для сервисов
Цитата:
Сообщение от DSPIC Посмотреть сообщение
Во-вторых - задача, когда на вход извне впаривают сырой JSON (или XML) стоит сплошь и рядом: "Мы вам будем передавать XML\JSON строку и точка. Мы не можем переделать нашу систему под вашу, чтобы завязаться на ваш веб сервис.".
Наверно, так тоже бывает, но в данном случае автор сам же пишет: я решил сделать сервис, вот его контракт, в контракте есть коллекция классов. Он не пишет, что парсит чей-то чужой JSON - напротив, он получает на вход именно тот контракт, который заказывал, только "недодесериализованный", полусырой, так сказать. Скажу больше: кастомные сервисы в D365FO доступны извне и как REST (с JSON-ом на входе), и как SOAP-сервисы (c XML на входе), но из-за того, что автор использовал не тот атрибут в контракте, он и для SOAP-варианта своего сервиса получил полусырой WSDL, в котором коллекция классов будет представлена как просто абстрактный массив не пойми чего. Использованием неправильного атрибута автор и себе лишнюю работу создал, и клиентам, которые попытаются WSDL сервиса посмотреть, проблем подкинул.
Цитата:
Сообщение от DSPIC Посмотреть сообщение
Про аттрибут:
Класс FormJsonSerializer понимает только DataCollectionAttribute. Более того, для дата-контрактов с их parm* методами этот аттрибут идеально подходит (на входе один параметр с таким же типом что и на выходе).
Идеально подходит для любителей ручного разбора JSON и полусырых WSDL-ей
Цитата:
Сообщение от DSPIC Посмотреть сообщение
AifCollectionTypeAttribute пришел из AX2012.
Какая разница, откуда пришел атрибут? DataContractAttribute тоже был в AX2012, и что, может, на этом основании его не использовать? Приложение D365FO на 90-95% - это приложение AX2012 R3, и если какие-то вещи из AX2012 продолжают успешно работать в D365FO, а альтернатива им - закат солнца вручную, то какой смысл от них отказываться?
Цитата:
Сообщение от DSPIC Посмотреть сообщение
Он больше подходит для описания сервис-операций, где может быть несколько входных параметров, разных типов, а на выходе что-то третье.
Либо на входе сервисного метода - коллекция одних типов, а на выходе - коллекция других типов.
Цитата:
Сообщение от DSPIC Посмотреть сообщение
Но в AX2012 других выриантов не было.
Во-первых, были альтернативы, посмотрите хотя бы на полёт фантазии в FormLetterContract:
X++:
[DataMemberAttribute]
public str parmDataSourceRecordsPacked(str _dataSourceRecordsPacked = '')
{
    if (prmIsDefault(_dataSourceRecordsPacked))
    {
        return SysOperationHelper::base64Encode(this.parmDataSourceRecordMapPacked());
    }

    return SysOperationHelper::base64Encode(this.parmDataSourceRecordMapPacked(SysOperationHelper::base64Decode(_dataSourceRecordsPacked)));
}
Мощно?!
Во-вторых, никто не мешал даже в AX2012 определить свой класс атрибута и парсить вручную что угодно аналогично тому, как это теперь делает FormJsonSerializer.
Цитата:
Сообщение от DSPIC Посмотреть сообщение
Для сравнения:
X++:
// D365:
[
  DataMemberAttribute("Student list"),
  DataCollectionAttribute(Types::Class, classStr(StudentInfoContract))
]
public List parmStudentList(List _studentList = studentList)

// ИЛИ AX2012:
[
  DataMemberAttribute("Student list"),
  AifCollectionTypeAttribute("return", Types::Class, classStr(StudentInfoContract)),
  AifCollectionTypeAttribute("_studentList", Types::Class, classStr(StudentInfoContract))
]
public List parmStudentList(List _studentList = studentList)
Мы же помним, что кастомные сервисы в D365FO доступны извне одновременно и как REST, и как SOAP-сервисы, причем в REST метаданные, мягко говоря, победнее. Сделаем в контракте два поля, одно - studentList - с атрибутом DataCollection, а другое - studentListAif - с атрибутом AifCollectionType. И посмотрим теперь, как такие поля в контракте сервиса D365FO выглядят в WSDL:
PHP код:
<xs:complexType name="StudentsContract">
 <
xs:complexContent mixed="false">
  <
xs:extension xmlns:q8="http://schemas.datacontract.org/2004/07/Microsoft.Dynamics.Ax.Xpp" base="q8:XppObjectBase">
   <
xs:sequence>
    <
xs:element xmlns:q9="http://schemas.microsoft.com/2003/10/Serialization/Arrays"
                
minOccurs="0" name="StudentList"
              
nillable="true" type="q9:ArrayOfanyType"/>
    <
xs:element minOccurs="0" name="StudentListAif"
              
nillable="true" type="tns:ArrayOfStudentInfoContract"/>
   </
xs:sequence>
  </
xs:extension>
 </
xs:complexContent>
</
xs:complexType>
<
xs:element name="StudentsContract" nillable="true" type="tns:StudentsContract"/>
<
xs:complexType name="ArrayOfStudentInfoContract">
 <
xs:sequence>
  <
xs:element minOccurs="0" maxOccurs="unbounded" name="StudentInfoContract" nillable="true" type="tns:StudentInfoContract"/>
 </
xs:sequence>
</
xs:complexType>
<
xs:element name="ArrayOfStudentInfoContract" nillable="true" type="tns:ArrayOfStudentInfoContract"/>
<
xs:complexType name="StudentInfoContract">
 <
xs:complexContent mixed="false">
  <
xs:extension xmlns:q10="http://schemas.datacontract.org/2004/07/Microsoft.Dynamics.Ax.Xpp" base="q10:XppObjectBase">
   <
xs:sequence>
    <
xs:element minOccurs="0" name="StudentId" nillable="true" type="xs:string"/>
    <
xs:element minOccurs="0" name="StudentName" nillable="true" type="xs:string"/>
   </
xs:sequence>
  </
xs:extension>
 </
xs:complexContent>
</
xs:complexType
Видим, что для DataCollection мы получили StudentList в виде ArrayOfanyType, без какой-либо дальнейшей детализации.
А для AifCollectionType мы получили StudentListAif в виде ArrayOfStudentInfoContract с полной разблюдовкой: мы видим в WSDL и сам StudentInfoContract, и то, что он состоит из двух строковых полей: StudentId и StudentName. И повторюсь: для AifCollectionType платформа занимается тем, что десериализует для вашего сервиса коллекцию объектов и заодно проверяет, что объекты в коллекции - нужного типа, а не абы что. По-моему, это всё в совокупности стоит того, чтобы добавить методу лишний атрибут.

Последний раз редактировалось gl00mie; 29.12.2021 в 23:28.
Старый 30.12.2021, 00:59   #6  
DSPIC is offline
DSPIC
Боец
 
1,077 / 1234 (44) ++++++++
Регистрация: 11.04.2008
Оккей, давай по существу. Как будешь решать задачу десериализации?
Цитата:
Recently I wanted to create a custom service in D365 taking in a JSON input.
У тебя на входе строка JSON.
Старый 30.12.2021, 01:42   #7  
ax_mct is offline
ax_mct
Banned
 
2,548 / 1091 (0) ++++++++
Регистрация: 10.10.2005
Адрес: Westlands
Цитата:
Сообщение от DSPIC Посмотреть сообщение
Оккей, давай по существу. Как будешь решать задачу десериализации?


У тебя на входе строка JSON.
Не уверен что в тему так как возможно в теме больше про паттерны дизайна,
но в AX 2012 мне очень пригодился метод

RetailCommonWebAPI::getMapFromJSONString()
Старый 30.12.2021, 17:26   #8  
gl00mie is offline
gl00mie
Участник
MCBMSS
Most Valuable Professional
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
Лучший по профессии 2009
 
3,684 / 5788 (200) ++++++++++
Регистрация: 28.11.2005
Адрес: Москва
Записей в блоге: 3
Цитата:
Сообщение от DSPIC Посмотреть сообщение
Оккей, давай по существу. Как будешь решать задачу десериализации? У тебя на входе строка JSON.
Не нужно пытаться подменять одну задачу на другую в попытках доказать свою правоту При разработке кастомного сервиса в D365FO нет задачи десериализации строки JSON на входе (хотя бы потому, что на входе может быть XML - если сервис будут дергать как SOAP). Нужно думать о том, чтобы разработать внятный контракт сервиса, а об остальном должны позаботиться платформа и клиент, вызывающий сервис. Если вы разработали контракт, в котором передается строка в JSON (ну мало ли - обстоятельства или контрагенты заставили), то это - совсем другая история. Я приводил штатный пример из FormLetterContract: на вход может подаваться строка, где в base64 закодирован бинарный контейнер, в который запакована коллекция записей произвольной таблицы. Ну и что это доказывает? Что все гланды на свете надо удалять через.. выхлопную трубу?
Платформа заботится о разработчике, беря на себя десериализацию и проверку корректности входного контракта сервиса - ей надо лишь чуть подсказать с помощью правильных атрибутов, если на входе ожидаются объекты-коллекции. Вообще вся тема, как мне лично представляется, не про вопрос выбора того или иного атрибута для поля-коллекции в контракте, не про то, что какие-то атрибуты кому-то нравятся больше, а кому-то - меньше, что от одних исходит аромат высоких стандартов разработки, а от других - затхлый запашок предыдущей версии системы, которая уже и с поддержки снята, и AIF-а вашего в новой версии нет, и вообще... Тема, по-моему, про то, сколько проблем и ручного труда можно поиметь, если использовать неправильный атрибут. Используйте правильный атрибут - и тратьте больше времени на бизнес-логику, а не на ковыряние в JSON-ах, парных квадратных скобках и форматах кодирования дат. Это всё уже давно решено в платформе, зачем изобретать велосипед?..

PS. С наступающим Новым годом
Старый 30.12.2021, 19:57   #9  
DSPIC is offline
DSPIC
Боец
 
1,077 / 1234 (44) ++++++++
Регистрация: 11.04.2008
Цитата:
Сообщение от Fattung
Taking JSON input with D365 service and deserialize into contract objects
Цитата:
Сообщение от Fattung
Recently I wanted to create a custom service in D365 taking in a JSON input.
Цитата:
Сообщение от gl00mie Посмотреть сообщение
Не нужно пытаться подменять одну задачу на другую в попытках доказать свою правоту При разработке кастомного сервиса в D365FO нет задачи десериализации строки JSON на входе
...
Как по мне - именно задачу десериализации JSON строки автор и пытается решить. Ну приходит она к нему через кастомный веб сервис, ровно как приходила бы из какого-то файла на FTP или ещё откуда-то - без разницы.

Ок. А какую по-твоему задачу он решает?
Теги
d365fo, json, интеграция

 

Похожие темы
Тема Автор Раздел Ответов Посл. сообщение
Navigate Into Success: Passing JSON from JavaScript to C/AL Blog bot Dynamics CRM: Blogs 0 31.12.2014 10:00
Navigate Into Success: Long time no see, Vienna, Nashville, demo gods, and other things Blog bot Dynamics CRM: Blogs 0 09.10.2013 01:15
Navigate Into Success: Web Reference vs. Service Reference, Part 3 Blog bot Dynamics CRM: Blogs 0 31.01.2012 04:44
Navigate Into Success: Web Reference vs. Service Reference, Part 2 Blog bot Dynamics CRM: Blogs 0 30.01.2012 11:04
Creating Custom Dynamics AX Services Blog bot DAX Blogs 0 17.12.2008 12:05
Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход

Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 11:13.
Powered by vBulletin® v3.8.5. Перевод: zCarot
Контактная информация, Реклама.