English Deutsch
preview
MQL5でのAI搭載取引システムの構築(第1回):AI API向けJSON処理の実装

MQL5でのAI搭載取引システムの構築(第1回):AI API向けJSON処理の実装

MetaTrader 5トレーディングシステム |
109 2
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

本連載では、MetaQuotes Language 5 (MQL5)を用いた取引システムへの人工知能(AI)統合を紹介します。本記事では、まずAI Application Programming Interface (API)(例:ChatGPT)とのやり取りを可能にするJavaScript Object Notation (JSON)解析フレームワークを開発します。目的は、AIサービスとのシームレスな通信を実現し、将来の取引アプリケーションに活かせる基盤を構築することです。本記事で扱う内容は以下の通りです。

  1. JSONの理解とAI統合における役割
  2. MQL5での実装
  3. JSONパーサーのテスト
  4. 結論

この記事を通して、JSONデータの取り扱いに関する確かな基礎を身につけ、AI駆動型取引システムへの第一歩を踏み出すことができます。


JSONの理解とAI統合における役割

JSON (JavaScript Object Notation)は、システム間でデータを構造化し、送受信するための軽量テキスト形式です。そのシンプルさ、可読性、プログラミング言語間の互換性から、特にWebベースAPIで広く利用されています。AI駆動型取引システム(私たちが構築しようとしているようなシステム)の文脈においては、JSONはOpenAIのChatGPTのようなAI APIとデータを交換する標準フォーマットとして機能します。これにより、MQL5アプリケーションは取引関連のプロンプトを送信し、意思決定に利用可能な構造化されたレスポンスを受け取ることができます。本記事では、このAPIとのやり取りを扱うJSON解析フレームワークの構築に焦点を当て、将来的にAIによる洞察を自動取引戦略に統合するための基盤を作ります。

JSONとは何か、そしてなぜ重要か

JSONはデータをキーと値のペア、配列、ネストされたオブジェクトとして表現します。この構造により、市場データや取引シグナル、AIレスポンスのような複雑な情報を、人間にも機械にも読み取り可能な形式でエンコードするのに最適です。たとえば、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のエキスパートアドバイザー(EA)はこれらを解析して、AIからの取引関連のレスポンスなど、必要な情報を抽出する必要があります。JSONはAI統合において非常に重要な役割を果たします。なぜなら、APIはレスポンスをJSON形式で返すため、プログラム側では入力データをJSONに変換するシリアライズ処理と、出力データを利用可能な形に解析するデシリアライズ処理が必要となり、動的な取引判断を可能にするからです。もしこの説明が専門用語ばかりに感じられる場合、シリアライズとデシリアライズの概念を視覚的に理解できます。

シリアライズとデシリアライズ

実装ロードマップ

本フレームワークの実装計画では、次の機能をサポートするJSON処理クラスを作成します。

  • データ表現:JSON値を格納するクラスで、型、キー、値(文字列、数値、真偽値など)の属性を持ち、入れ子構造を扱うための子要素用配列を保持します。
  • 解析ロジック:JSON文字列をデシリアライズしてオブジェクトに変換するメソッドを実装します。文字を処理しながらオブジェクト、配列、プリミティブ型を識別し、空白文字やエスケープ文字を正しく処理します。
  • シリアライズロジック:内部データをJSON文字列に変換するメソッドを実装します。APIリクエスト用に適切なフォーマットを保証し、特殊文字のエスケープもおこないます。
  • エラー処理:無効なJSON、型の不一致、範囲外アクセスなどを厳密にチェックし、API通信中のクラッシュを防止します。
  • ユーザーインターフェース準備:将来のUI統合に備えて、プロンプトの入力やAIレスポンスの表示をサポートする準備をします。これらは解析済みのJSONデータに依存します。

このフレームワークをテストし、OpenAIなどの典型的なAI APIレスポンスを正しく解析できるか、取引関連プロンプトを正確にシリアライズできるかを確認します。

MQL5 AIレスポンス

