Создание торговых систем с искусственным интеллектом на MQL5 (Часть 1): Реализация обработки JSON для API ИИ
Введение
В этой серии рассматривается интеграция искусственного интеллекта (ИИ) в торговые системы на языке MetaQuotes Language 5 (MQL5). В первой части разрабатывается надежный модуль разбора JSON для обмена данными при работе с API ИИ, например ChatGPT. Основное внимание уделяется созданию основы для обработки JSON-структур, обеспечивающей бесперебойное взаимодействие с ИИ-сервисами в будущих торговых приложениях. Мы рассмотрим следующие темы:
- Понимание JSON и его роли в интеграции ИИ
- Реализация на MQL5
- Тестирование модуля разбора JSON
- Заключение
К концу статьи у вас будет надежная основа для работы с JSON-данными, которая подготовит почву для торговых систем на базе ИИ. Приступим к реализации.
Понимание JSON и его роли в интеграции ИИ
JSON (JavaScript Object Notation) – легковесный текстовый формат обмена данными, широко используемый для структурирования и передачи данных между системами, особенно в веб-API, благодаря простоте, читаемости и совместимости с разными языками программирования. В торговых системах с искусственным интеллектом JSON служит стандартным форматом обмена данными с API ИИ, например ChatGPT от OpenAI. Это позволяет приложениям MQL5 отправлять торговые промпты и получать структурированные ответы для принятия решений. В статье создается модуль разбора JSON для обработки таких взаимодействий с API и закладывается основа для интеграции аналитики, генерируемой ИИ, в автоматизированные торговые стратегии.
Что такое JSON и почему он важен
JSON представляет данные в виде пар "ключ-значение", массивов и вложенных объектов. Поэтому он подходит для кодирования сложной информации, например рыночных данных, торговых сигналов и ответов ИИ, в формате, понятном человеку и обрабатываемом машиной. Например, объект JSON может выглядеть так:
{
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "Analyze EURUSD trend"},
{"role": "assistant", "content": "EURUSD shows a bullish trend"}
],
"max_tokens": 500
}Эта структура включает строки, числа, массивы и вложенные объекты. Советник MQL5 должен разбирать эту структуру, чтобы извлекать нужную информацию, например ответ ИИ на торговый запрос. Роль JSON в интеграции ИИ критична, поскольку API возвращают ответы в этом формате. Для динамического принятия торговых решений программа должна сериализовывать входные данные, то есть преобразовывать их в JSON, и десериализовывать ответы в пригодные для использования данные. Если эта терминология кажется слишком специфической, ниже объясняется, что такое сериализация и десериализация данных.

План реализации
План реализации включает создание класса для обработки JSON со следующими возможностями:
- Представление данных. Класс для хранения JSON-значений с полями типа, ключа и значения (например, строкового, численного или булевого), а также массивом дочерних элементов для обработки вложенных структур.
- Логика разбора. Методы десериализации JSON-строк в объекты с посимвольной обработкой для распознавания объектов, массивов, примитивных типов, пробелов и экранированных последовательностей.
- Логика сериализации. Методы преобразования внутренних данных обратно в JSON-строки с правильным форматированием API-запросов, включая экранирование специальных символов.
- Обработка ошибок. Надежные проверки недопустимого JSON, несоответствия типов и выхода за границы массива для предотвращения сбоев при обмене данными с API.
- Подготовка пользовательского интерфейса. Подготовка к будущей интеграции с пользовательским интерфейсом для ввода промптов и отображения ответов ИИ, опирающимся на разобранные JSON-данные.
Этот модуль будет протестирован на разборе типичных ответов API ИИ, например OpenAI. Также мы проверим его способность точно сериализовать торговые промпты, как показано ниже.

