English Deutsch 日本語
preview
Создание торговых систем с искусственным интеллектом на MQL5 (Часть 1): Реализация обработки JSON для API ИИ

Создание торговых систем с искусственным интеллектом на MQL5 (Часть 1): Реализация обработки JSON для API ИИ

MetaTrader 5Торговые системы |
53 2
Allan Munene Mutiiria
Allan Munene Mutiiria

Введение

В этой серии рассматривается интеграция искусственного интеллекта (ИИ) в торговые системы на языке MetaQuotes Language 5 (MQL5). В первой части разрабатывается надежный модуль разбора JSON для обмена данными при работе с API ИИ, например ChatGPT. Основное внимание уделяется созданию основы для обработки JSON-структур, обеспечивающей бесперебойное взаимодействие с ИИ-сервисами в будущих торговых приложениях. Мы рассмотрим следующие темы:

  1. Понимание JSON и его роли в интеграции ИИ
  2. Реализация на MQL5
  3. Тестирование модуля разбора JSON
  4. Заключение

К концу статьи у вас будет надежная основа для работы с 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, и десериализовывать ответы в пригодные для использования данные. Если эта терминология кажется слишком специфической, ниже объясняется, что такое сериализация и десериализация данных.

SERIALIZATION & DESERIALIZATION

План реализации

План реализации включает создание класса для обработки JSON со следующими возможностями:

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

Этот модуль будет протестирован на разборе типичных ответов API ИИ, например OpenAI. Также мы проверим его способность точно сериализовать торговые промпты, как показано ниже.

MQL5 AI RESPONSES

Освоив разбор 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 &currentIndex);
   virtual bool ExtractStringFromArray(char &jsonCharacterArray[],int arrayLength,int &currentIndex);
   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, например эмодзи. Ниже показан пример того, как это выглядит.

UNICODE SAMPLE

Теперь можно определить эти методы так, чтобы они точно выполняли свое назначение.

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 &currentIndex){
   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 &currentIndex){
   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-запросов и надежного разбора ответов ИИ. Ниже приведен пример использования специальных символов, которые будут корректно интерпретированы.

EXAMPLE USAGE OF SPECIAL CHARACTERS

После завершения реализации класса можно использовать логику разбора 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 как признак успешной настройки. Получаем следующий результат.

INITIAL BASIC SERIALIZATION

На изображении видно, что базовая сериализация выполнена успешно. Теперь проверим базовую десериализацию.

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");
   }
}

После тестирования получаем следующий результат.

INITIAL BASIC DESERIALIZATION

Этот тест также выполнен успешно. Теперь увеличим уровень сложности теста.

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");
   }
}

После тестирования получаем следующий результат.

COMPLEX TESTS

Теперь проверим производительность, вложенные структуры и типы данных.

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());
   }
}

Получаем следующий результат.

PERFORMANCE, NESTED STRUCTURES & DATA TYPE TEST

Все прошло успешно. Теперь проверим экранированные символы.

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");
   }
}

Результат показан ниже.

ESCAPE CHARACTERS

Все прошло успешно. Поскольку мы убедились, что реализация JSON работоспособна, ее можно использовать для создания интеграций с ИИ в будущих программах.


Заключение

В итоге мы разработали модуль разбора JSON на языке MQL5 с классом JsonValue для сериализации и десериализации JSON-данных, критически важных для взаимодействия с API. Методы SerializeToString, DeserializeFromArray и EscapeString, а также тестирование через TestBasicSerialization обеспечивают надежную обработку различных JSON-структур и создают прочную основу для будущих торговых систем на основе ИИ. В следующих частях мы займемся интеграцией ИИ и взаимодействием с ним в торговых приложениях. Оставайтесь с нами.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/19562

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Peter Wlliams
Peter Wlliams | 23 сент. 2025 в 23:44

Выглядит потрясающе. С чего начать?

Файл для скачивания — это a._JSON_Code_File.mq5 и советник?

Я переименовал файл A_JSON_Code_File.mq5 (так было понятнее), а затем скомпилировал. Ошибок не было

Однако на моей платформе я не увидел никаких упоминаний о статусе EXPERT — то есть я не смог загрузить и запустить его.

Просто нужно «поиграться», попробовать и понять, что происходит и почему.


Большое спасибо за то, что поделились тем, что, судя по всему, является фантастической возможностью использовать ИИ

Allan Munene Mutiiria
Allan Munene Mutiiria | 24 сент. 2025 в 11:44
Peter Wlliams #:

Выглядит потрясающе. С чего бы начать?

Файл для скачивания — это a._JSON_Code_File.mq5 и EA?

Я переименовал файл A_JSON_Code_File.mq5 (так было понятнее), а затем скомпилировал. Ошибок не было.

Однако на моей платформе я не увидел никаких упоминаний об EXPERT — то есть я не смог загрузить и запустить его.

Просто нужно «поиграться», попробовать и понять, что происходит и почему.


Большое спасибо за то, что поделились этой, судя по всему, фантастической возможностью использовать ИИ

Спасибо за отзыв. Похоже, вам действительно стоит прочитать статью. Речь в ней идет о JSON, который позволит интегрировать ИИ в будущих версиях.
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Изучение стандартной библиотеки MQL5 (Часть 10): Канал полиномиальной регрессии Изучение стандартной библиотеки MQL5 (Часть 10): Канал полиномиальной регрессии
Сегодня мы рассмотрим еще одну составляющую ALGLIB и задействуем её математический потенциал, чтобы создать индикатор на основе полиномиальной регрессии (Polynomial Regression Channel). К концу этой статьи вы получите практические знания о разработке индикаторов с использованием стандартной библиотеки MQL5, а также полностью функциональный, математически обоснованный исходный код индикатора.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Возможности Мастера MQL5, которые вам нужно знать (Часть 91): Использование списков с пропусками и сети Хопфилда в пользовательском классе трейлинга Возможности Мастера MQL5, которые вам нужно знать (Часть 91): Использование списков с пропусками и сети Хопфилда в пользовательском классе трейлинга
В следующем исследовании идей, тестируемых с помощью Мастера MQL5, рассмотрим, могут ли списки с пропусками и сеть Хопфилда сформировать стратегию трейлинга, направленную на защиту прибыли. Как уже отмечалось, управлению трейлинг-стопом в торговых системах часто уделяется меньше внимания, чем сигналам входа или управлению капиталом. Трейлинг-стопы могут сыграть решающую роль, например на трендовых рынках, поэтому данный подход тестируется на GBPJPY.