本記事でJSON解析を習得することで、将来のAI駆動型プログラムが複雑なデータ構造を正確に処理できるようになります。これにより、プライスアクションやチャートパターン、AI生成のインサイトを組み合わせた高度な取引戦略の構築が可能になります。それでは、MQL5での実装に進みましょう。


MQL5での実装

統合を実装するために、まず包括的なJSON解析クラスを作成します。このクラスは、最初のプロンプト送信や将来の応用に使用します。以下にその実装方法を示します。

//+------------------------------------------------------------------+
//|                                            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解析フレームワークの実装を開始し、まずAI API統合のためのJSONデータを扱う基礎クラスであるJsonValueクラスに焦点を当てます。まず、デバッグ出力を制御するためにDEBUG_PRINTマクロを定義してfalseに設定し、本番環境ではログを最小限に抑えるようにします。次に、JSONデータ型を分類するための列挙型JsonValueTypeを定義します。値はJsonUndefined、JsonNull、JsonBoolean、JsonInteger、JsonDouble、JsonString、JsonArray、JsonObjectで、APIから返される多様なJSONデータ型を処理できるようにします。

次に、JsonValueクラスを実装します。このクラスには publicメンバーとして、ネストされたJSON要素を格納する配列m_children、パース中にキーを保持する文字列m_keyおよびm_temporaryKey、階層関係を保持する親オブジェクトへのポインタm_parent、データ型を示すJsonValueType型の変数m_type、それぞれのデータ型の値を格納する変数m_booleanValue、m_integerValue、m_doubleValue、m_stringValue、さらに文字エンコーディング用の静的変数encodingCodePageが含まれます。JsonValueオブジェクトを初期化するために、複数のコンストラクタを提供しています。デフォルトコンストラクタではResetを呼び出して初期化し、親オブジェクトと型を指定するコンストラクタ、型と文字列値を指定するコンストラクタ、整数、長整数、倍精度浮動小数点、真偽値用のコンストラクタ、およびコピーコンストラクタを備え、JSON要素を柔軟に生成できるようにしています。

Resetメソッドは、すべてのメンバーをデフォルト値にリセットします。具体的には、親ポインタはnull、文字列は空文字、型はJsonUndefined、数値はゼロ、子要素配列は空になります。CopyFrom、CopyDataFrom、CopyChildrenFromメソッドは、子要素を含むJSON構造全体のディープコピーをおこない、コピー後も親ポインタが正しく再割り当てされるようにします。この基礎的な実装により、JSONデータを解析・操作するための構造が整い、将来のAI 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メソッドを実装します。このメソッドは、m_typeがJsonDoubleまたはJsonIntegerの場合にtrueを返すことで、JSON値が数値であるかどうかを判定し、数値データに対する型固有の処理を可能にします。次に、FindChildByKeyメソッドを開発します。このメソッドはm_children配列を後ろから順に反復処理し、指定したキーに一致する子要素を探して、そのポインタを返すか、見つからなければNULLを返します。これにより、ネストされたJSONオブジェクトへのアクセスが容易になります。

続いて、JsonValue、integer、long、double、boolean、string型に対する代入演算子(operator=)をオーバーロードします。これにより、m_typeおよび対応する値フィールド(m_integerValue、m_doubleValue、m_stringValue、m_booleanValue)が更新され、integerをdoubleやstringに変換するなど、型の一貫性が保たれます。また、integer、long、double、boolean、string型に対する比較演算子(operator==およびoperator!=)も実装し、各フィールドとの直接比較を可能にします。

さらに、値をそれぞれの型で取得するための変換メソッドとして、ToInteger、ToDouble、ToBoolean、ToStringを提供します。SetFromStringメソッドでは、指定されたJsonValueTypeに応じてJSON値を設定し、boolean、integer、double、string型を入力文字列から変換して関連するフィールドを更新します。string値はUnescapeStringでエスケープ解除されます。GetSubstringFromArrayメソッドは、文字配列から指定範囲の部分文字列を抽出し、CharArrayToStringとencodingCodePageを使って文字列に変換します。