Освоив разбор JSON в рамках этой статьи, мы обеспечим будущим программам на основе ИИ возможность обрабатывать сложные структуры данных. Это откроет путь к торговым стратегиям, объединяющим анализ Price Action, графические паттерны и аналитику, сгенерированную ИИ. Перейдем к реализации.
Реализация на MQL5
Для реализации интеграции сначала создается комплексный класс разбора, который будет использоваться для первых промптов и будущих приложений. Ниже показано, как это реализовать.
//+------------------------------------------------------------------+ //| a. JSON Code File.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){return(INIT_SUCCEEDED);} //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){} //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){} //+------------------------------------------------------------------+ //| JSON (JavaScript Object Notation) | //+------------------------------------------------------------------+ #define DEBUG_PRINT false //+------------------------------------------------------------------+ //| Enumeration of JSON value types | //+------------------------------------------------------------------+ enum JsonValueType {JsonUndefined,JsonNull,JsonBoolean,JsonInteger,JsonDouble,JsonString,JsonArray,JsonObject}; //+------------------------------------------------------------------+ //| Class representing a JSON value | //+------------------------------------------------------------------+ class JsonValue{ public: // CONSTRUCTOR virtual void Reset(){ m_parent=NULL; //--- Set parent pointer to NULL m_key=""; //--- Clear the key string m_type=JsonUndefined; //--- Set type to undefined m_booleanValue=false; //--- Set boolean value to false m_integerValue=0; //--- Set integer value to zero m_doubleValue=0; //--- Set double value to zero m_stringValue=""; //--- Clear the string value ArrayResize(m_children,0); //--- Resize children array to zero } virtual bool CopyFrom(const JsonValue &source){ m_key=source.m_key; //--- Copy the key from source CopyDataFrom(source); //--- Copy data from source return true; //--- Return success } virtual void CopyDataFrom(const JsonValue &source){ m_type=source.m_type; //--- Copy the type from source m_booleanValue=source.m_booleanValue; //--- Copy the boolean value from source m_integerValue=source.m_integerValue; //--- Copy the integer value from source m_doubleValue=source.m_doubleValue; //--- Copy the double value from source m_stringValue=source.m_stringValue; //--- Copy the string value from source CopyChildrenFrom(source); //--- Copy children from source } virtual void CopyChildrenFrom(const JsonValue &source){ int numChildren=ArrayResize(m_children,ArraySize(source.m_children)); //--- Resize children array to match source size for(int index=0; index<numChildren; index++){ //--- Loop through each child m_children[index]=source.m_children[index]; //--- Copy child from source m_children[index].m_parent=GetPointer(this); //--- Set parent of child to current object } } public: JsonValue m_children[]; //--- Array to hold child JSON values string m_key; //--- Key for this JSON value string m_temporaryKey; //--- Temporary key used during parsing JsonValue *m_parent; //--- Pointer to parent JSON value JsonValueType m_type; //--- Type of this JSON value bool m_booleanValue; //--- Boolean value storage long m_integerValue; //--- Integer value storage double m_doubleValue; //--- Double value storage string m_stringValue; //--- String value storage static int encodingCodePage; //--- Static code page for encoding public: JsonValue(){ Reset(); //--- Call reset to initialize } JsonValue(JsonValue *parent,JsonValueType type){ Reset(); //--- Call reset to initialize m_type=type; //--- Set the type m_parent=parent; //--- Set the parent } JsonValue(JsonValueType type,string value){ Reset(); //--- Call reset to initialize SetFromString(type,value); //--- Set value from string based on type } JsonValue(const int integerValue){ Reset(); //--- Call reset to initialize m_type=JsonInteger; //--- Set type to integer m_integerValue=integerValue; //--- Set integer value m_doubleValue=(double)m_integerValue; //--- Convert to double m_stringValue=IntegerToString(m_integerValue); //--- Convert to string m_booleanValue=m_integerValue!=0; //--- Set boolean based on integer } JsonValue(const long longValue){ Reset(); //--- Call reset to initialize m_type=JsonInteger; //--- Set type to integer m_integerValue=longValue; //--- Set integer value m_doubleValue=(double)m_integerValue; //--- Convert to double m_stringValue=IntegerToString(m_integerValue); //--- Convert to string m_booleanValue=m_integerValue!=0; //--- Set boolean based on integer } JsonValue(const double doubleValue){ Reset(); //--- Call reset to initialize m_type=JsonDouble; //--- Set type to double m_doubleValue=doubleValue; //--- Set double value m_integerValue=(long)m_doubleValue; //--- Convert to integer m_stringValue=DoubleToString(m_doubleValue); //--- Convert to string m_booleanValue=m_integerValue!=0; //--- Set boolean based on integer } JsonValue(const bool booleanValue){ Reset(); //--- Call reset to initialize m_type=JsonBoolean; //--- Set type to boolean m_booleanValue=booleanValue; //--- Set boolean value m_integerValue=m_booleanValue; //--- Convert to integer m_doubleValue=m_booleanValue; //--- Convert to double m_stringValue=IntegerToString(m_integerValue); //--- Convert to string } JsonValue(const JsonValue &other){ Reset(); //--- Call reset to initialize CopyFrom(other); //--- Copy from other object } // DECONSTRUCTOR ~JsonValue(){ Reset(); //--- Call reset to clean up } }
Начинаем реализацию модуля разбора JSON с базового класса JsonValue для обработки JSON-данных при интеграции с API ИИ. Сначала определяется макрос DEBUG_PRINT со значением false для управления отладочным выводом и минимального журналирования в рабочей среде. Затем создается перечисление JsonValueType со значениями JsonUndefined, JsonNull, JsonBoolean, JsonInteger, JsonDouble, JsonString, JsonArray и JsonObject для классификации типов JSON-данных. Это позволяет классу обрабатывать различные структуры, возвращаемые API.
Далее реализуется класс JsonValue с публичными членами: массивом m_children для вложенных JSON-элементов; строками m_key и m_temporaryKey для хранения ключей при разборе; указателем m_parent для иерархических связей; переменной m_type типа JsonValueType; переменными m_booleanValue, m_integerValue, m_doubleValue и m_stringValue для хранения данных; а также статической переменной encodingCodePage для кодировки символов. Для инициализации объектов JsonValue предусмотрено несколько конструкторов: конструктор по умолчанию, вызывающий Reset; конструктор с родителем и типом; конструктор с типом и строковым значением; а также конструкторы для integer, long, double, boolean и копирования. Это обеспечивает гибкость при создании JSON-элементов.
Метод Reset сбрасывает все члены к значениям по умолчанию: null-указатель на родителя, пустые строки, неопределенный тип, обнуленные значения и пустой массив дочерних элементов. Методы CopyFrom, CopyDataFrom и CopyChildrenFrom выполняют глубокое копирование JSON-структур с дочерними элементами и корректным переназначением родителя. Эта базовая реализация формирует структуру для разбора и обработки JSON-данных, критически важную для дальнейшей работы с API ИИ. Затем можно реализовать некоторые виртуальные функции класса, также находящиеся под спецификатором доступа public. Виртуальные функции делают систему гибкой и полиморфной.
public: virtual bool IsNumericValue(){ return (m_type==JsonDouble || m_type==JsonInteger); //--- Check if type is double or integer } virtual JsonValue *FindChildByKey(string key){ for(int index=ArraySize(m_children)-1; index>=0; --index){ //--- Loop backwards through children if(m_children[index].m_key==key){ //--- Check if key matches return GetPointer(m_children[index]); //--- Return pointer to matching child } } return NULL; //--- Return NULL if no match } virtual JsonValue *HasChildWithKey(string key,JsonValueType type=JsonUndefined); virtual JsonValue *operator[](string key); virtual JsonValue *operator[](int index); void operator=(const JsonValue &value){ CopyFrom(value); //--- Copy from value } void operator=(const int integerValue){ m_type=JsonInteger; //--- Set type to integer m_integerValue=integerValue; //--- Set integer value m_doubleValue=(double)m_integerValue; //--- Convert to double m_booleanValue=m_integerValue!=0; //--- Set boolean based on integer } void operator=(const long longValue){ m_type=JsonInteger; //--- Set type to integer m_integerValue=longValue; //--- Set integer value m_doubleValue=(double)m_integerValue; //--- Convert to double m_booleanValue=m_integerValue!=0; //--- Set boolean based on integer } void operator=(const double doubleValue){ m_type=JsonDouble; //--- Set type to double m_doubleValue=doubleValue; //--- Set double value m_integerValue=(long)m_doubleValue; //--- Convert to integer m_booleanValue=m_integerValue!=0; //--- Set boolean based on integer } void operator=(const bool booleanValue){ m_type=JsonBoolean; //--- Set type to boolean m_booleanValue=booleanValue; //--- Set boolean value m_integerValue=(long)m_booleanValue; //--- Convert to integer m_doubleValue=(double)m_booleanValue; //--- Convert to double } void operator=(string stringValue){ m_type=(stringValue!=NULL)?JsonString:JsonNull; //--- Set type to string or null m_stringValue=stringValue; //--- Set string value m_integerValue=StringToInteger(m_stringValue); //--- Convert to integer m_doubleValue=StringToDouble(m_stringValue); //--- Convert to double m_booleanValue=stringValue!=NULL; //--- Set boolean based on string presence } bool operator==(const int integerValue){return m_integerValue==integerValue;} //--- Compare integer value bool operator==(const long longValue){return m_integerValue==longValue;} //--- Compare long value bool operator==(const double doubleValue){return m_doubleValue==doubleValue;} //--- Compare double value bool operator==(const bool booleanValue){return m_booleanValue==booleanValue;} //--- Compare boolean value bool operator==(string stringValue){return m_stringValue==stringValue;} //--- Compare string value bool operator!=(const int integerValue){return m_integerValue!=integerValue;} //--- Check inequality for integer bool operator!=(const long longValue){return m_integerValue!=longValue;} //--- Check inequality for long bool operator!=(const double doubleValue){return m_doubleValue!=doubleValue;} //--- Check inequality for double bool operator!=(const bool booleanValue){return m_booleanValue!=booleanValue;} //--- Check inequality for boolean bool operator!=(string stringValue){return m_stringValue!=stringValue;} //--- Check inequality for string long ToInteger() const{return m_integerValue;} //--- Return integer value double ToDouble() const{return m_doubleValue;} //--- Return double value bool ToBoolean() const{return m_booleanValue;} //--- Return boolean value string ToString(){return m_stringValue;} //--- Return string value virtual void SetFromString(JsonValueType type,string stringValue){ m_type=type; //--- Set the type switch(m_type){ //--- Handle based on type case JsonBoolean: m_booleanValue=(StringToInteger(stringValue)!=0); //--- Convert string to boolean m_integerValue=(long)m_booleanValue; //--- Set integer from boolean m_doubleValue=(double)m_booleanValue; //--- Set double from boolean m_stringValue=stringValue; //--- Set string value break; //--- Exit case case JsonInteger: m_integerValue=StringToInteger(stringValue); //--- Convert string to integer m_doubleValue=(double)m_integerValue; //--- Set double from integer m_stringValue=stringValue; //--- Set string value m_booleanValue=m_integerValue!=0; //--- Set boolean from integer break; //--- Exit case case JsonDouble: m_doubleValue=StringToDouble(stringValue); //--- Convert string to double m_integerValue=(long)m_doubleValue; //--- Set integer from double m_stringValue=stringValue; //--- Set string value m_booleanValue=m_integerValue!=0; //--- Set boolean from integer break; //--- Exit case case JsonString: m_stringValue=UnescapeString(stringValue); //--- Unescape the string m_type=(m_stringValue!=NULL)?JsonString:JsonNull; //--- Set type based on string presence m_integerValue=StringToInteger(m_stringValue); //--- Convert to integer m_doubleValue=StringToDouble(m_stringValue); //--- Convert to double m_booleanValue=m_stringValue!=NULL; //--- Set boolean based on string break; //--- Exit case } } virtual string GetSubstringFromArray(char &jsonCharacterArray[],int startPosition,int substringLength){ #ifdef __MQL4__ if(substringLength<=0) return ""; //--- Return empty if length invalid in MQL4 #endif char temporaryArray[]; //--- Declare temporary array ArrayCopy(temporaryArray,jsonCharacterArray,0,startPosition,substringLength); //--- Copy substring to temporary array return CharArrayToString(temporaryArray, 0, WHOLE_ARRAY, JsonValue::encodingCodePage); //--- Convert to string using code page } virtual void SetValue(const JsonValue &value){ if(m_type==JsonUndefined) {m_type=JsonObject;} //--- Set type to object if undefined CopyDataFrom(value); //--- Copy data from value } virtual void SetArrayValues(const JsonValue &list[]); virtual JsonValue *AddChild(const JsonValue &item){ if(m_type==JsonUndefined){m_type=JsonArray;} //--- Set type to array if undefined return AddChildInternal(item); //--- Call internal add child } virtual JsonValue *AddChild(const int integerValue){ JsonValue item(integerValue); //--- Create item from integer return AddChild(item); //--- Add the item } virtual JsonValue *AddChild(const long longValue){ JsonValue item(longValue); //--- Create item from long return AddChild(item); //--- Add the item } virtual JsonValue *AddChild(const double doubleValue){ JsonValue item(doubleValue); //--- Create item from double return AddChild(item); //--- Add the item } virtual JsonValue *AddChild(const bool booleanValue){ JsonValue item(booleanValue); //--- Create item from boolean return AddChild(item); //--- Add the item } virtual JsonValue *AddChild(string stringValue){ JsonValue item(JsonString,stringValue); //--- Create item from string return AddChild(item); //--- Add the item } virtual JsonValue *AddChildInternal(const JsonValue &item){ int currentSize=ArraySize(m_children); //--- Get current children size ArrayResize(m_children,currentSize+1); //--- Resize array to add one more m_children[currentSize]=item; //--- Add the item m_children[currentSize].m_parent=GetPointer(this); //--- Set parent to current object return GetPointer(m_children[currentSize]); //--- Return pointer to added child } virtual JsonValue *CreateNewChild(){ if(m_type==JsonUndefined) {m_type=JsonArray;} //--- Set type to array if undefined return CreateNewChildInternal(); //--- Call internal create new child } virtual JsonValue *CreateNewChildInternal(){ int currentSize=ArraySize(m_children); //--- Get current children size ArrayResize(m_children,currentSize+1); //--- Resize array to add one more return GetPointer(m_children[currentSize]); //--- Return pointer to new child } virtual string EscapeString(string value); virtual string UnescapeString(string value);
В разделе public реализуется метод IsNumericValue. Он проверяет, является ли JSON-значение числом, возвращая true, если m_type равно JsonDouble или JsonInteger. Это позволяет обрабатывать числовые данные с учетом их типа. Затем разрабатывается метод FindChildByKey, перебирающий массив m_children в обратном порядке для поиска дочернего элемента с совпадающим ключом. Он возвращает указатель на элемент либо NULL, если элемент не найден, облегчая доступ к вложенным JSON-объектам.
Далее определяются перегруженные операторы присваивания operator= для JsonValue и типов int, long, double, bool и string. Они обновляют m_type и соответствующие поля m_integerValue, m_doubleValue, m_stringValue и m_booleanValue, сохраняя согласованность внутренних представлений значения. Также реализуются операторы сравнения operator== и operator!= для типов int, long, double, bool и string, позволяющие напрямую сравнивать значения с соответствующими внутренними полями.
Кроме того, предусмотрены методы преобразования ToInteger, ToDouble, ToBoolean и ToString для получения значений в соответствующих форматах. Метод SetFromString задает JSON-значение на основе указанного JsonValueType. Он обрабатывает типы bool, integer, double и string, преобразуя входную строку и обновляя связанные поля; строковые значения деэкранируются с помощью UnescapeString. Метод GetSubstringFromArray извлекает подстроку из массива символов: копирует указанную часть и преобразует ее в строку с помощью CharArrayToString и заданной encodingCodePage.
Наконец, реализуются методы управления дочерними элементами: AddChild для JsonValue, int, long, double, bool и string добавляет новый дочерний элемент в m_children, задавая при необходимости родителя и тип; CreateNewChild и CreateNewChildInternal создают пустой дочерний элемент; SetValue копирует данные из другого JsonValue. Эти методы позволяют классу создавать и изменять JSON-структуры, а также получать к ним доступ. Наконец, класс дополняется методами сериализации, десериализации и кодирования символов в ответах и промптах.
public: virtual void SerializeToString(string &jsonString,bool includeKey=false,bool includeComma=false); virtual string SerializeToString(){ string jsonString; //--- Declare json string SerializeToString(jsonString); //--- Call serialize with default params return jsonString; //--- Return the serialized string } virtual bool DeserializeFromArray(char &jsonCharacterArray[],int arrayLength,int ¤tIndex); virtual bool ExtractStringFromArray(char &jsonCharacterArray[],int arrayLength,int ¤tIndex); virtual bool DeserializeFromString(string jsonString,int encoding=CP_ACP){ int currentIndex=0; //--- Initialize current index Reset(); //--- Reset the object JsonValue::encodingCodePage=encoding; //--- Set encoding code page char characterArray[]; //--- Declare character array int arrayLength=StringToCharArray(jsonString,characterArray,0,WHOLE_ARRAY,JsonValue::encodingCodePage); //--- Convert string to char array return DeserializeFromArray(characterArray,arrayLength,currentIndex); //--- Call deserialize from array } virtual bool DeserializeFromArray(char &jsonCharacterArray[],int encoding=CP_ACP){ int currentIndex=0; //--- Initialize current index Reset(); //--- Reset the object JsonValue::encodingCodePage=encoding; //--- Set encoding code page return DeserializeFromArray(jsonCharacterArray,ArraySize(jsonCharacterArray),currentIndex); //--- Call deserialize with size }
Здесь реализуется метод SerializeToString со ссылкой на строку, includeKey и includeComma в качестве параметров. Метод преобразует JSON-структуру в строку; добавляет запятую, если includeComma равно true; включает ключ, если includeKey равно true; и обрабатывает различные типы: JsonNull добавляет строку "null"; JsonBoolean добавляет "true" или "false" в зависимости от m_booleanValue; JsonInteger и JsonDouble преобразуются в строки; JsonString экранирует m_stringValue с помощью EscapeString и заключает значение этой строки в кавычки, либо добавляет "null", если строка пуста; JsonArray заключает дочерние элементы в квадратные скобки с рекурсивными вызовами SerializeToString без ключей и с запятыми перед элементами, кроме первого; JsonObject заключает дочерние элементы с ключами в фигурные скобки. Для вложенных структур дочерние элементы сериализуются рекурсивно. Вспомогательная перегрузка SerializeToString создает строку, вызывает версию с параметрами и возвращает результат.
Далее реализуется метод DeserializeFromString, принимающий JSON-строку и кодировку, по умолчанию CP_ACP. Метод сбрасывает объект, задает encodingCodePage, преобразует строку в массив символов с помощью StringToCharArray и передает управление методу DeserializeFromArray. Перегрузка DeserializeFromArray с кодировкой инициализирует индекс, сбрасывает объект, задает encodingCodePage и вызывает основной метод DeserializeFromArray. Основной метод, в свою очередь, разбирает массив символов, обрабатывая пробельные символы, массивы (между символами "[" и "]"), объекты (между символами "{" и "}"), булевы значения ("true" и "false"), null-значение ("null"), числа (различая целые числа и double путем посимвольной проверки) и строки с обработкой экранирования, создавая при необходимости дочерние элементы и управляя иерархией с помощью "m_parent" и "m_temporaryKey".
Наконец, ExtractStringFromArray разбирает строки внутри массива до закрывающей кавычки, обрабатывая экранированные символы, например "\n", "\t" и Unicode-последовательности "\uXXXX". Это обеспечивает точное извлечение строк, в том числе содержащих символы Unicode, например эмодзи. Ниже показан пример того, как это выглядит.

Теперь можно определить эти методы так, чтобы они точно выполняли свое назначение.
int JsonValue::encodingCodePage=CP_ACP; //--- Initialize static code page //+------------------------------------------------------------------+ //| Checks if child with specific key and optional type exists | //+------------------------------------------------------------------+ JsonValue *JsonValue::HasChildWithKey(string key,JsonValueType type){ for(int index=0; index<ArraySize(m_children); index++) if(m_children[index].m_key==key){ //--- Loop through children if(type==JsonUndefined || type==m_children[index].m_type){ //--- Check type condition return GetPointer(m_children[index]); //--- Return matching child } break; //--- Exit loop } return NULL; //--- Return NULL if no match } //+------------------------------------------------------------------+ //| Accessor for object key, creates if not exists | //+------------------------------------------------------------------+ JsonValue *JsonValue::operator[](string key){ if(m_type==JsonUndefined){m_type=JsonObject;} //--- Set type to object if undefined JsonValue *value=FindChildByKey(key); //--- Find child by key if(value){return value;} //--- Return if found JsonValue newValue(GetPointer(this),JsonUndefined); //--- Create new undefined value newValue.m_key=key; //--- Set key for new value value=AddChild(newValue); //--- Add new value as child return value; //--- Return the new value } //+------------------------------------------------------------------+ //| Accessor for array index, expands if necessary | //+------------------------------------------------------------------+ JsonValue *JsonValue::operator[](int index){ if(m_type==JsonUndefined) m_type=JsonArray; //--- Set type to array if undefined while(index>=ArraySize(m_children)){ //--- Loop to expand array if needed JsonValue newElement(GetPointer(this),JsonUndefined); //--- Create new undefined element if(CheckPointer(AddChild(newElement))==POINTER_INVALID){return NULL;} //--- Add and check pointer } return GetPointer(m_children[index]); //--- Return pointer to element at index } //+------------------------------------------------------------------+ //| Sets array values from list | //+------------------------------------------------------------------+ void JsonValue::SetArrayValues(const JsonValue &list[]){ if(m_type==JsonUndefined){m_type=JsonArray;} //--- Set type to array if undefined int numChildren=ArrayResize(m_children,ArraySize(list)); //--- Resize children to list size for(int index=0; index<numChildren; ++index){ //--- Loop through list m_children[index]=list[index]; //--- Copy from list m_children[index].m_parent=GetPointer(this); //--- Set parent to current } }
Для определения методов класса вне тела используется оператор разрешения области видимости. Их можно определить внутри класса, но такой подход делает код модульным. Статический член encodingCodePage инициализируется значением CP_ACP (Active Code Page) для задания кодировки символов по умолчанию при преобразовании строк. Это обеспечивает совместимость с JSON-данными из API ИИ. Затем реализуется метод HasChildWithKey, перебирающий массив m_children в прямом порядке для поиска дочернего элемента с совпадающим ключом и при необходимости указанным JsonValueType. По умолчанию используется JsonUndefined, что означает игнорирование типа. Метод возвращает указатель на совпадающий дочерний элемент либо NULL, если элемент не найден. Раннее завершение поиска и проверка типа повышают эффективность по сравнению с FindChildByKey.
Далее разрабатывается перегруженный метод operator[] для доступа к объекту. Если m_type не определен, ему присваивается JsonObject; затем с помощью FindChildByKey ищется дочерний элемент с заданным ключом. Если элемент не найден, создается новый JsonValue с этим ключом и добавляется в m_children через AddChild. Метод возвращает указатель на существующий или новый дочерний элемент для удобного доступа к объекту. Аналогично перегруженный метод operator[] для массива присваивает m_type значение JsonArray, если тип не определен. Если индекс превышает размер m_children, массив расширяется элементами с неопределенным значением через AddChild. Затем возвращается указатель на элемент по указанному индексу, что обеспечивает безопасный доступ к массиву.
Наконец, реализуется SetArrayValues. Если m_type не определен, ему присваивается JsonArray; размер m_children приводится к размеру входного списка; каждый JsonValue копируется из списка в m_children; родителем каждого дочернего элемента назначается текущий объект. Это позволяет массово присваивать значения массива. Для остальных методов используется аналогичный формат. Начнем с методов сериализации и десериализации строк.
//+------------------------------------------------------------------+ //| Serializes the JSON value to string | //+------------------------------------------------------------------+ void JsonValue::SerializeToString(string &jsonString,bool includeKey,bool includeComma){ if(m_type==JsonUndefined){return;} //--- Return if undefined if(includeComma){jsonString+=",";} //--- Add comma if needed if(includeKey){jsonString+=StringFormat("\"%s\":", m_key);} //--- Add key if needed int numChildren=ArraySize(m_children); //--- Get number of children switch(m_type){ //--- Handle based on type case JsonNull: jsonString+="null"; //--- Append null break; //--- Exit case case JsonBoolean: jsonString+=(m_booleanValue?"true":"false"); //--- Append true or false break; //--- Exit case case JsonInteger: jsonString+=IntegerToString(m_integerValue); //--- Append integer as string break; //--- Exit case case JsonDouble: jsonString+=DoubleToString(m_doubleValue); //--- Append double as string break; //--- Exit case case JsonString: { string value=EscapeString(m_stringValue); //--- Escape the string if(StringLen(value)>0){jsonString+=StringFormat("\"%s\"",value);} //--- Append escaped string if not empty else{jsonString+="null";} //--- Append null if empty } break; //--- Exit case case JsonArray: jsonString+="["; //--- Start array for(int index=0; index<numChildren; index++){m_children[index].SerializeToString(jsonString,false,index>0);} //--- Serialize each child jsonString+="]"; //--- End array break; //--- Exit case case JsonObject: jsonString+="{"; //--- Start object for(int index=0; index<numChildren; index++){m_children[index].SerializeToString(jsonString,true,index>0);} //--- Serialize each child with key jsonString+="}"; //--- End object break; //--- Exit case } } //+------------------------------------------------------------------+ //| Deserializes from character array | //+------------------------------------------------------------------+ bool JsonValue::DeserializeFromArray(char &jsonCharacterArray[],int arrayLength,int ¤tIndex){ string validNumericCharacters="0123456789+-.eE"; //--- Define valid number characters int startPosition=currentIndex; //--- Set start position for(; currentIndex<arrayLength; currentIndex++){ //--- Loop through array char currentCharacter=jsonCharacterArray[currentIndex]; //--- Get current character if(currentCharacter==0){break;} //--- Break if null character switch(currentCharacter){ //--- Handle based on character case '\t': case '\r': case '\n': case ' ': //--- Skip whitespace startPosition=currentIndex+1; //--- Update start position break; //--- Exit case case '[': //--- Start of array { startPosition=currentIndex+1; //--- Update start position if(m_type!=JsonUndefined){ //--- Check if type is undefined if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug if error return false; //--- Return false on error } m_type=JsonArray; //--- Set type to array currentIndex++; //--- Increment index JsonValue childValue(GetPointer(this),JsonUndefined); //--- Create child value while(childValue.DeserializeFromArray(jsonCharacterArray,arrayLength,currentIndex)){ //--- Deserialize children if(childValue.m_type!=JsonUndefined){AddChild(childValue);} //--- Add if not undefined if(childValue.m_type==JsonInteger || childValue.m_type==JsonDouble || childValue.m_type==JsonArray){currentIndex++;} //--- Increment if certain types childValue.Reset(); //--- Reset child childValue.m_parent=GetPointer(this); //--- Set parent if(jsonCharacterArray[currentIndex]==']'){break;} //--- Break if end of array currentIndex++; //--- Increment index if(currentIndex>=arrayLength){ //--- Check bounds if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } } return (jsonCharacterArray[currentIndex]==']' || jsonCharacterArray[currentIndex]==0); //--- Return true if properly ended } break; //--- Exit case case ']': //--- End of array if(!m_parent){return false;} //--- Return false if no parent return (m_parent.m_type==JsonArray); //--- Check parent is array case ':': //--- Key-value separator { if(m_temporaryKey==""){ //--- Check temporary key if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } JsonValue childValue(GetPointer(this),JsonUndefined); //--- Create child JsonValue *addedChild=AddChild(childValue); //--- Add child addedChild.m_key=m_temporaryKey; //--- Set key m_temporaryKey=""; //--- Clear temporary key currentIndex++; //--- Increment index if(!addedChild.DeserializeFromArray(jsonCharacterArray,arrayLength,currentIndex)){ //--- Deserialize child if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } break; //--- Exit case } case ',': //--- Value separator startPosition=currentIndex+1; //--- Update start if(!m_parent && m_type!=JsonObject){ //--- Check conditions if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } else if(m_parent){ //--- If has parent if(m_parent.m_type!=JsonArray && m_parent.m_type!=JsonObject){ //--- Check parent type if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } if(m_parent.m_type==JsonArray && m_type==JsonUndefined){return true;} //--- Return true for undefined in array } break; //--- Exit case case '{': //--- Start of object startPosition=currentIndex+1; //--- Update start if(m_type!=JsonUndefined){ //--- Check type if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } m_type=JsonObject; //--- Set type to object currentIndex++; //--- Increment index if(!DeserializeFromArray(jsonCharacterArray,arrayLength,currentIndex)){ //--- Recurse deserialize if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } return (jsonCharacterArray[currentIndex]=='}' || jsonCharacterArray[currentIndex]==0); //--- Check end break; //--- Exit case case '}': //--- End of object return (m_type==JsonObject); //--- Check type is object case 't': case 'T': //--- Start of true case 'f': case 'F': //--- Start of false if(m_type!=JsonUndefined){ //--- Check type if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } m_type=JsonBoolean; //--- Set type to boolean if(currentIndex+3<arrayLength){ //--- Check for true if(StringCompare(GetSubstringFromArray(jsonCharacterArray, currentIndex, 4), "true", false)==0){ //--- Compare substring m_booleanValue=true; //--- Set to true currentIndex+=3; //--- Advance index return true; //--- Return true } } if(currentIndex+4<arrayLength){ //--- Check for false if(StringCompare(GetSubstringFromArray(jsonCharacterArray, currentIndex, 5), "false", false)==0){ //--- Compare substring m_booleanValue=false; //--- Set to false currentIndex+=4; //--- Advance index return true; //--- Return true } } if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false break; //--- Exit case case 'n': case 'N': //--- Start of null if(m_type!=JsonUndefined){ //--- Check type if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } m_type=JsonNull; //--- Set type to null if(currentIndex+3<arrayLength){ //--- Check bounds if(StringCompare(GetSubstringFromArray(jsonCharacterArray,currentIndex,4),"null",false)==0){ //--- Compare substring currentIndex+=3; //--- Advance index return true; //--- Return true } } if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false break; //--- Exit case case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': case '+': case '.': //--- Start of number { if(m_type!=JsonUndefined){ //--- Check type if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } bool isDouble=false; //--- Initialize double flag int startOfNumber=currentIndex; //--- Set start of number while(jsonCharacterArray[currentIndex]!=0 && currentIndex<arrayLength){ //--- Loop to parse number currentIndex++; //--- Increment index if(StringFind(validNumericCharacters,GetSubstringFromArray(jsonCharacterArray,currentIndex,1))<0){break;} //--- Break if invalid char if(!isDouble){isDouble=(jsonCharacterArray[currentIndex]=='.' || jsonCharacterArray[currentIndex]=='e' || jsonCharacterArray[currentIndex]=='E');} //--- Set double flag } m_stringValue=GetSubstringFromArray(jsonCharacterArray,startOfNumber,currentIndex-startOfNumber); //--- Get number string if(isDouble){ //--- If double m_type=JsonDouble; //--- Set type to double m_doubleValue=StringToDouble(m_stringValue); //--- Convert to double m_integerValue=(long)m_doubleValue; //--- Convert to integer m_booleanValue=m_integerValue!=0; //--- Set boolean } else{ //--- Else integer m_type=JsonInteger; //--- Set type to integer m_integerValue=StringToInteger(m_stringValue); //--- Convert to integer m_doubleValue=(double)m_integerValue; //--- Convert to double m_booleanValue=m_integerValue!=0; //--- Set boolean } currentIndex--; //--- Decrement index return true; //--- Return true break; //--- Exit case } case '\"': //--- Start of string or key if(m_type==JsonObject){ //--- If object type currentIndex++; //--- Increment index int startOfString=currentIndex; //--- Set start of string if(!ExtractStringFromArray(jsonCharacterArray,arrayLength,currentIndex)){ //--- Extract string if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } m_temporaryKey=GetSubstringFromArray(jsonCharacterArray,startOfString,currentIndex-startOfString); //--- Set temporary key } else{ //--- Else value string if(m_type!=JsonUndefined){ //--- Check type if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } m_type=JsonString; //--- Set type to string currentIndex++; //--- Increment index int startOfString=currentIndex; //--- Set start of string if(!ExtractStringFromArray(jsonCharacterArray,arrayLength,currentIndex)){ //--- Extract string if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } SetFromString(JsonString,GetSubstringFromArray(jsonCharacterArray,startOfString,currentIndex-startOfString)); //--- Set from extracted string return true; //--- Return true } break; //--- Exit case } } return true; //--- Return true at end }
Продолжается реализация модуля разбора JSON с акцентом на критически важные методы сериализации и десериализации.
Сначала определяется метод SerializeToString, преобразующий JSON-структуру в строку. Он пропускает значения с неопределенным типом, добавляет запятую при includeComma == true, добавляет ключ при includeKey == true с помощью StringFormat и обрабатывает каждый вариант m_type: JsonNull добавляет null; JsonBoolean добавляет true или false в зависимости от m_booleanValue; JsonInteger использует IntegerToString для m_integerValue; JsonDouble использует DoubleToString для m_doubleValue; JsonString экранирует m_stringValue с помощью EscapeString и заключает его в кавычки, либо добавляет строку "null", если строка пуста; JsonArray заключает дочерние элементы в квадратные скобки с рекурсивными вызовами SerializeToString без ключей и с запятыми перед элементами, кроме первого; JsonObject заключает дочерние элементы с ключами в фигурные скобки. Вспомогательная перегрузка SerializeToString создает строку, вызывает версию с параметрами по умолчанию и возвращает результат.
Далее мы реализуем метод DeserializeFromArray, разбирающий массив символов для построения JSON-структуры. Он использует строку допустимых символов числа ("0123456789+-.eE") и последовательно обрабатывает элементы массива. Метод пропускает пробельные символы; обрабатывает массивы от "[" до "]" путем присвоения m_type = JsonArray и рекурсивной десериализации дочерних элементов до закрывающей скобки; проверяет корректность закрытия массива; обрабатывает разделитель между ключом и значением (":") путем назначения m_temporaryKey новому дочернему элементу; обрабатывает разделители значений (","); обрабатывает объекты от "{" до "}" путем присвоения m_type = JsonObject и рекурсивной десериализации дочерних элементов до закрывающей фигурной скобки с проверкой корректности закрытия объекта. Метод также разбирает булевы значения ("true" или "false"), устанавливая m_booleanValue; null-значение ("null"), присваивая m_type = JsonNull; числа, выявляя десятичную или экспоненциальную нотацию для присвоения m_type = JsonDouble либо m_type = JsonInteger; и строки в кавычках с помощью ExtractStringFromArray для обработки экранированных символов.
Метод DeserializeFromArray обеспечивает корректные связи между родительскими и дочерними элементами, а также обработку ошибок через сообщения DEBUG_PRINT для недопустимых структур. Благодаря этому он надежно разбирает ответы. Наконец, можно определить методы экранирования.
//+------------------------------------------------------------------+ //| Extracts string from array handling escapes | //+------------------------------------------------------------------+ bool JsonValue::ExtractStringFromArray(char &jsonCharacterArray[],int arrayLength,int ¤tIndex){ for(; jsonCharacterArray[currentIndex]!=0 && currentIndex<arrayLength; currentIndex++){ //--- Loop through string char currentCharacter=jsonCharacterArray[currentIndex]; //--- Get current char if(currentCharacter=='\"') break; //--- Break on closing quote if(currentCharacter=='\\' && currentIndex+1<arrayLength){ //--- Handle escape currentIndex++; //--- Increment for escaped char currentCharacter=jsonCharacterArray[currentIndex]; //--- Get escaped char switch(currentCharacter){ //--- Handle escaped type case '/': case '\\': case '\"': case 'b': case 'f': case 'r': case 'n': case 't': break; //--- Allowed escapes case 'u': //--- Unicode escape { currentIndex++; //--- Increment for(int hexDigitIndex=0; hexDigitIndex<4 && currentIndex<arrayLength && jsonCharacterArray[currentIndex]!=0; hexDigitIndex++,currentIndex++){ //--- Loop hex digits if(!((jsonCharacterArray[currentIndex]>='0' && jsonCharacterArray[currentIndex]<='9') || (jsonCharacterArray[currentIndex]>='A' && jsonCharacterArray[currentIndex]<='F') || (jsonCharacterArray[currentIndex]>='a' && jsonCharacterArray[currentIndex]<='f'))){ //--- Check hex if(DEBUG_PRINT){Print(m_key+" "+CharToString(jsonCharacterArray[currentIndex])+" "+string(__LINE__));} //--- Print debug return false; //--- Return false on invalid hex } } currentIndex--; //--- Decrement after loop break; //--- Exit case } default: break; //--- Handle other (commented return false) } } } return true; //--- Return true } //+------------------------------------------------------------------+ //| Escapes special characters in string | //+------------------------------------------------------------------+ string JsonValue::EscapeString(string stringValue){ ushort inputCharacters[], escapedCharacters[]; //--- Declare arrays int inputLength=StringToShortArray(stringValue, inputCharacters); //--- Convert string to short array if(ArrayResize(escapedCharacters, 2*inputLength)!=2*inputLength){return NULL;} //--- Resize escaped array, return NULL on fail int escapedIndex=0; //--- Initialize escaped index for(int inputIndex=0; inputIndex<inputLength; inputIndex++){ //--- Loop through input switch(inputCharacters[inputIndex]){ //--- Handle special chars case '\\': escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='\\'; //--- Add backslash escapedIndex++; //--- Increment break; //--- Exit case case '"': escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='"'; //--- Add quote escapedIndex++; //--- Increment break; //--- Exit case case '/': escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='/'; //--- Add slash escapedIndex++; //--- Increment break; //--- Exit case case 8: escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='b'; //--- Add backspace escapedIndex++; //--- Increment break; //--- Exit case case 12: escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='f'; //--- Add form feed escapedIndex++; //--- Increment break; //--- Exit case case '\n': escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='n'; //--- Add newline escapedIndex++; //--- Increment break; //--- Exit case case '\r': escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='r'; //--- Add carriage return escapedIndex++; //--- Increment break; //--- Exit case case '\t': escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='t'; //--- Add tab escapedIndex++; //--- Increment break; //--- Exit case default: escapedCharacters[escapedIndex]=inputCharacters[inputIndex]; //--- Copy normal char escapedIndex++; //--- Increment break; //--- Exit case } } stringValue=ShortArrayToString(escapedCharacters,0,escapedIndex); //--- Convert back to string return stringValue; //--- Return escaped string } //+------------------------------------------------------------------+ //| Unescapes special characters in string | //+------------------------------------------------------------------+ string JsonValue::UnescapeString(string stringValue){ ushort inputCharacters[], unescapedCharacters[]; //--- Declare arrays int inputLength=StringToShortArray(stringValue, inputCharacters); //--- Convert to short array if(ArrayResize(unescapedCharacters, inputLength)!=inputLength){return NULL;} //--- Resize, return NULL on fail int outputIndex=0,inputIndex=0; //--- Initialize indices while(inputIndex<inputLength){ //--- Loop through input ushort currentCharacter=inputCharacters[inputIndex]; //--- Get current char if(currentCharacter=='\\' && inputIndex<inputLength-1){ //--- Handle escape switch(inputCharacters[inputIndex+1]){ //--- Handle escaped type case '\\': currentCharacter='\\'; //--- Set to backslash inputIndex++; //--- Increment break; //--- Exit case case '"': currentCharacter='"'; //--- Set to quote inputIndex++; //--- Increment break; //--- Exit case case '/': currentCharacter='/'; //--- Set to slash inputIndex++; //--- Increment break; //--- Exit case case 'b': currentCharacter=8; //--- Set to backspace inputIndex++; //--- Increment break; //--- Exit case case 'f': currentCharacter=12; //--- Set to form feed inputIndex++; //--- Increment break; //--- Exit case case 'n': currentCharacter='\n'; //--- Set to newline inputIndex++; //--- Increment break; //--- Exit case case 'r': currentCharacter='\r'; //--- Set to carriage return inputIndex++; //--- Increment break; //--- Exit case case 't': currentCharacter='\t'; //--- Set to tab inputIndex++; //--- Increment break; //--- Exit case } } unescapedCharacters[outputIndex]=currentCharacter; //--- Copy to output outputIndex++; //--- Increment output inputIndex++; //--- Increment input } stringValue=ShortArrayToString(unescapedCharacters,0,outputIndex); //--- Convert back to string return stringValue; //--- Return unescaped string }
Реализация завершается методами обработки строк. Сначала реализуется метод ExtractStringFromArray, извлекающий строку из массива символов до закрывающей кавычки или конца массива. Он обрабатывает экранированные последовательности \\, \", \/, \b, \f, \n, \r и \t, а также Unicode-последовательности \uXXXX, проверяя четыре шестнадцатеричные цифры. При недопустимой шестнадцатеричной последовательности или выходе за границы массива метод возвращает false; DEBUG_PRINT используется для отладки некорректных случаев. Это обеспечивает точное извлечение строк при разборе JSON.
Затем разрабатывается метод EscapeString, преобразующий строку в формат, совместимый с JSON. Строка преобразуется в массив типа ushort с помощью StringToShortArray; размер выходного массива задается вдвое больше длины входной строки для размещения экранированных символов. Метод последовательно обрабатывает все символы: обратную косую черту, кавычку, простую косую черту, символ возврата на шаг назад (backspace, 8), символ перевода страницы (form feed, 12), символ переноса строки, возврат каретки и табуляцию, экранируя их предшествующей обратной косой чертой, например \n для переноса строки. Остальные символы копируются напрямую, а экранированная строка возвращается через ShortArrayToString.
Наконец, реализуется метод UnescapeString, выполняющий обратное преобразование экранированных последовательностей. Входная строка преобразуется в массив типа ushort, размер выходного массива задается равным длине входной строки, после чего элементы последовательно обрабатываются. При обнаружении обратной косой черты интерпретируется следующий символ, например \n интерпретируется как символ переноса строки, а \t – как табуляция; деэкранированный символ копируется в выходной массив. Неэкранированные символы копируются напрямую, а через ShortArrayToString возвращается итоговая деэкранированная строка либо NULL при ошибке изменения размера. Эти методы обеспечивают корректную обработку классом JsonValue специальных символов в JSON-строках. Это критически важно для правильного форматирования API-запросов и надежного разбора ответов ИИ. Ниже приведен пример использования специальных символов, которые будут корректно интерпретированы.

После завершения реализации класса можно использовать логику разбора JSON. Но сначала проверим модуль разбора JSON и убедимся, что все работает корректно. Это будет сделано в следующем разделе.
Тестирование модуля разбора JSON
Для проверки надежности модуля разбора JSON мы проводим тщательное тестирование MQL5-класса JsonValue. Проверяется его способность обрабатывать различные JSON-структуры, критически важные для интеграции с API ИИ. Ниже описан подход к тестированию: тестовые случаи, ожидаемые результаты и методы проверки работы модуля в торговой среде. Определяется функция, которая вызывается в обработчике событий OnInit и выводит содержимое. Ниже приведен первый фрагмент кода. Использовать будем тот же файл. Сначала выведем "Hello world", как в самом простом стартовом примере.
//+------------------------------------------------------------------+ //| Test Functions | //+------------------------------------------------------------------+ void TestBasicSerialization(){ Print("\n--- Testing Basic Serialization ---"); JsonValue root; root["string"] = "hello world"; root["number"] = 42; root["double"] = 3.14159; root["boolean"] = true; root["empty"] = ""; string json = root.SerializeToString(); Print("Serialized JSON: ", json); JsonValue parsed; if(parsed.DeserializeFromString(json)){ Print("Deserialization successful"); Print("String: ", parsed["string"].ToString()); Print("Number: ", (int)parsed["number"].ToInteger()); Print("Double: ", parsed["double"].ToDouble()); Print("Boolean: ", parsed["boolean"].ToBoolean()); Print("Empty: ", parsed["empty"].ToString()); } else { Print("Deserialization failed!"); } } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ TestBasicSerialization(); return(INIT_SUCCEEDED); }
Создается функция TestBasicSerialization и объект JsonValue с именем root. С помощью operator[] присваиваются значения: string для hello world, number для 42, double для 3.14159, boolean для true, empty для пустой строки. Это охватывает основные JSON-типы JsonString, JsonInteger, JsonDouble и JsonBoolean. Затем для root вызывается SerializeToString, генерирующий JSON-строку. Она сохраняется в json и выводится в терминал MetaTrader 5 с помощью Print для проверки.
Далее создается объект JsonValue с именем "parsed", для которого вызывается DeserializeFromString со строкой "json" для восстановления JSON-структуры. Если метод возвращает true, выводится строка "Deserialization successful", а с помощью ToString, ToInteger с приведением к int, ToDouble и ToBoolean извлекаются и выводятся значения "string", "number", "double", "boolean" и "empty" через operator[]. При ошибке выводится "Deserialization failed!". Наконец, в OnInit вызывается TestBasicSerialization для выполнения теста при инициализации программы, после чего возвращается INIT_SUCCEEDED как признак успешной настройки. Получаем следующий результат.

На изображении видно, что базовая сериализация выполнена успешно. Теперь проверим базовую десериализацию.
void TestBasicDeserialization(){ Print("\n--- Testing Basic Deserialization ---"); string testJson = "{\"name\":\"John\",\"age\":30,\"isStudent\":false,\"salary\":1500.75}"; JsonValue parsed; if(parsed.DeserializeFromString(testJson)){ Print("Parsed successfully:"); Print("Name: ", parsed["name"].ToString()); Print("Age: ", (int)parsed["age"].ToInteger()); Print("Is Student: ", parsed["isStudent"].ToBoolean()); Print("Salary: ", parsed["salary"].ToDouble()); } else { Print("Failed to parse JSON"); } }
После тестирования получаем следующий результат.

Этот тест также выполнен успешно. Теперь увеличим уровень сложности теста.
void TestComplexObject(){ Print("\n--- Testing Complex Object ---"); JsonValue root; root["person"]["name"] = "Alice"; root["person"]["age"] = 25; root["person"]["isActive"] = true; root["person"]["score"] = 95.5; root["person"]["address"]["street"] = "123 Main St"; root["person"]["address"]["city"] = "New York"; root["person"]["address"]["zipcode"] = "10001"; root["person"]["hobbies"].AddChild("reading"); root["person"]["hobbies"].AddChild("gaming"); root["person"]["hobbies"].AddChild("coding"); root["person"]["preferences"]["theme"] = "dark"; root["person"]["preferences"]["notifications"] = true; string json = root.SerializeToString(); Print("Complex JSON: ", json); JsonValue parsed; if(parsed.DeserializeFromString(json)){ Print("Round-trip successful"); Print("Name: ", parsed["person"]["name"].ToString()); Print("Age: ", (int)parsed["person"]["age"].ToInteger()); Print("City: ", parsed["person"]["address"]["city"].ToString()); Print("Zipcode: ", parsed["person"]["address"]["zipcode"].ToString()); Print("Theme: ", parsed["person"]["preferences"]["theme"].ToString()); Print("Hobby count: ", ArraySize(parsed["person"]["hobbies"].m_children)); } } void TestArrayHandling(){ Print("\n--- Testing Array Handling ---"); JsonValue root; // Array of numbers for(int i = 0; i < 5; i++){ root["numbers"].AddChild(i * 10); } // Array of mixed types root["mixed"].AddChild("string"); root["mixed"].AddChild(123); root["mixed"].AddChild(45.67); root["mixed"].AddChild(true); // Array of objects JsonValue item; item["id"] = 1; item["name"] = "Item 1"; item["price"] = 19.99; root["items"].AddChild(item); item["id"] = 2; item["name"] = "Item 2"; item["price"] = 29.99; root["items"].AddChild(item); // Nested arrays JsonValue nestedArray; nestedArray.AddChild("nested1"); nestedArray.AddChild("nested2"); root["nested"].AddChild(nestedArray); string json = root.SerializeToString(); Print("Array JSON: ", json); JsonValue parsed; if(parsed.DeserializeFromString(json)){ Print("Numbers array length: ", ArraySize(parsed["numbers"].m_children)); Print("Mixed array length: ", ArraySize(parsed["mixed"].m_children)); Print("Items array length: ", ArraySize(parsed["items"].m_children)); Print("First item name: ", parsed["items"][0]["name"].ToString()); Print("Second item price: ", parsed["items"][1]["price"].ToDouble()); } } void TestErrorHandling(){ Print("\n--- Testing Error Handling ---"); JsonValue parsed; // Test invalid JSON string invalidJson = "{\"name\": \"test\", \"age\": }"; if(!parsed.DeserializeFromString(invalidJson)){ Print("✓ Correctly rejected invalid JSON"); } // Test malformed JSON string malformedJson = "{\"name\": \"test\" \"age\": 30}"; // Missing comma if(!parsed.DeserializeFromString(malformedJson)){ Print("✓ Correctly rejected malformed JSON"); } // Test empty string if(!parsed.DeserializeFromString("")){ Print("✓ Correctly handled empty string"); } // Test incomplete object string incompleteJson = "{\"name\": \"test\""; if(!parsed.DeserializeFromString(incompleteJson)){ Print("✓ Correctly rejected incomplete JSON"); } }
После тестирования получаем следующий результат.

Теперь проверим производительность, вложенные структуры и типы данных.
void TestPerformance(){ Print("\n--- Testing Performance ---"); int startTime = GetTickCount(); int iterations = 100; int successCount = 0; for(int i = 0; i < iterations; i++){ JsonValue root; root["test_id"] = i; root["name"] = "Test Item " + IntegerToString(i); root["value"] = i * 1.5; root["active"] = (i % 2 == 0); // Add array for(int j = 0; j < 5; j++){ root["tags"].AddChild("tag" + IntegerToString(j)); } string json = root.SerializeToString(); JsonValue parsed; if(parsed.DeserializeFromString(json)){ successCount++; } } int endTime = GetTickCount(); Print("Performance: ", iterations, " iterations in ", endTime - startTime, "ms"); Print("Success rate: ", successCount, "/", iterations, " (", DoubleToString(successCount*100.0/iterations, 1), "%)"); } void TestNestedStructures(){ Print("\n--- Testing Nested Structures ---"); JsonValue root; // Deep nesting root["level1"]["level2"]["level3"]["level4"]["value"] = "deep_nested"; root["level1"]["level2"]["level3"]["level4"]["number"] = 999; // Array of objects with nesting JsonValue user; user["name"] = "John"; user["profile"]["age"] = 30; user["profile"]["settings"]["theme"] = "dark"; user["profile"]["settings"]["notifications"] = true; root["users"].AddChild(user); user["name"] = "Jane"; user["profile"]["age"] = 25; user["profile"]["settings"]["theme"] = "light"; user["profile"]["settings"]["notifications"] = false; root["users"].AddChild(user); string json = root.SerializeToString(); Print("Nested JSON: ", json); JsonValue parsed; if(parsed.DeserializeFromString(json)){ Print("✓ Nested structures parsed successfully"); Print("Deep value: ", parsed["level1"]["level2"]["level3"]["level4"]["value"].ToString()); Print("User count: ", ArraySize(parsed["users"].m_children)); Print("First user theme: ", parsed["users"][0]["profile"]["settings"]["theme"].ToString()); Print("Second user age: ", (int)parsed["users"][1]["profile"]["age"].ToInteger()); } } void TestDataTypes(){ Print("\n--- Testing Data Types ---"); JsonValue root; // All supported data types root["string_type"] = "hello world"; root["int_type"] = 42; root["long_type"] = 1234567890123; root["double_type"] = 3.14159265358979; root["bool_true"] = true; root["bool_false"] = false; root["empty_string"] = ""; // Scientific notation root["scientific_positive"] = 1.23e+10; root["scientific_negative"] = 1.23e-10; string json = root.SerializeToString(); Print("Data Types JSON: ", json); JsonValue parsed; if(parsed.DeserializeFromString(json)){ Print("✓ All data types parsed successfully"); Print("String: ", parsed["string_type"].ToString()); Print("Integer: ", (int)parsed["int_type"].ToInteger()); Print("Long: ", parsed["long_type"].ToInteger()); Print("Double: ", parsed["double_type"].ToDouble()); Print("Bool True: ", parsed["bool_true"].ToBoolean()); Print("Bool False: ", parsed["bool_false"].ToBoolean()); Print("Scientific Positive: ", parsed["scientific_positive"].ToDouble()); Print("Scientific Negative: ", parsed["scientific_negative"].ToDouble()); } }
Получаем следующий результат.

Все прошло успешно. Теперь проверим экранированные символы.
void TestEscapeCharacters(){ Print("\n--- Testing Escape Characters ---"); JsonValue root; // Various escape sequences - using only MQL5 supported escapes root["backslash"] = "\\\\"; // Double backslash for single backslash root["quote"] = "\\\""; // Escaped quote root["newline"] = "\\n"; // Escaped newline root["tab"] = "\\t"; // Escaped tab root["carriage_return"] = "\\r"; // Escaped carriage return // For form feed and backspace, we need to use their actual ASCII codes // since MQL5 doesn't support \f and \b escape sequences root["form_feed"] = "\\u000C"; // Unicode escape for form feed root["backspace"] = "\\u0008"; // Unicode escape for backspace root["mixed_escapes"] = "Line1\\nLine2\\tTabbed\\\"Quoted\\\"\\\\Backslash"; string json = root.SerializeToString(); Print("Escape Chars JSON: ", json); JsonValue parsed; if(parsed.DeserializeFromString(json)){ Print("✓ Escape characters parsed successfully"); Print("Backslash: '", parsed["backslash"].ToString(), "'"); Print("Quote: '", parsed["quote"].ToString(), "'"); Print("Newline: '", parsed["newline"].ToString(), "'"); Print("Tab: '", parsed["tab"].ToString(), "'"); Print("Carriage Return: '", parsed["carriage_return"].ToString(), "'"); Print("Form Feed: '", parsed["form_feed"].ToString(), "'"); Print("Backspace: '", parsed["backspace"].ToString(), "'"); Print("Mixed: '", parsed["mixed_escapes"].ToString(), "'"); } else { Print("✗ Escape characters parsing failed"); } }
Результат показан ниже.

Все прошло успешно. Поскольку мы убедились, что реализация JSON работоспособна, ее можно использовать для создания интеграций с ИИ в будущих программах.
Заключение
В итоге мы разработали модуль разбора JSON на языке MQL5 с классом JsonValue для сериализации и десериализации JSON-данных, критически важных для взаимодействия с API. Методы SerializeToString, DeserializeFromArray и EscapeString, а также тестирование через TestBasicSerialization обеспечивают надежную обработку различных JSON-структур и создают прочную основу для будущих торговых систем на основе ИИ. В следующих частях мы займемся интеграцией ИИ и взаимодействием с ним в торговых приложениях. Оставайтесь с нами.
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19562
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Изучение стандартной библиотеки MQL5 (Часть 10): Канал полиномиальной регрессии
Возможности Мастера MQL5, которые вам нужно знать (Часть 91): Использование списков с пропусками и сети Хопфилда в пользовательском классе трейлинга
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Ознакомьтесь с новой статьей: «Создание торговых систем на базе искусственного интеллекта в MQL5 (Часть 1): реализация обработки JSON для API искусственного интеллекта».
Автор: Аллан Мунене Мутирия
Выглядит потрясающе. С чего начать?
Файл для скачивания — это a._JSON_Code_File.mq5 и советник?
Я переименовал файл A_JSON_Code_File.mq5 (так было понятнее), а затем скомпилировал. Ошибок не было
Однако на моей платформе я не увидел никаких упоминаний о статусе EXPERT — то есть я не смог загрузить и запустить его.
Просто нужно «поиграться», попробовать и понять, что происходит и почему.
Большое спасибо за то, что поделились тем, что, судя по всему, является фантастической возможностью использовать ИИ
Выглядит потрясающе. С чего бы начать?
Файл для скачивания — это a._JSON_Code_File.mq5 и EA?
Я переименовал файл A_JSON_Code_File.mq5 (так было понятнее), а затем скомпилировал. Ошибок не было.
Однако на моей платформе я не увидел никаких упоминаний об EXPERT — то есть я не смог загрузить и запустить его.
Просто нужно «поиграться», попробовать и понять, что происходит и почему.
Большое спасибо за то, что поделились этой, судя по всему, фантастической возможностью использовать ИИ