MQL5でのAI搭載取引システムの構築(第1回):AI API向けJSON処理の実装
はじめに
本連載では、MetaQuotes Language 5 (MQL5)を用いた取引システムへの人工知能(AI)統合を紹介します。本記事では、まずAI Application Programming Interface (API)(例:ChatGPT)とのやり取りを可能にするJavaScript Object Notation (JSON)解析フレームワークを開発します。目的は、AIサービスとのシームレスな通信を実現し、将来の取引アプリケーションに活かせる基盤を構築することです。本記事で扱う内容は以下の通りです。
この記事を通して、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レスポンスを正しく解析できるか、取引関連プロンプトを正確にシリアライズできるかを確認します。

本記事で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 ¤tIndex); virtual bool ExtractStringFromArray(char &jsonCharacterArray[],int arrayLength,int ¤tIndex); virtual bool DeserializeFromString(string jsonString,int encoding=CP_ACP){ int currentIndex=0; //--- Initialize current index Reset(); //--- Reset the object JsonValue::encodingCodePage=encoding; //--- Set encoding code page char characterArray[]; //--- Declare character array int arrayLength=StringToCharArray(jsonString,characterArray,0,WHOLE_ARRAY,JsonValue::encodingCodePage); //--- Convert string to char array return DeserializeFromArray(characterArray,arrayLength,currentIndex); //--- Call deserialize from array } virtual bool DeserializeFromArray(char &jsonCharacterArray[],int encoding=CP_ACP){ int currentIndex=0; //--- Initialize current index Reset(); //--- Reset the object JsonValue::encodingCodePage=encoding; //--- Set encoding code page return DeserializeFromArray(jsonCharacterArray,ArraySize(jsonCharacterArray),currentIndex); //--- Call deserialize with size }
ここでは、SerializeToStringメソッドを実装します。このメソッドは、文字列の参照、includeKey、includeCommaのパラメータを受け取り、JSON構造を文字列に変換します。includeCommaがtrueの場合はカンマを追加し、includeKeyがtrueの場合はキーを含めます。異なる型に応じて処理をおこない、JsonNullはnull、JsonBooleanはtrueまたはfalse、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文字を使用する場合にも対応できるように設計されており、正確な文字列抽出を保証します。これらは次のようになります。

これで、これらのメソッドを定義し、意図した通りに正確に動作させることができます。
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 ¤tIndex){ string validNumericCharacters="0123456789+-.eE"; //--- Define valid number characters int startPosition=currentIndex; //--- Set start position for(; currentIndex<arrayLength; currentIndex++){ //--- Loop through array char currentCharacter=jsonCharacterArray[currentIndex]; //--- Get current character if(currentCharacter==0){break;} //--- Break if null character switch(currentCharacter){ //--- Handle based on character case '\t': case '\r': case '\n': case ' ': //--- Skip whitespace startPosition=currentIndex+1; //--- Update start position break; //--- Exit case case '[': //--- Start of array { startPosition=currentIndex+1; //--- Update start position if(m_type!=JsonUndefined){ //--- Check if type is undefined if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug if error return false; //--- Return false on error } m_type=JsonArray; //--- Set type to array currentIndex++; //--- Increment index JsonValue childValue(GetPointer(this),JsonUndefined); //--- Create child value while(childValue.DeserializeFromArray(jsonCharacterArray,arrayLength,currentIndex)){ //--- Deserialize children if(childValue.m_type!=JsonUndefined){AddChild(childValue);} //--- Add if not undefined if(childValue.m_type==JsonInteger || childValue.m_type==JsonDouble || childValue.m_type==JsonArray){currentIndex++;} //--- Increment if certain types childValue.Reset(); //--- Reset child childValue.m_parent=GetPointer(this); //--- Set parent if(jsonCharacterArray[currentIndex]==']'){break;} //--- Break if end of array currentIndex++; //--- Increment index if(currentIndex>=arrayLength){ //--- Check bounds if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } } return (jsonCharacterArray[currentIndex]==']' || jsonCharacterArray[currentIndex]==0); //--- Return true if properly ended } break; //--- Exit case case ']': //--- End of array if(!m_parent){return false;} //--- Return false if no parent return (m_parent.m_type==JsonArray); //--- Check parent is array case ':': //--- Key-value separator { if(m_temporaryKey==""){ //--- Check temporary key if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } JsonValue childValue(GetPointer(this),JsonUndefined); //--- Create child JsonValue *addedChild=AddChild(childValue); //--- Add child addedChild.m_key=m_temporaryKey; //--- Set key m_temporaryKey=""; //--- Clear temporary key currentIndex++; //--- Increment index if(!addedChild.DeserializeFromArray(jsonCharacterArray,arrayLength,currentIndex)){ //--- Deserialize child if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } break; //--- Exit case } case ',': //--- Value separator startPosition=currentIndex+1; //--- Update start if(!m_parent && m_type!=JsonObject){ //--- Check conditions if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } else if(m_parent){ //--- If has parent if(m_parent.m_type!=JsonArray && m_parent.m_type!=JsonObject){ //--- Check parent type if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } if(m_parent.m_type==JsonArray && m_type==JsonUndefined){return true;} //--- Return true for undefined in array } break; //--- Exit case case '{': //--- Start of object startPosition=currentIndex+1; //--- Update start if(m_type!=JsonUndefined){ //--- Check type if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } m_type=JsonObject; //--- Set type to object currentIndex++; //--- Increment index if(!DeserializeFromArray(jsonCharacterArray,arrayLength,currentIndex)){ //--- Recurse deserialize if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } return (jsonCharacterArray[currentIndex]=='}' || jsonCharacterArray[currentIndex]==0); //--- Check end break; //--- Exit case case '}': //--- End of object return (m_type==JsonObject); //--- Check type is object case 't': case 'T': //--- Start of true case 'f': case 'F': //--- Start of false if(m_type!=JsonUndefined){ //--- Check type if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } m_type=JsonBoolean; //--- Set type to boolean if(currentIndex+3<arrayLength){ //--- Check for true if(StringCompare(GetSubstringFromArray(jsonCharacterArray, currentIndex, 4), "true", false)==0){ //--- Compare substring m_booleanValue=true; //--- Set to true currentIndex+=3; //--- Advance index return true; //--- Return true } } if(currentIndex+4<arrayLength){ //--- Check for false if(StringCompare(GetSubstringFromArray(jsonCharacterArray, currentIndex, 5), "false", false)==0){ //--- Compare substring m_booleanValue=false; //--- Set to false currentIndex+=4; //--- Advance index return true; //--- Return true } } if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false break; //--- Exit case case 'n': case 'N': //--- Start of null if(m_type!=JsonUndefined){ //--- Check type if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } m_type=JsonNull; //--- Set type to null if(currentIndex+3<arrayLength){ //--- Check bounds if(StringCompare(GetSubstringFromArray(jsonCharacterArray,currentIndex,4),"null",false)==0){ //--- Compare substring currentIndex+=3; //--- Advance index return true; //--- Return true } } if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false break; //--- Exit case case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': case '+': case '.': //--- Start of number { if(m_type!=JsonUndefined){ //--- Check type if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } bool isDouble=false; //--- Initialize double flag int startOfNumber=currentIndex; //--- Set start of number while(jsonCharacterArray[currentIndex]!=0 && currentIndex<arrayLength){ //--- Loop to parse number currentIndex++; //--- Increment index if(StringFind(validNumericCharacters,GetSubstringFromArray(jsonCharacterArray,currentIndex,1))<0){break;} //--- Break if invalid char if(!isDouble){isDouble=(jsonCharacterArray[currentIndex]=='.' || jsonCharacterArray[currentIndex]=='e' || jsonCharacterArray[currentIndex]=='E');} //--- Set double flag } m_stringValue=GetSubstringFromArray(jsonCharacterArray,startOfNumber,currentIndex-startOfNumber); //--- Get number string if(isDouble){ //--- If double m_type=JsonDouble; //--- Set type to double m_doubleValue=StringToDouble(m_stringValue); //--- Convert to double m_integerValue=(long)m_doubleValue; //--- Convert to integer m_booleanValue=m_integerValue!=0; //--- Set boolean } else{ //--- Else integer m_type=JsonInteger; //--- Set type to integer m_integerValue=StringToInteger(m_stringValue); //--- Convert to integer m_doubleValue=(double)m_integerValue; //--- Convert to double m_booleanValue=m_integerValue!=0; //--- Set boolean } currentIndex--; //--- Decrement index return true; //--- Return true break; //--- Exit case } case '\"': //--- Start of string or key if(m_type==JsonObject){ //--- If object type currentIndex++; //--- Increment index int startOfString=currentIndex; //--- Set start of string if(!ExtractStringFromArray(jsonCharacterArray,arrayLength,currentIndex)){ //--- Extract string if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } m_temporaryKey=GetSubstringFromArray(jsonCharacterArray,startOfString,currentIndex-startOfString); //--- Set temporary key } else{ //--- Else value string if(m_type!=JsonUndefined){ //--- Check type if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } m_type=JsonString; //--- Set type to string currentIndex++; //--- Increment index int startOfString=currentIndex; //--- Set start of string if(!ExtractStringFromArray(jsonCharacterArray,arrayLength,currentIndex)){ //--- Extract string if(DEBUG_PRINT){Print(m_key+" "+string(__LINE__));} //--- Print debug return false; //--- Return false } SetFromString(JsonString,GetSubstringFromArray(jsonCharacterArray,startOfString,currentIndex-startOfString)); //--- Set from extracted string return true; //--- Return true } break; //--- Exit case } } return true; //--- Return true at end }
解析フレームワークの実装を続け、重要なシリアライズおよびデシリアライズのメソッドに焦点を当てます。
まず、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 ¤tIndex){ for(; jsonCharacterArray[currentIndex]!=0 && currentIndex<arrayLength; currentIndex++){ //--- Loop through string char currentCharacter=jsonCharacterArray[currentIndex]; //--- Get current char if(currentCharacter=='\"') break; //--- Break on closing quote if(currentCharacter=='\\' && currentIndex+1<arrayLength){ //--- Handle escape currentIndex++; //--- Increment for escaped char currentCharacter=jsonCharacterArray[currentIndex]; //--- Get escaped char switch(currentCharacter){ //--- Handle escaped type case '/': case '\\': case '\"': case 'b': case 'f': case 'r': case 'n': case 't': break; //--- Allowed escapes case 'u': //--- Unicode escape { currentIndex++; //--- Increment for(int hexDigitIndex=0; hexDigitIndex<4 && currentIndex<arrayLength && jsonCharacterArray[currentIndex]!=0; hexDigitIndex++,currentIndex++){ //--- Loop hex digits if(!((jsonCharacterArray[currentIndex]>='0' && jsonCharacterArray[currentIndex]<='9') || (jsonCharacterArray[currentIndex]>='A' && jsonCharacterArray[currentIndex]<='F') || (jsonCharacterArray[currentIndex]>='a' && jsonCharacterArray[currentIndex]<='f'))){ //--- Check hex if(DEBUG_PRINT){Print(m_key+" "+CharToString(jsonCharacterArray[currentIndex])+" "+string(__LINE__));} //--- Print debug return false; //--- Return false on invalid hex } } currentIndex--; //--- Decrement after loop break; //--- Exit case } default: break; //--- Handle other (commented return false) } } } return true; //--- Return true } //+------------------------------------------------------------------+ //| Escapes special characters in string | //+------------------------------------------------------------------+ string JsonValue::EscapeString(string stringValue){ ushort inputCharacters[], escapedCharacters[]; //--- Declare arrays int inputLength=StringToShortArray(stringValue, inputCharacters); //--- Convert string to short array if(ArrayResize(escapedCharacters, 2*inputLength)!=2*inputLength){return NULL;} //--- Resize escaped array, return NULL on fail int escapedIndex=0; //--- Initialize escaped index for(int inputIndex=0; inputIndex<inputLength; inputIndex++){ //--- Loop through input switch(inputCharacters[inputIndex]){ //--- Handle special chars case '\\': escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='\\'; //--- Add backslash escapedIndex++; //--- Increment break; //--- Exit case case '"': escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='"'; //--- Add quote escapedIndex++; //--- Increment break; //--- Exit case case '/': escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='/'; //--- Add slash escapedIndex++; //--- Increment break; //--- Exit case case 8: escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='b'; //--- Add backspace escapedIndex++; //--- Increment break; //--- Exit case case 12: escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='f'; //--- Add form feed escapedIndex++; //--- Increment break; //--- Exit case case '\n': escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='n'; //--- Add newline escapedIndex++; //--- Increment break; //--- Exit case case '\r': escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='r'; //--- Add carriage return escapedIndex++; //--- Increment break; //--- Exit case case '\t': escapedCharacters[escapedIndex]='\\'; //--- Add escape escapedIndex++; //--- Increment escapedCharacters[escapedIndex]='t'; //--- Add tab escapedIndex++; //--- Increment break; //--- Exit case default: escapedCharacters[escapedIndex]=inputCharacters[inputIndex]; //--- Copy normal char escapedIndex++; //--- Increment break; //--- Exit case } } stringValue=ShortArrayToString(escapedCharacters,0,escapedIndex); //--- Convert back to string return stringValue; //--- Return escaped string } //+------------------------------------------------------------------+ //| Unescapes special characters in string | //+------------------------------------------------------------------+ string JsonValue::UnescapeString(string stringValue){ ushort inputCharacters[], unescapedCharacters[]; //--- Declare arrays int inputLength=StringToShortArray(stringValue, inputCharacters); //--- Convert to short array if(ArrayResize(unescapedCharacters, inputLength)!=inputLength){return NULL;} //--- Resize, return NULL on fail int outputIndex=0,inputIndex=0; //--- Initialize indices while(inputIndex<inputLength){ //--- Loop through input ushort currentCharacter=inputCharacters[inputIndex]; //--- Get current char if(currentCharacter=='\\' && inputIndex<inputLength-1){ //--- Handle escape switch(inputCharacters[inputIndex+1]){ //--- Handle escaped type case '\\': currentCharacter='\\'; //--- Set to backslash inputIndex++; //--- Increment break; //--- Exit case case '"': currentCharacter='"'; //--- Set to quote inputIndex++; //--- Increment break; //--- Exit case case '/': currentCharacter='/'; //--- Set to slash inputIndex++; //--- Increment break; //--- Exit case case 'b': currentCharacter=8; //--- Set to backspace inputIndex++; //--- Increment break; //--- Exit case case 'f': currentCharacter=12; //--- Set to form feed inputIndex++; //--- Increment break; //--- Exit case case 'n': currentCharacter='\n'; //--- Set to newline inputIndex++; //--- Increment break; //--- Exit case case 'r': currentCharacter='\r'; //--- Set to carriage return inputIndex++; //--- Increment break; //--- Exit case case 't': currentCharacter='\t'; //--- Set to tab inputIndex++; //--- Increment break; //--- Exit case } } unescapedCharacters[outputIndex]=currentCharacter; //--- Copy to output outputIndex++; //--- Increment output inputIndex++; //--- Increment input } stringValue=ShortArrayToString(unescapedCharacters,0,outputIndex); //--- Convert back to string return stringValue; //--- Return unescaped string }
実装を締めくくるにあたり、文字列処理メソッドに焦点を当てます。まず、ExtractStringFromArrayメソッドを実装します。このメソッドは文字配列から文字列を解析し、閉じ引用符または配列の終端に達するまで反復処理します。エスケープシーケンス(\"、\\、\/、\b、\f、\n、\r、\t)およびUnicodeエスケープ(\uXXXX)を処理し、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に格納し、PrintでMetaTrader 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
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
初心者からエキスパートへ:MQL5を使ったアニメーションニュース見出し(XI) - ニュース取引における相関
MQL5入門(第21回):ハーモニックパターン検出の自動化
平均足を使ったプロフェッショナルな取引システムの構築(第2回):EAの開発
Parafrac V2オシレーター:パラボリックSARとATRの統合
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
新しい記事をご覧ください:MQL5でAIトレーディング・システムを構築する(パート1):AI APIのJSONハンドリングを実装する。
著者アラン・ムネネ・ムティリア
素晴らしい。何から始めたらいいのでしょうか?
ダウンロードファイルはa._JSON_Code_File.mq5とEAですか?
私はA_JSON_Code_File.mq5(より身近に見えた)の名前を変更し、その後コンパイルしました。 エラーはありませんでした。
しかし、私のプラットフォームではEXPERTとしてのリファレンスは見当たりませんでした。
つまり、ロードして実行することができなかったのです。ただ、"プレイ "して、何が起こり、なぜそうなるのかを理解する必要があります。
AIを活用する素晴らしい機会を共有してくれてありがとう。
素晴らしい。どこから始める?
ダウンロードファイルはa._JSON_Code_File.mq5とEAですか?
A_JSON_Code_File.mq5をリネームしてコンパイルしました。 エラーなし。
しかし、私のプラットフォームでは、EXPERTとしてのリファレンスは見当たりませんでした。
つまり、ロードして実行することができなかったのだ。ただ、"プレイ "して、何がなぜ起こるのかを理解する必要がある。
AIを活用する素晴らしい機会を共有してくれてありがとう。