最後に、子要素を管理するメソッドを実装します。AddChild(JsonValue、integer、long、double、boolean、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、JsonIntegerおよびJsonDoubleは文字列に変換、JsonStringはエスケープされた値、JsonArrayは括弧で囲まれた子要素、JsonObjectはキー付きの子要素として処理します。入れ子構造に対しては、子要素を再帰的にシリアライズします。また、利便性のために、文字列を生成し、パラメータ付きバージョンを呼び出して結果を返すオーバーロードも提供します。

次に、DeserializeFromStringメソッドを実装します。このメソッドはJSON文字列とエンコーディング(デフォルトはCP_ACP)を受け取り、オブジェクトをリセットし、encodingCodePageを設定します。その後、StringToCharArrayで文字列を文字配列に変換し、DeserializeFromArrayに処理を委譲します。エンコーディング付きのDeserializeFromArrayオーバーロードでは、インデックスを初期化し、オブジェクトをリセットしてencodingCodePageを設定し、メインのDeserializeFromArrayメソッドを呼び出します。このメソッドは文字配列を反復処理し、空白文字の処理、配列(「[」から「]」)、オブジェクト(「{」から「}」)、真偽値(trueまたはfalse)、null、数値(有効文字から整数または倍精度浮動小数点を判定)、文字列(エスケープ処理付き)を解析し、必要に応じて子要素を生成し、階層をm_parentおよびm_temporaryKeyで管理します。

最後に、ExtractStringFromArrayメソッドを実装します。このメソッドは文字配列内の文字列を解析し、エスケープ文字(\n、\t、Unicode形式の\uXXXXなど)を処理します。将来的に絵文字などのUnicode文字を使用する場合にも対応できるように設計されており、正確な文字列抽出を保証します。これらは次のようになります。

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)に初期化し、文字列変換時のデフォルトの文字エンコーディングを設定します。これにより、AI APIから取得したJSONデータとの互換性が確保されます。次に、HasChildWithKeyメソッドを実装します。このメソッドはm_children配列を順方向に反復処理し、指定されたキーと一致する子要素、オプションで特定のJsonValueType(デフォルトはJsonUndefinedで型を無視)を探します。見つかればその子のポインタを返し、見つからなければNULLを返します。これにより、FindChildByKeyよりも効率的に検索でき、型固有のチェックも可能になります。

次に、オーバーロードされたoperatorメソッドを開発します。このメソッドでは、m_typeが未定義の場合はJsonObjectに設定し、FindChildByKeyで指定されたキーの子要素を検索します。見つからなければ、新しいJsonValueを作成してキーを設定し、AddChildでm_childrenに追加します。これにより、既存または新規の子要素のポインタを返し、オブジェクトへのシームレスなアクセスを実現します。同様に、配列用のoperatorメソッドでは、m_typeが未定義の場合はJsonArrayに設定し、インデックスが配列サイズを超える場合はAddChildで未定義の要素を追加してm_childrenを拡張し、指定されたインデックスの要素へのポインタを返します。これにより、安全に配列要素にアクセスできます。

最後に、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
}

解析フレームワークの実装を続け、重要なシリアライズおよびデシリアライズのメソッドに焦点を当てます。

まず、SerializeToStringメソッドを定義します。このメソッドはJSON構造を文字列に変換します。未定義の型はスキップし、includeCommaがtrueの場合はカンマを追加し、includeKeyがtrueの場合はStringFormatを用いてキーを付加します。m_typeごとの処理は以下の通りです。JsonNullの場合はnullを追加し、JsonBooleanの場合はm_booleanValueに応じてtrueまたはfalseを追加します。JsonIntegerはm_integerValueをIntegerToStringで変換し、JsonDoubleはm_doubleValueをDoubleToStringで変換します。JsonStringはm_stringValueをEscapeStringでエスケープし、引用符で囲んで追加します(空文字の場合はnull)。JsonArrayは子要素を括弧で囲み、再帰的にSerializeToStringを呼び出します(キーは含めず、先頭以外の要素にカンマを追加)。JsonObjectは子要素を波括弧で囲み、キーを含めて処理します。さらに、利便性のために、文字列を生成し、デフォルト引数でパラメータ付きバージョンを呼び出して結果を返すオーバーロードも提供します。

次に、DeserializeFromArrayを実装します。このメソッドは文字配列を解析してJSON構造を構築します。有効な数値文字列("0123456789+-.eE")を使用し、配列を順に反復処理します。空白文字はスキップし、配列("[")の場合はm_typeをJsonArrayに設定し、閉じ括弧が現れるまで子要素を再帰的にデシリアライズします。配列の閉じ括弧を検証し、キーと値の区切り(":")は新しい子要素にm_temporaryKeyを割り当てることで処理し、値の区切り(",")も適切に処理します。オブジェクト("{")の場合はm_typeをJsonObjectに設定し、閉じ波括弧が現れるまで子要素をデシリアライズし、閉じ波括弧を検証します。真偽値(trueまたはfalse)はm_booleanValueに設定し、nullはm_typeをJsonNullに設定します。数値は小数点や指数表記を判定してm_typeをJsonDoubleまたは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)を処理し、4桁の16進数が有効かを検証します。無効な16進数や配列範囲外の場合はfalseを返し、DEBUG_PRINTで不正なケースをデバッグ可能にします。これにより、JSON解析において正確な文字列抽出が保証されます。

次に、EscapeStringメソッドを開発します。このメソッドは文字列をJSON準拠形式に変換します。まずStringToShortArrayで文字列をショート配列に変換し、出力配列のサイズを入力の2倍に拡張してエスケープに対応します。各文字を反復処理し、バックスラッシュ、引用符、スラッシュ、バックスペース(8)、フォームフィード(12)、改行、復帰、タブを前置バックスラッシュ付きでエスケープします(例:改行は\n)。その他の文字はそのままコピーし、最終的にShortArrayToStringを使ってエスケープ済み文字列を返します。

最後に、UnescapeStringメソッドを実装します。このメソッドはエスケープ済み文字列を元に戻します。入力文字列をショート配列に変換し、出力配列を入力長にリサイズして、エスケープシーケンスを処理します。バックスラッシュを検出した場合は次の文字を解釈し(例:\nは改行、\tはタブ)、アンエスケープされた文字を出力にコピーします。エスケープされていない文字はそのままコピーします。最終的にShortArrayToStringで文字列を返すか、リサイズに失敗した場合はNULLを返します。これらのメソッドにより、JsonValueクラスはJSON文字列内の特殊文字を正確に処理できるようになり、APIリクエストの正しいフォーマットやAIレスポンスの解析を堅牢かつ信頼性高くおこなえるようになります。たとえば、特殊文字を含むJSON文字列も正しく解釈され、シームレスな通信と理解が可能になります。

特殊文字の使用例

クラスの実装が完了したので、これでパースロジックを使用する準備が整いました。しかし、その前にまずパーサーをテストし、すべてが正しく動作することを確認します。次のセクションでそのテストをおこないます。


JSONパーサーのテスト

JSON解析フレームワークの信頼性を確保するために、MQL5でJsonValueクラスを厳密にテストし、AI API統合に不可欠なさまざまなJSON構造を正しく処理できることを検証します。以下では、テストアプローチ、テストケース、期待される結果、取引環境におけるパーサー機能の検証方法を概説します。テストでは、コード関数を定義し、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に格納し、PrintMetaTrader 5ターミナルに出力して確認します。

次に、新しいJsonValueオブジェクト「parsed」を作成し、DeserializeFromStringをjson文字列で呼び出してJSON構造を再構築します。戻り値がtrueであれば成功を意味し、「Deserialization successful」を出力します。その後、operator[]を使い、ToString、ToInteger(intにキャスト)、ToDouble、ToBooleanでそれぞれの値を取得し、キー"string"、"number"、"double"、"boolean"、"empty"を出力します。失敗した場合は「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実装が問題なく利用可能であることが確認できたので、今後作成するプログラムでAI統合をおこなう際に、このフレームワークを活用できます。


結論

本記事では、MQL5でJSON (JavaScript Object Notation)解析フレームワークを開発し、API (Application Programming Interface)とのやり取りに不可欠なJSONデータのシリアライズおよびデシリアライズを処理するJsonValueクラスを実装しました。SerializeToString、DeserializeFromArray、EscapeStringなどのメソッドや、TestBasicSerializationのようなテスト関数を通じて、多様なJSON構造を確実に処理できることを確認しました。これにより、将来のAI駆動型取引システムのための堅牢な基盤を構築できました。次回以降では、取引アプリケーションにおけるAIとの統合やインタラクションを実装していきますので、ご期待ください。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19562

添付されたファイル |
最後のコメント | ディスカッションに移動 (2)
Peter Wlliams
Peter Wlliams | 23 9月 2025 において 23:44

素晴らしい。何から始めたらいいのでしょうか?

ダウンロードファイルはa._JSON_Code_File.mq5とEAですか?

私はA_JSON_Code_File.mq5(より身近に見えた)の名前を変更し、その後コンパイルしました。 エラーはありませんでした。

しかし、私のプラットフォームではEXPERTとしてのリファレンスは見当たりませんでした。

つまり、ロードして実行することができなかったのです。ただ、"プレイ "して、何が起こり、なぜそうなるのかを理解する必要があります。


AIを活用する素晴らしい機会を共有してくれてありがとう。

Allan Munene Mutiiria
Allan Munene Mutiiria | 24 9月 2025 において 11:44
Peter Wlliams #:

素晴らしい。どこから始める?

ダウンロードファイルはa._JSON_Code_File.mq5とEAですか?

A_JSON_Code_File.mq5をリネームしてコンパイルしました。 エラーなし。

しかし、私のプラットフォームでは、EXPERTとしてのリファレンスは見当たりませんでした。

つまり、ロードして実行することができなかったのだ。ただ、"プレイ "して、何がなぜ起こるのかを理解する必要がある。


AIを活用する素晴らしい機会を共有してくれてありがとう。

フィードバックをありがとう。記事を読む必要があるようだ。将来のバージョンでAIの統合を可能にするためのJSONについてだ。
初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(XI) - ニュース取引における相関 初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(XI) - ニュース取引における相関
本記事では、金融相関の概念を活用して、主要な経済指標発表時に複数の通貨ペアを取引する際の判断効率を高める方法を検討します。特に、ニュースリリース時のボラティリティ上昇によるリスク増大という課題に焦点を当てます。
MQL5入門(第21回):ハーモニックパターン検出の自動化 MQL5入門(第21回):ハーモニックパターン検出の自動化
MetaTrader 5でMQL5を使ってガートリーハーモニックパターンを検出して表示する方法を学びます。この記事では、スイングポイントの特定からフィボナッチ比率の適用、チャート上へのパターン描画までの手順を順を追って解説し、視覚的に確認できる形で表示する方法を紹介します。
平均足を使ったプロフェッショナルな取引システムの構築(第2回):EAの開発 平均足を使ったプロフェッショナルな取引システムの構築(第2回):EAの開発
本記事では、MQL5を用いてプロフェッショナルな平均足ベースのエキスパートアドバイザー(EA)を開発する方法について解説します。入力パラメータ、列挙型、インジケーター、グローバル変数の設定方法から、コアとなる売買ロジックの実装までを順を追って説明します。また、開発したEAを金(ゴールド)でバックテストして、正しく動作するかどうかを検証する方法も学べます。
Parafrac V2オシレーター:パラボリックSARとATRの統合 Parafrac V2オシレーター:パラボリックSARとATRの統合
Parafrac V2オシレーターは、パラボリックSARとATR(Average True Range、平均真の範囲)を統合した高度なテクニカル分析ツールです。前バージョンのParafracオシレーターではフラクタルを使用していたため、過去や現在のシグナルを覆い隠すようなスパイクが発生しやすいという課題がありました。Parafrac V2ではATRによるボラティリティ測定を活用することで、トレンドや反転、ダイバージェンスの検出をより滑らかで信頼性の高い方法で行えるようになり、チャートの混雑や分析の過負荷を軽減できます。