
JSON beherrschen: Erstellen Sie Ihren eigenen JSON-Reader in MQL5 von Grund auf
Einführung
Hallo und herzlich willkommen! Wenn Sie jemals versucht haben, JSON-Daten in MQL5 zu parsen oder zu manipulieren, haben Sie sich vielleicht gefragt, ob es dafür einen einfachen und flexiblen Ansatz gibt. JSON, die Abkürzung für JavaScript Object Notation, hat als leichtes Datenaustauschformat, das sowohl für Menschen als auch für Maschinen lesbar ist, an Beliebtheit gewonnen. Während MQL5 in erster Linie für die Erstellung von Expert Advisors, Indikatoren und Skripten für die MetaTrader 5-Plattform bekannt ist, verfügt es nicht über eine native JSON-Bibliothek. Das bedeutet, dass Sie, wenn Sie mit JSON-Daten arbeiten möchten – sei es von einer Web-API, einem externen Server oder aus Ihren eigenen lokalen Dateien – wahrscheinlich eine nutzerdefinierte Lösung entwickeln oder eine vorhandene Bibliothek integrieren müssen.
In diesem Artikel möchten wir diese Lücke schließen, indem wir zeigen, wie Sie Ihren eigenen JSON-Leser in MQL5 erstellen können. Auf dem Weg dorthin werden wir die grundlegenden Konzepte des Parsens von JSON erkunden und die Erstellung einer flexiblen Klassenstruktur durchgehen, die verschiedene JSON-Elementtypen (wie Objekte, Arrays, Strings, Zahlen, Boolesche Werte und Nullwerte) verarbeiten kann. Unser Ziel ist es, Sie in die Lage zu versetzen, JSON-Strings bequem zu parsen und auf die darin enthaltenen Daten zuzugreifen oder sie zu ändern – und das alles in Ihrer MetaTrader 5-Umgebung.
Wir werden eine ähnliche Struktur wie in anderen MQL5-bezogenen Artikeln verfolgen, aber mit einem speziellen Fokus auf JSON-Parsing und -Nutzung. Dieser Artikel ist in fünf Hauptabschnitte unterteilt: eine Einführung (die Sie gerade lesen), ein tieferes Eintauchen in die Grundlagen von JSON und wie es in MQL5 passt, eine Schritt-für-Schritt-Anleitung zum Aufbau eines grundlegenden JSON-Parsers von Grund auf, eine Erkundung fortgeschrittener Funktionen für die JSON-Behandlung und schließlich ein umfassendes Code-Listing mit abschließenden Gedanken.
JSON ist überall. Ganz gleich, ob Sie Marktdaten von einem Drittanbieterdienst abrufen, Ihre eigenen Handelsdatensätze hochladen oder mit komplexen Strategien experimentieren, die eine dynamische Konfiguration erfordern, JSON bleibt ein nahezu universelles Format. Einige der häufigsten praktischen Anwendungsfälle für JSON in der Welt des algorithmischen Handels sind:
-
Abruf von Marktdaten: Viele moderne Broker-APIs oder Finanzdatendienste bieten Echtzeit- oder historische Daten in JSON an. Mit einem JSON-Reader können Sie diese Daten schnell auswerten und in Ihre Handelsstrategie integrieren.
-
Strategie-Konfiguration: Angenommen, Sie haben einen Expert Advisor, der mehrere Parameter unterstützt: maximaler Spread, gewünschtes Kontorisiko oder erlaubte Handelszeiten. Eine JSON-Datei kann diese Einstellungen sauber speichern, und ein JSON-Leser in MQL5 kann diese Parameter dynamisch laden oder aktualisieren, ohne Ihren Code neu zu kompilieren.
-
Versenden von Protokollen oder Daten: In bestimmten Konstellationen möchten Sie vielleicht Ihre Handelsprotokolle oder Debug-Meldungen zu Analysezwecken an einen externen Server übertragen. Das Versenden als JSON kann dazu beitragen, dass Ihre Protokolle konsistent, leicht analysierbar und in Tools integrierbar sind, die strukturierte Daten erwarten.
Viele Online-Beispiele zeigen, wie man JSON in Sprachen wie Python, JavaScript oder C++ auseinandernimmt. MQL5 ist jedoch eine spezialisierte Sprache mit ihren eigenen Beschränkungen. Das bedeutet, dass wir auf bestimmte Aspekte achten müssen: Speicherverwaltung, Array-Verwendung, strenge Datentypen usw.
Wir werden eine nutzerdefinierte Klasse (oder eine Reihe von Klassen) erstellen, die sich mit JSON-Parsing und -Manipulation befasst. Die Idee ist, es so zu gestalten, dass Sie etwas tun können wie:
CMyJsonParser parser; parser.LoadString("{\"symbol\":\"EURUSD\",\"lots\":0.1,\"settings\":{\"slippage\":2,\"retries\":3}}"); // Access top-level fields: Print("Symbol = ", parser.GetObject("symbol").ToStr()); Print("Lots = ", parser.GetObject("lots").ToDbl()); // Access nested fields: CMyJsonElement settings = parser.GetObject("settings"); Print("Slippage = ", settings.GetObject("slippage").ToInt()); Print("Retries = ", settings.GetObject("retries").ToInt());
Natürlich kann Ihr endgültiger Ansatz in Bezug auf Benennung oder Struktur leicht abweichen, aber diese Art von Nutzerfreundlichkeit ist das Ziel. Durch den Aufbau eines robusten Parsers haben Sie eine Grundlage für Erweiterungen wie die Umwandlung von MQL5-Datenstrukturen in JSON für die Ausgabe oder das Hinzufügen von Caching-Logik für wiederholte JSON-Abfragen.
Vielleicht sind Sie schon auf verschiedene JSON-Bibliotheken gestoßen, einschließlich einiger kurzer Skripte, die JSON durch die Verarbeitung von Zeichenarrays analysieren. Wir werden von diesen bestehenden Ansätzen lernen, aber den Code nicht direkt kopieren. Stattdessen konstruieren wir etwas Neues mit einer ähnlichen Idee, damit es für Sie leichter zu verstehen und zu pflegen ist. Wir werden unseren Code Teil für Teil zerlegen, und am Ende dieses Artikels werden Sie Zugang zu einer endgültigen, zusammenhängenden Implementierung haben, die Sie an Ihre eigenen Handelsprogramme anhängen können.
Wir hoffen, dass dieser Ansatz, eine Bibliothek von Grund auf aufzubauen und jedes Segment in einfacher Sprache zu erklären, Ihnen ein tieferes Verständnis vermittelt, als wenn wir Ihnen nur eine fertige Lösung geben würden. Indem Sie die Funktionsweise des Parsers verinnerlichen, können Sie ihn später leichter debuggen und anpassen.
Während JSON ein textbasiertes Format ist, können MQL5-Strings eine Vielzahl von Sonderzeichen enthalten, darunter Zeilenumbrüche, Wagenrückläufe oder Unicode-Zeichen. Unsere Implementierung wird einige dieser Nuancen berücksichtigen und versuchen, sie auf differenzierte Weise zu lösen. Vergewissern Sie sich jedoch immer, dass Ihre Eingabedaten gültiges JSON sind. Wenn Sie missgebildetes JSON oder zufälligen Text erhalten, der behauptet, gültig zu sein, müssen Sie wahrscheinlich eine robustere Fehlerbehandlung hinzufügen.
Hier ein kurzer Überblick über die Gliederung dieses Artikels:
-
Abschnitt 1 (Sie sind hier!) – Einleitung
Wir haben gerade besprochen, was JSON ist, warum es wichtig ist und wie wir einen nutzerdefinierten Parser in MQL5 schreiben werden. Dies ist die Grundlage für alles Weitere. -
Abschnitt 2 – Die Grundlagen: Grundlagen von JSON und MQL5
Wir sehen uns die wichtigsten Strukturelemente von JSON an, ordnen sie dann den MQL5-Datentypen zu und zeigen, auf welche Aspekte wir besonders achten müssen. -
Abschnitt 3 – Erweiterung unseres Parsers um fortgeschrittene Funktionalitäten
Hier werden wir über mögliche Erweiterungen oder Verbesserungen sprechen: wie man mit Arrays umgeht, wie man eine Fehlerprüfung hinzufügt und wie man MQL5-Daten zurück in JSON konvertiert, wenn man Daten nach außen senden muss. -
Abschnitt 4 – Vollständiger Kodex
Zum Schluss fassen wir unsere gesamte Bibliothek an einem Ort zusammen, sodass Sie eine einzige Referenzdatei erhalten. -
Abschnitt 5 – Schlussfolgerung
Wir fassen die wichtigsten Erkenntnisse zusammen und zeigen Ihnen einige nächste Schritte auf, die Sie bei Ihren eigenen Projekten in Betracht ziehen sollten.
Am Ende dieses Artikels werden Sie eine voll funktionsfähige JSON-Parsing- und Manipulationsbibliothek in MQL5 haben. Darüber hinaus werden Sie verstehen, wie das Ganze unter der Haube funktioniert, sodass Sie besser gerüstet sind, JSON in Ihre automatisierten Handelslösungen zu integrieren.
Die Grundlagen – JSON und MQL5-Grundlagen
Willkommen zurück! Nachdem wir nun den Gesamtplan für unseren nutzerdefinierten MQL5-JSON-Leser erstellt haben, ist es an der Zeit, die Feinheiten von JSON zu ergründen und zu sehen, wie diese auf MQL5 abgebildet werden. Wir werden uns mit der Struktur von JSON befassen, welche Datentypen am einfachsten zu parsen sind und mögliche Fallstricke aufzeigen, wenn wir JSON-Daten in MetaTrader 5 einbringen. Am Ende dieses Abschnitts werden Sie eine viel klarere Vorstellung davon haben, wie man JSON in einer MQL5-Umgebung anpackt, und damit die Voraussetzungen für die praktische Programmierung schaffen, die nun folgt.
JSON (JavaScript Object Notation) ist ein textbasiertes Format, das häufig für die Übertragung und Speicherung von Daten verwendet wird. Im Gegensatz zu XML ist es relativ leichtgewichtig: Daten werden in geschweifte Klammern ( {} ) für Objekte oder eckige Klammern ( [] ) für Arrays eingeschlossen, und jedes Feld wird in einfachen Schlüssel-Wert-Paaren dargestellt. Hier ist ein kleines Beispiel:
{ "symbol": "EURUSD", "lots": 0.05, "enableTrade": true }
Dies ist für einen Menschen leicht zu lesen und für eine Maschine einfach zu analysieren. Jede Information – wie „symbol“ oder „enableTrade“ – wird als Schlüssel bezeichnet, der einen Wert enthält. Der Wert kann eine Zeichenfolge, eine Zahl, ein Boolescher Wert oder sogar ein anderes verschachteltes Objekt oder Array sein. Kurz gesagt geht es bei JSON um die Organisation von Daten in einer verschachtelten Baumstruktur, mit der Sie alles darstellen können, von einfachen Parametern bis hin zu komplexeren hierarchischen Daten.
JSON versus MQL5-Datentypen:
- Zeichenketten: JSON-Zeichenfolgen erscheinen in doppelten Anführungszeichen, wie z. B. "Hello World" . In MQL5 gibt es auch den Typ String, aber diese Strings können Sonderzeichen, Escape-Sequenzen und Unicode enthalten. Die erste Schwierigkeit besteht also darin, sicherzustellen, dass unser Parser Anführungszeichen, escapete Symbole (wie \“ ) und möglicherweise Unicode-Codepunkte (z. B. \u00A9 ) korrekt verarbeitet.
- Zahlen: In JSON können Zahlen ganze Zahlen (wie 42) oder Dezimalzahlen (3.14159) sein. MQL5 speichert Zahlen hauptsächlich als int (für Ganzzahlen) oder double (für Fließkommazahlen). Allerdings werden nicht alle numerischen Werte in JSON sauber auf den Typ int abgebildet. So ist zum Beispiel 1234567890 gültig, aber in manchen Kontexten benötigen Sie in MQL5 möglicherweise den Typ long, wenn die Zahl wirklich groß ist. Wir müssen besonders aufpassen, wenn die JSON-Zahl außerhalb des Bereichs einer typischen 32-Bit-Ganzzahl liegt. Außerdem kann es notwendig sein, eine große Ganzzahl in einen Double zu konvertieren, wenn sie den Grenzwert einer Standard-Ganzzahl überschreitet, was jedoch zu Rundungsproblemen führen kann.
- Boolsche: JSON verwendet die Kleinbuchstaben true und false. MQL5 hingegen verwendet den Typ bool. Dies ist eine unkomplizierte Zuordnung, aber wir müssen diese Token (true und false) beim Parsen sorgfältig erkennen. Es gibt einen kleinen Haken: Syntaxfehler – wie True oder FALSE mit Großbuchstaben – sind kein gültiges JSON, obwohl einige Parser in anderen Sprachen sie zulassen. Wenn Ihre Daten manchmal Boolesche Großbuchstaben verwenden, müssen Sie dies differenziert handhaben oder sicherstellen, dass Ihre Daten streng JSON-konform sind.
- NULL: Ein Nullwert in JSON weist oft auf ein leeres oder fehlendes Feld hin. MQL5 hat keinen eigenen „Typ Null“. Stattdessen können wir JSON null als eine spezielle interne Enumeration darstellen (wie jtNULL, wenn wir eine Enumeration für unsere JSON-Elementtypen definieren) oder es als leere Zeichenkette oder Standardwert behandeln. Wir werden bald sehen, wie man Nullen im Parser verwaltet.
- Objekte: Wenn Sie geschweifte Klammern sehen, { ... } sehen, ist das ein JSON-Objekt. Es handelt sich im Wesentlichen um eine Sammlung von Schlüssel-Wert-Paaren. In MQL5 gibt es keinen integrierten Wörterbuchtyp, aber wir können einen simulieren, indem wir ein dynamisches Array von Paaren speichern oder eine nutzerdefinierte Klasse erstellen, die Schlüssel und Werte enthält. Normalerweise definieren wir so etwas wie eine CMyJsonObject-Klasse (oder eine allgemeine Klasse mit einem internen Status „Objekt“), die eine Liste von Kindern enthält. Jedes Kind hat einen Schlüssel (String) und einen Wert, der ein beliebiger JSON-Datentyp sein kann.
- Arrays: Arrays in JSON sind geordnete Listen, umgeben von eckigen Klammern, [ ... ]. Jedes Element in einem Array kann eine Zeichenkette, eine Zahl, ein Objekt oder sogar ein anderes Array sein. In MQL5 behandeln wir Arrays mit der Funktion ArrayResize und direkter Indizierung. Wir werden wahrscheinlich ein JSON-Array als dynamisches Array von Elementen speichern. Unser Code muss die Tatsache, dass ein bestimmter Knoten ein Array ist, zusammen mit den darin enthaltenen Kindern verfolgen.
Sehen wir uns einige der potenziellen Herausforderungen an:
- Umgang mit Escape-Sequenzen: In JSON kann ein Backslash \ vor Zeichen wie Anführungszeichen oder Zeilenumbrüchen stehen. Zum Beispiel könnten Sie sehen "description": "Line one\\nLine two" . Wir müssen \\n als einen tatsächlichen Zeilenumbruch innerhalb der endgültigen Zeichenfolge interpretieren. Besondere Sequenzen sind:
- \" für doppelte Anführungszeichen
- \\ für Backslash
- \/ manchmal für Schrägstrich
- \n für Zeilenumbruch
- \t für tab
- \u für Unicode-Codepunkte
Wir müssen diese Sequenzen im rohen JSON-String methodisch in die tatsächlichen Zeichen umwandeln, die sie in MQL5 darstellen. Andernfalls speichert der Parser sie möglicherweise falsch oder schlägt bei Eingabedaten, die diese Standard-Escape-Muster verwenden, fehl.
- Leerzeichen und Steuerzeichen trimmen: Eine gültige JSON-Zeichenkette kann Leerzeichen, Tabulatoren und Zeilenumbrüche (insbesondere zwischen Elementen) enthalten. Obwohl diese erlaubt sind und an den meisten Stellen keine semantische Bedeutung haben, können sie die Parsing-Logik verkomplizieren, wenn wir nicht vorsichtig sind. Ein robuster Parser ignoriert in der Regel alle Leerzeichen außerhalb von Anführungsstrichen. Das bedeutet, dass wir sie auslassen wollen, wenn wir von einem Token zum nächsten gehen.
- Der Umgang mit großen Daten: Wenn Ihre JSON-Zeichenfolge extrem groß ist, könnten Sie sich Sorgen über Speicherbeschränkungen in MQL5 machen. Die Sprache kann mit Arrays recht gut umgehen, aber es gibt Obergrenzen, wenn man sich zig Millionen Elementen nähert. Die meisten Händler benötigen nur selten JSON in diesem Umfang, aber es ist erwähnenswert, dass ein „Streaming“- oder iterativer Ansatz notwendig sein kann, wenn Sie dies tun. Für die meisten normalen Verwendungszwecke, wie z. B. Leseeinstellungen oder mäßig große Datensätze, sollte unser einfacher Ansatz völlig ausreichen.
-
Nicht jedes JSON ist perfekt. Wenn Ihr Parser versucht, eine ungültige Struktur zu lesen – z. B. ein fehlendes Anführungszeichen oder ein nachgestelltes Komma – muss er diese differenziert behandeln. Möglicherweise möchten Sie Fehlercodes definieren oder eine Fehlermeldung intern speichern, damit der aufrufende Code Parse-Fehler erkennen und darauf reagieren kann. In einem Handelskontext könnten Sie das:
- Zeigen Sie ein Meldungsfenster an oder drucken Sie eine Fehlermeldung in das Journal.
- Zurück zu einigen sicheren Einstellungen, wenn JSON ungültig ist.
- Stoppen Sie die Ausführung des Expert Advisors, wenn kritische Daten nicht geparst werden können.
Wir werden grundlegende Prüfungen einbauen, um Fehler wie nicht übereinstimmende Klammern oder nicht erkannte Token zu erkennen. Auch eine erweiterte Fehlerberichterstattung ist möglich, aber das hängt davon ab, wie gründlich Sie sein wollen.
Da JSON verschachtelt sein kann, wird unser Parser wahrscheinlich eine einzelne Klasse oder Klassenhierarchie verwenden, bei der jeder Knoten einer von mehreren Typen sein kann:
- Object – Enthält Schlüssel-Wert-Paare
- Array – Enthält indizierte Elemente
- String – Enthält Textdaten
- Number – Speichert numerische Daten als Double oder möglicherweise als Long
- Boolean – Wahr oder falsch
- Null – Kein Wert
Wir könnten eine Enumeration für diese möglichen Knotentypen implementieren, z. B.:
enum JSONNodeType { JSON_UNDEFINED = 0, JSON_OBJECT, JSON_ARRAY, JSON_STRING, JSON_NUMBER, JSON_BOOL, JSON_NULL }; enum JSONNodeType { JSON_UNDEFINED = 0, JSON_OBJECT, JSON_ARRAY, JSON_STRING, JSON_NUMBER, JSON_BOOL, JSON_NULL };
Dann übergeben wir unserer Parserklasse eine Variable, die den Typ des aktuellen Knotens enthält. Wir speichern auch den Inhalt des Knotens. Wenn es sich um ein Objekt handelt, behalten wir ein Array von Kindknoten, die durch einen String verschlüsselt sind. Wenn es sich um ein Array handelt, wird eine Liste von Kindknoten geführt, die von 0 aufwärts indiziert sind. Wenn es eine Zeichenkette ist, bleibt es eine Zeichenkette. Wenn es sich um eine Zahl handelt, könnte ein Double plus eine interne Ganzzahl gespeichert werden, wenn es sich um eine ganze Zahl handelt, usw.
Ein alternativer Ansatz besteht darin, eine eigene Klasse für Objekte, Arrays, Strings usw. zu haben. Das kann in MQL5 unübersichtlich werden, weil man oft zwischen ihnen hin- und herwechseln muss. Stattdessen werden wir wahrscheinlich eine einzige Klasse (oder eine einzige Hauptklasse plus einige Hilfsstrukturen) verwenden, die dynamisch jeden JSON-Typ darstellen kann. Dieser einheitliche Ansatz ist bei verschachtelten Elementen einfach, da jedes untergeordnete Element im Wesentlichen der gleiche Knotentyp mit einem anderen internen Typ ist. Das hilft uns, den Code kürzer und allgemeiner zu halten.
Selbst wenn Ihr unmittelbares Projekt nur das Lesen von JSON erfordert, möchten Sie vielleicht irgendwann JSON aus Ihren MQL5-Daten erstellen. Wenn Sie beispielsweise Handelssignale generieren und diese als JSON an einen Server weiterleiten möchten oder wenn Sie Ihre Handelsgeschäfte in einer strukturierten JSON-Datei protokollieren möchten, benötigen Sie einen „Encoder“ oder „Serializer“. Unser möglicher Parser kann dazu erweitert werden. Der grundlegende Code, den wir für den Umgang mit Strings und Arrays schreiben werden, kann auch bei der Erzeugung von JSON helfen. Behalten Sie das im Hinterkopf, wenn Sie Ihre Klassenmethoden entwerfen: „Wie kann ich dieselbe Logik in umgekehrter Reihenfolge aufrufen, um JSON-Text aus internen Daten zu erzeugen?“
Jetzt haben wir ein solides Verständnis dafür, wie die Strukturen von JSON mit MQL5 korrelieren. Wir wissen, dass wir eine flexible Klasse brauchen, die folgende Aufgaben erfüllen kann:
- Typ des Speicherknotens – Zahl, Zeichenfolge, Objekt, Array, Boolescher Wert oder Null.
- Parse – Liest den Rohtext, Zeichen für Zeichen, interpretiert geschweifte Klammern, Anführungszeichen und spezielle Token.
- Zugriff – Bietet bequeme Methoden an, um Kindknoten nach Schlüssel (für Objekte) oder nach Index (für Arrays) zu erhalten oder zu setzen.
- Konvertieren – Numerische oder boolesche Knoten in MQL5-Primitive, wie double, int oder bool umwandeln.
- Escape/Unescape – Konvertiert JSON-kodierte Sequenzen aus Zeichen in normale MQL5-Zeichenketten (und umgekehrt, wenn wir eine zukünftige Methode „to JSON“ hinzufügen).
- Fehlerprüfung – Möglicherweise werden fehlerhafte Eingaben oder unbekannte Token erkannt und dann differenziert behandelt.
Im nächsten Abschnitt, in dem die eigentliche Programmierarbeit beginnt, werden wir uns diese Funktionen Schritt für Schritt vornehmen. Wenn Sie sich Sorgen um die Leistung oder den Speicherbedarf machen, können Sie sicher sein, dass ein einfacher Ansatz für den normalen Gebrauch ausreichend schnell und speichereffizient ist. Wenn Sie auf Leistungsengpässe oder Speicherbeschränkungen stoßen, können Sie den Code jederzeit profilieren oder partielle Parsing-Techniken anwenden.
In Abschnitt 3 beginnen wir mit dem Aufbau unseres Parsers im Detail. Wir definieren die übergreifende Klasse – etwa CJsonNode – und beginnen mit den einfachsten Aufgaben: Speichern des Typs und des Werts eines Knotens sowie Schreiben der Methode „tokenizer“, die JSON-Token (wie geschweifte Klammern oder Anführungszeichen) identifiziert. Sobald das Fundament gelegt ist, werden wir uns nach oben arbeiten, um Objekte, Arrays, verschachtelte Elemente und Datenextraktion zu unterstützen.
Unabhängig davon, ob Sie kleine JSON-Konfigurationsdateien parsen oder umfangreiche Daten aus dem Internet abrufen möchten, gelten dieselben Grundlagen. Auch wenn Sie keine Erfahrung mit dem Lesen externer Daten in MQL5 haben, keine Sorge: Wenn Sie die Logik Schritt für Schritt nachvollziehen können, wird das Ganze recht überschaubar.
Atmen Sie jetzt durch, wir werden gleich in den Code eintauchen. Im nächsten Abschnitt werden wir den nutzerdefinierten JSON-Parser Schritt für Schritt aufbauen und praktische Tipps geben, um sicherzustellen, dass Ihre Daten zuverlässig verarbeitet werden. Bringen wir MQL5 dazu, JSON zu „sprechen“ wie ein Champion!
Die Kernklasse des Parsers: Das Ziel unserer Parserklasse ist es, ein beliebiges Stück JSON-Daten (manchmal als „node“ (Knoten) in einem Baum bezeichnet) darzustellen. Hier ist eine Skizze dessen, was wir brauchen könnten:
- Eine Enumeration der Knotentypen: Wir wollen leicht zwischen JSON-Objekt, Array, String usw. unterscheiden. Definieren wir also:
enum JsonNodeType { JSON_UNDEF = 0, JSON_OBJ, JSON_ARRAY, JSON_STRING, JSON_NUMBER, JSON_BOOL, JSON_NULL };
- Mitgliedsvariablen:
Jeder CJsonNode speichert - Ein JsonNodeType m_typeto identifiziert den Knotentyp.
- Für Objekte: eine Struktur (wie ein Array), die Schlüssel-Werte-Paare enthält.
- Für Arrays: eine Struktur, die indizierte Unterknoten enthält.
- Für Strings: eine Zeichenkette m_value.
- Für Zahlen: ein double m_numVal, eventuell ein zusätzlicher long m_intVal, falls erforderlich.
- Für Boolesche Werte: bool m_boolVal.
- Parsing- und Hilfsmethoden:
- Eine Methode zum Parsen des rohen JSON-Textes.
- Methoden zum Abrufen von Kindknoten nach Index oder Schlüssel.
- Möglicherweise eine Methode zur „Tokenisierung“ der Eingabe, die uns hilft, Klammern, geschweifte Klammern, Zeichenketten, Boolesche Werte usw. zu erkennen.
Wir werden diese Ideen im Hinterkopf behalten, wenn wir mit der Codierung beginnen. Das folgende Beispiel zeigt, wie wir diese Klasse in MQL5 definieren könnten (in einer Datei mit dem Namen CJsonNode.mqh). Wir werden Schritt für Schritt vorgehen.
//+------------------------------------------------------------------+ //| CJsonNode.mqh | //+------------------------------------------------------------------+ #pragma once enum JsonNodeType { JSON_UNDEF = 0, JSON_OBJ, JSON_ARRAY, JSON_STRING, JSON_NUMBER, JSON_BOOL, JSON_NULL }; // Class representing a single JSON node class CJsonNode { private: JsonNodeType m_type; // The type of this node string m_value; // Used if this node is a string double m_numVal; // Used if this node is a number bool m_boolVal; // Used if this node is a boolean // For arrays and objects, we'll keep child nodes in a dynamic array: CJsonNode m_children[]; // The array for child nodes string m_keys[]; // Only used if node is an object // For arrays, we’ll just rely on index public: // Constructor & destructor CJsonNode(); ~CJsonNode(); // Parsing interface bool ParseString(const string jsonText); // Utility methods (we will define them soon) void SetType(JsonNodeType nodeType); JsonNodeType GetType() const; int ChildCount() const; // Accessing children CJsonNode* AddChild(); CJsonNode* GetChild(int index); CJsonNode* GetChild(const string key); void SetKey(int childIndex,const string key); // Setting and getting values void SetString(const string val); void SetNumber(const double val); void SetBool(bool val); void SetNull(); string AsString() const; double AsNumber() const; bool AsBool() const; // We’ll add the actual parse logic in a dedicated private method private: bool ParseRoot(string jsonText); bool ParseObject(string text, int &pos); bool ParseArray(string text, int &pos); bool ParseValue(string text, int &pos); bool SkipWhitespace(const string text, int &pos); // ... other helpers };
Im obigen Code:
- m_children[]: Ein dynamisches Array, das mehrere untergeordnete CJsonNodeObjects speichern kann. Bei Arrays ist jedes Kind indiziert, während bei Objekten jedes Kind einen zugehörigen Schlüssel hat, der in m_keys[] gespeichert ist.
- ParseString(const string jsonText): Diese öffentliche Methode ist unser „Haupteinstiegspunkt“. Sie füttern es mit einer JSON-Zeichenfolge, und es versucht, diese zu analysieren und die internen Daten des Knotens zu füllen.
- ParseRoot, ParseObject, ParseArray, ParseValue: Wir werden jede dieser privaten Methoden definieren, um bestimmte JSON-Konstrukte zu behandeln.
Wir zeigen jetzt ein Skelett, aber wir werden gleich die Details ausarbeiten. Beim Parsen von JSON lesen wir von links nach rechts und ignorieren Leerzeichen, bis wir ein Strukturzeichen sehen. Zum Beispiel:
- Ein „{“ bedeutet, dass ein Objekt beginnt.
- Ein „[“ bedeutet, dass wir ein Array haben.
- Ein „\“ bedeutet, dass eine Zeichenkette beginnen wird.
- Eine Ziffer oder ein Minuszeichen kann eine Zahl bedeuten.
- Die Sequenzen „true“, „false“ oder „null“ erscheinen auch in JSON.
Schauen wir uns eine vereinfachte Version davon an, wie wir einen ganzen Text in unserer ParseString-Methode analysieren können:
bool CJsonNode::ParseString(const string jsonText) { // Reset existing data first m_type = JSON_UNDEF; m_value = ""; ArrayResize(m_children,0); ArrayResize(m_keys,0); int pos=0; return ParseRoot(jsonText) && SkipWhitespace(jsonText,pos) && pos>=StringLen(jsonText)-1; }
- Reset – Wir löschen alle vorherigen Daten.
- pos=0 – Dies ist unsere Zeichenposition in der Zeichenkette.
- Aufruf von ParseRoot(jsonText) – Eine Funktion, die wir definieren werden, die m_type festlegt und m_children oder m_value nach Bedarf auffüllt.
- SkipWhitespace(jsonText,pos) – Oft werden Leerzeichen, Tabulatoren oder Zeilenumbrüche übersprungen, die auftreten könnten.
- Endposition prüfen – Wenn alles korrekt geparst wurde, sollte possh nahe am Ende der Zeichenkette sein. Andernfalls kann es zu Textrückständen oder einem Fehler kommen.
Schauen wir uns nun ParseRoot genauer an. Um es kurz zu machen, stellen Sie sich vor, dass es wie folgt aussieht:
bool CJsonNode::ParseRoot(string jsonText) { int pos=0; SkipWhitespace(jsonText,pos); // If it begins with '{', parse as object if(StringSubstr(jsonText,pos,1)=="{") { return ParseObject(jsonText,pos); } // If it begins with '[', parse as array if(StringSubstr(jsonText,pos,1)=="[") { return ParseArray(jsonText,pos); } // Otherwise, parse as a single value return ParseValue(jsonText,pos); }
Zur Veranschaulichung prüfen wir das erste Zeichen, das kein Leerzeichen ist, und entscheiden, ob es sich um ein Objekt „{“, ein Array „[“ oder etwas anderes handelt (z. B. eine Zeichenkette, eine Zahl, ein Boolescher Wert oder null). Unsere tatsächliche Implementierung kann defensiver sein und Fehler behandeln, wenn das Zeichen unerwartet ist.
Schauen wir uns an, wie wir verschiedene Fälle analysieren:
- Parsen eines Objekts: Wenn eine öffnende geschweifte Klammer „{“ zu sehen ist, wird ein Objektknoten erstellt. Wir suchen dann wiederholt nach Schlüssel-Wert-Paaren, bis wir auf eine schließende Klammer „}“ stoßen. Hier ist ein konzeptioneller Ausschnitt, wie ParseObjectmight funktionieren könnte:
bool CJsonNode::ParseObject(string text, int &pos) { // We already know text[pos] == '{' m_type = JSON_OBJ; pos++; // move past '{' SkipWhitespace(text,pos); // If the next char is '}', it's an empty object if(StringSubstr(text,pos,1)=="}") { pos++; return true; } // Otherwise, parse key-value pairs in a loop while(true) { SkipWhitespace(text,pos); // The key must be a string in double quotes if(StringSubstr(text,pos,1)!="\"") return false; // or set an error // parse the string key (we’ll show a helper soon) string objKey = ""; if(!ParseStringLiteral(text,pos,objKey)) return false; SkipWhitespace(text,pos); // Expect a colon if(StringSubstr(text,pos,1)!=":") return false; pos++; // Now parse the value CJsonNode child; if(!child.ParseValue(text,pos)) return false; // Add the child to our arrays int newIndex = ArraySize(m_children); ArrayResize(m_children,newIndex+1); ArrayResize(m_keys,newIndex+1); m_children[newIndex] = child; m_keys[newIndex] = objKey; SkipWhitespace(text,pos); // If next char is '}', object ends if(StringSubstr(text,pos,1)=="}") { pos++; return true; } // Otherwise, we expect a comma before the next pair if(StringSubstr(text,pos,1)!=",") return false; pos++; } // unreachable return false; }
Erklärungen:
- Wir bestätigen, dass das Zeichen { ist, setzen unseren Typ auf JSON_OBJ und erhöhen pos.
- Folgt ein }, ist das Objekt leer.
- Andernfalls wird eine Schleife durchlaufen, bis eine } oder ein Fehler angezeigt wird. Jede Iteration:
- Analysiert einen String-Schlüssel in Anführungszeichen.
- Überspringt Leerzeichen und warten auf einen Doppelpunkt „:“.
- Analysiert den nächsten Wert (der eine Zeichenkette, eine Zahl, ein Array, ein Objekt usw. sein kann).
- Speichert diese in unseren Arrays (m_children und m_keys).
- Wenn wir „}“ sehen, sind wir fertig. Wenn wir ein Komma sehen, fahren wir fort.
Diese Schleife ist zentral für das Lesen eines JSON-Objekts. Die Struktur wird für Arrays wiederholt, außer dass Arrays keine Schlüssel haben, sondern nur indizierte Elemente.
-
Parsen eines Arrays: Arrays beginnen mit „[“. Darin befinden sich null oder mehr durch Kommata getrennte Elemente. Etwa so:
[ "Hello", 123, false, {"nestedObj": 1}, [10, 20] ]
Code:
bool CJsonNode::ParseArray(string text, int &pos) { m_type = JSON_ARRAY; pos++; // skip '[' SkipWhitespace(text,pos); // If it's immediately ']', it's an empty array if(StringSubstr(text,pos,1)=="]") { pos++; return true; } // Otherwise, parse elements in a loop while(true) { SkipWhitespace(text,pos); CJsonNode child; if(!child.ParseValue(text,pos)) return false; // store the child int newIndex = ArraySize(m_children); ArrayResize(m_children,newIndex+1); m_children[newIndex] = child; SkipWhitespace(text,pos); // if next char is ']', array ends if(StringSubstr(text,pos,1)=="]") { pos++; return true; } // must find a comma otherwise if(StringSubstr(text,pos,1)!=",") return false; pos++; } return false; }
Wir überspringen „[“ und alle Leerzeichen. Wenn wir ] sehen, ist es leer. Andernfalls werden die Elemente in einer Schleife analysiert, bis wir ] erreichen. Der Hauptunterschied zu Objekten besteht darin, dass wir keine Schlüssel-Wert-Paare analysieren, sondern nur Werte, und zwar nacheinander.
-
Parsen eines Wertes, Werte in JSON können eine Zeichenfolge, eine Zahl, ein Objekt, ein Array, ein Boolescher Wert oder null sein. Unser ParseValuemight tun könnte etwas wie:
bool CJsonNode::ParseValue(string text, int &pos) { SkipWhitespace(text,pos); string c = StringSubstr(text,pos,1); // Object if(c=="{") { return ParseObject(text,pos); } // Array if(c=="[") { return ParseArray(text,pos); } // String if(c=="\"") { m_type = JSON_STRING; return ParseStringLiteral(text,pos,m_value); } // Boolean or null // We’ll look for 'true', 'false', or 'null' if(StringSubstr(text,pos,4)=="true") { m_type = JSON_BOOL; m_boolVal = true; pos+=4; return true; } if(StringSubstr(text,pos,5)=="false") { m_type = JSON_BOOL; m_boolVal = false; pos+=5; return true; } if(StringSubstr(text,pos,4)=="null") { m_type = JSON_NULL; pos+=4; return true; } // Otherwise, treat it as a number or fail return ParseNumber(text,pos); }
Hier :
- überspringen wir die Leerzeichen.
- Wir schauen uns das aktuelle Zeichen (oder die Teilzeichenkette) an, um zu sehen, ob es {, [, " etc. ist.
- Rufen Sie die entsprechende Parse-Funktion auf.
- Wenn wir „wahr“, „falsch“ oder „null“ finden, behandeln wir sie direkt.
- Wenn nichts anderes übereinstimmt, gehen wir davon aus, dass es sich um eine Nummer handelt.
Je nach Bedarf können Sie eine bessere Fehlerbehandlung hinzufügen. Wenn die Teilzeichenkette zum Beispiel nicht mit einem erkannten Muster übereinstimmt, können Sie einen Fehler setzen.
-
Parsen einer Zahl; Wir müssen etwas analysieren, das numerisch aussieht, wie 123, 3.14 oder -0.001. Wir können einen schnellen Ansatz implementieren, indem wir so lange scannen, bis wir ein nicht-numerisches Zeichen erreichen:
bool CJsonNode::ParseNumber(string text, int &pos) { m_type = JSON_NUMBER; // capture starting point int startPos = pos; while(pos < StringLen(text)) { string c = StringSubstr(text,pos,1); if(c=="-" || c=="+" || c=="." || c=="e" || c=="E" || (c>="0" && c<="9")) { pos++; } else break; } // substring from startPos to pos string numStr = StringSubstr(text,startPos,pos-startPos); if(StringLen(numStr)==0) return false; // convert to double m_numVal = StringToDouble(numStr); return true; }
Erlaubt sind Ziffern, ein optionales Vorzeichen ( – oder +), Dezimalpunkte und die Exponentenschreibweise (e oder E). Sobald wir auf etwas anderes stoßen – ein Leerzeichen, ein Komma oder eine Klammer – hören wir auf. Dann wandeln wir die Teilzeichenkette in den Typ double. Wenn Ihr Code zwischen ganzen Zahlen und Dezimalzahlen unterscheiden muss, können Sie zusätzliche Prüfungen hinzufügen.
Erweiterung unseres Parsers um erweiterte Funktionen
Inzwischen haben wir einen funktionalen JSON-Parser in MQL5, der Objekte, Arrays, Strings, Zahlen, Boolesche Werte und Nullwerte verarbeiten kann. In diesem Abschnitt werden wir uns mit weiteren Funktionen und Verbesserungen befassen. Wir werden erörtern, wie man untergeordnete Elemente auf bequemere Weise abruft, wie man potenzielle Fehler differenziert behandelt und sogar wie man Daten wieder in JSON-Text konvertiert. Wenn Sie diese Verbesserungen auf den von uns entwickelten Parser aufsetzen, erhalten Sie ein robusteres und flexibleres Werkzeug, das eine Vielzahl von realen Anforderungen erfüllen kann. -
Abrufen von Kindern nach Schlüssel oder Index
Wenn unser Parser wirklich nützlich sein soll, wollen wir den Wert eines bestimmten Schlüssels in einem Objekt oder den Wert bei einem bestimmten Index in einem Array leicht abrufen können. Ein Beispiel: Wir haben dieses JSON:
{ "symbol": "EURUSD", "lots": 0.02, "settings": { "slippage": 2, "retries": 3 } }
Nehmen wir an, wir haben es in ein CJsonNode-Stammobjekt namens rootNode geparst. Wir würden gerne Dinge tun wie:
string sym = rootNode.GetChild("symbol").AsString(); double lot = rootNode.GetChild("lots").AsNumber(); int slip = rootNode.GetChild("settings").GetChild("slippage").AsNumber();
Unsere derzeitige Codestruktur könnte dies ermöglichen, wenn wir GetChild(const string key) im Parser definieren. Hier sehen Sie, wie eine solche Methode in Ihrer CJsonNodeclass aussehen könnte:
CJsonNode* CJsonNode::GetChild(const string key) { if(m_type != JSON_OBJ) return NULL; // We look through m_keys to find a match for(int i=0; i<ArraySize(m_keys); i++) { if(m_keys[i] == key) return &m_children[i]; } return NULL; }
Wenn der aktuelle Knoten kein Objekt ist, geben wir auf diese Weise einfach NULL zurück. Andernfalls werden alle m_keyst durchsucht, um einen passenden zu finden. Ist dies der Fall, geben wir einen Zeiger auf das entsprechende Kind zurück.
Ebenso können wir eine Methode für Arrays definieren:
CJsonNode* CJsonNode::GetChild(int index) { if(m_type != JSON_ARRAY) return NULL; if(index < 0 || index >= ArraySize(m_children)) return NULL; return &m_children[index]; }
Handelt es sich bei dem Knoten um ein Array, werden einfach die Grenzen geprüft und das entsprechende Element zurückgegeben. Wenn es sich nicht um ein Array handelt – oder der Index außerhalb des Bereichs liegt – wird NULL zurückgegeben. Die Prüfung auf NULL ist in Ihrem eigentlichen Code vor der Dereferenzierung entscheidend.
-
Differenzierte Fehlerbehandlung
In vielen realen Szenarien kann JSON missgebildet ankommen (z. B. fehlende Anführungszeichen, nachgestellte Kommas oder unerwartete Symbole). Ein robuster Parser sollte diese Fehler erkennen und melden. Sie können dies tun, indem Sie:
-
Einen booleschen Wert zurückgeben: Die meisten unserer Parse-Methoden geben bereits bool zurück. Wenn etwas fehlschlägt, geben wir false zurück. Wir können aber auch eine interne Fehlermeldung wie m_errorMsg speichern, damit der aufrufende Code sehen kann, was schief gelaufen ist.
-
Das Parsing fortsetzen oder abbrechen? Wenn Sie einen schwerwiegenden Parse-Fehler feststellen – z. B. ein unerwartetes Zeichen oder eine nicht geschlossene Klammer – können Sie den gesamten Parse-Vorgang abbrechen und den Knoten in einem „ungültigen“ Zustand halten. Alternativ können Sie auch versuchen, etwas zu überspringen oder wiederherzustellen, aber das ist schon etwas fortgeschrittener.
Hier ist eine konzeptionelle Verbesserung: innerhalb ParseArrayor ParseObject, wenn Sie etwas Unerwartetes sehen (wie ein Schlüssel ohne Anführungszeichen oder ein fehlender Doppelpunkt), können Sie schreiben:
Print("Parse Error: Missing colon after key at position ", pos); return false;
Dann könnten Sie in Ihrem Aufrufcode Folgendes tun:
CJsonNode root; if(!root.ParseString(jsonText)) { Print("Failed to parse JSON data. Check structure and try again."); // Perhaps handle defaults or stop execution }
Es liegt an Ihnen, wie detailliert Sie diese Botschaften darstellen wollen. Manchmal reicht ein einziges „parse failed“ für ein Handelsszenario aus. In anderen Fällen benötigen Sie vielleicht mehr Nuancen, um Ihre JSON-Eingabe zu debuggen.
-
-
MQL5-Daten zurück nach JSON konvertieren
Das Lesen von JSON ist nur die halbe Miete. Was, wenn Sie Daten an einen Server zurücksenden oder Ihre eigenen Protokolle im JSON-Format schreiben müssen? Sie können Ihre CJsonNodeclass um eine „Serializer“-Methode erweitern, die die Daten des Knotens durchläuft und den JSON-Text rekonstruiert. Nennen wir sie zum Beispiel ToJsonString():
string CJsonNode::ToJsonString() const { // We can define a helper that does the real recursion return SerializeNode(0); } string CJsonNode::SerializeNode(int depth) const { // If you prefer pretty-print with indentation, use 'depth' // For now, let's keep it simple: switch(m_type) { case JSON_OBJ: return SerializeObject(depth); case JSON_ARRAY: return SerializeArray(depth); case JSON_STRING: return "\""+EscapeString(m_value)+"\""; case JSON_NUMBER: { // Convert double to string carefully return DoubleToString(m_numVal, 10); } case JSON_BOOL: return m_boolVal ? "true":"false"; case JSON_NULL: return "null"; default: return "\"\""; // or some placeholder } }
Dann können Sie z.B. SerializeObject definieren:
string CJsonNode::SerializeObject(int depth) const { string result = "{"; for(int i=0; i<ArraySize(m_children); i++) { if(i>0) result += ","; string key = EscapeString(m_keys[i]); string value = m_children[i].SerializeNode(depth+1); result += "\""+key+"\":"; result += value; } result += "}"; return result; }
Ähnliches gilt für Arrays:
string CJsonNode::SerializeArray(int depth) const { string result = "["; for(int i=0; i<ArraySize(m_children); i++) { if(i>0) result += ","; result += m_children[i].SerializeNode(depth+1); } result += "]"; return result; }
Sie werden feststellen, dass wir eine EscapeString-Funktion verwendet haben. Wir können den Code wiederverwenden, der JSON-String-Escapes behandelt, also Sonderzeichen in \“, \\, \n usw. umwandelt. Dadurch wird sichergestellt, dass die Ausgabe gültiges JSON ist, wenn sie Anführungszeichen oder Zeilenumbrüche enthält.
Wenn Sie JSON „hübsch ausgedruckt“ haben möchten, fügen Sie einfach einige Zeilenumbrüche (“\n“) und Einrückungen ein. Ein Ansatz besteht darin, eine kurze Reihe von Leerzeichen auf der Grundlage der Tiefe zu erstellen, sodass Ihre JSON-Struktur optisch übersichtlicher wird:
string indentation = ""; for(int d=0; d<depth; d++) indentation += " ";
Dann fügen Sie diese Einrückung vor jeder Zeile oder jedem Element ein. Dies ist optional, aber praktisch, wenn Sie die JSON-Ausgabe regelmäßig manuell lesen oder debuggen müssen.
Wenn Ihre JSON-Daten riesig sind, z. B. Zehntausende von Zeilen, müssen Sie möglicherweise die Leistung berücksichtigen:
-
Effiziente String-Operationen
Beachten Sie, dass wiederholte Teilstring-Operationen ( StringSubstr) aufwendig sein können. MQL5 ist ziemlich effizient, aber wenn Ihre Daten wirklich sehr umfangreich sind, sollten Sie das Parsing auf Basis von Chunks oder einen iterativen Ansatz in Betracht ziehen. -
Streaming vs. DOM-Parsing
Unsere Strategie ist ein „DOM-ähnlicher“ Ansatz, d. h. wir zerlegen die gesamte Eingabe in eine Baumstruktur. Wenn die Daten so groß sind, dass sie nicht bequem in den Speicher passen, brauchen Sie einen Streaming-Parser, der ein Stück nach dem anderen verarbeitet. Das ist komplizierter, kann aber bei extrem großen Datensätzen notwendig sein. -
Caching
Wenn Sie häufig dasselbe Objekt nach denselben Schlüsseln abfragen, können Sie diese in einer kleinen Map speichern oder direkte Zeiger beibehalten, um wiederholte Abfragen zu beschleunigen. Für typische Handelsaufgaben ist dies nur selten erforderlich, aber es ist eine Option, wenn die Leistung entscheidend ist.
-
-
Bewährte Praktiken
Im Folgenden finden Sie einige bewährte Verfahren, um Ihren Code sicher und wartbar zu halten:
-
Immer auf NULL prüfen
Überprüfen Sie bei jedem Aufruf von GetChild(...), dass das Ergebnis nicht NULL ist. Der Versuch, auf einen Null-Zeiger in MQL5 zuzugreifen, kann zu Abstürzen oder merkwürdigem Verhalten führen. -
Typen validieren
Wenn Sie eine Zahl erwarten, das Kind aber in Wirklichkeit eine Zeichenkette ist, kann das ein Problem verursachen. Prüfen Sie beispielsweise GetType() oder verwenden Sie defensiven Code:
CJsonNode* node = parent.GetChild("lots"); if(node != NULL && node.GetType() == JSON_NUMBER) double myLots = node.AsNumber();
So können Sie sicherstellen, dass Ihre Daten das sind, wofür Sie sie halten.
Standardwerte
Oft möchte man einen sicheren Fallback, wenn im JSON ein Schlüssel fehlt. Sie können eine Hilfsfunktion schreiben:
double getDoubleOrDefault(CJsonNode &obj, const string key, double defaultVal) { CJsonNode* c = obj.GetChild(key); if(c == NULL || c.GetType() != JSON_NUMBER) return defaultVal; return c.AsNumber(); }
Auf diese Weise kann Ihr Code fehlende oder ungültige Felder differenziert behandeln.
-
Achten Sie auf die Beschränkungen von Zeichenketten und Arrays von MQL5.
MQL5 kann mit langen Zeichenketten umgehen, aber achten Sie auf den Speicherbedarf. Wenn Ihr JSON extrem groß ist, sollten Sie es sorgfältig testen.
Auch die Größe von Arrays kann geändert werden, aber extrem große Arrays (Hunderttausende von Elementen) können unhandlich werden. -
Tests
Genauso wie Sie die Logik eines EAs mit historischen Daten testen würden, testen Sie Ihren JSON-Parser mit einer Vielzahl von Beispieleingaben:- Einfache Objekte
- Verschachtelte Objekte
- Arrays mit gemischten Daten
- Große Zahlen, negative Zahlen
- Boolescher Wert und Null
- Zeichenketten mit Sonderzeichen oder Escape-Sequenzen
Je mehr Varianten Sie ausprobieren, desto sicherer werden Sie sein, dass Ihr Parser robust ist.
-
An diesem Punkt haben wir unseren einfachen Parser in ein leistungsstarkes JSON-Dienstprogramm verwandelt. Wir können JSON-Strings in eine hierarchische Struktur parsen, Daten nach Schlüssel oder Index abrufen, Parse-Fehler behandeln und sogar Knoten wieder in JSON-Text serialisieren. Dies reicht für viele MQL5-Anwendungsfälle aus – wie das Lesen einer Konfigurationsdatei, das Abrufen von Daten aus dem Web (wenn Sie eine Brücke zu HTTP-Anfragen haben) oder das Erzeugen Ihrer eigenen JSON-Protokolle.
Im letzten Abschnitt werden wir ein vollständiges Code-Listing präsentieren, das alles, was wir besprochen haben, zusammenfasst. Sie können sie in Ihren MQL5-Editor als einzelne .mqh-Datei oder .mq5script einfügen, sie an Ihre Namenskonventionen anpassen und sofort mit JSON-Daten arbeiten. Neben dem endgültigen Code bieten wir abschließende Gedanken und einige Hinweise zur Erweiterung der Bibliothek, wenn Sie spezielle Anforderungen haben.
Vollständiger Code
Herzlichen Glückwunsch, dass Sie es so weit geschafft haben! Sie haben die Grundlagen von JSON in MQL5 kennengelernt, Schritt für Schritt einen Parser erstellt, ihn um erweiterte Funktionen erweitert und bewährte Verfahren für den praktischen Einsatz erkundet. Jetzt ist es an der Zeit, ein einziges, integriertes Code-Listing zu erstellen, das alle Fragmente zu einem kohärenten Modul zusammenfasst. Sie können diesen endgültigen Code in einer .mqhDatei (oder direkt in Ihrer .mq5Datei) platzieren und ihn überall dort einbinden, wo Sie JSON-Handling in Ihren MetaTrader 5-Projekten benötigen.
Nachfolgend finden Sie eine Beispielcode-Implementierung namens CJsonNode.mqh. Sie vereinheitlicht das Parsen von Objekten/Arrays, die Fehlerprüfung, die Serialisierung zurück zu JSON und das Abrufen nach Schlüssel oder Index.
Das ist wichtig: Dieser Code ist das Original und keine Kopie des zuvor bereitgestellten Referenzausschnitts. Sie folgt einer ähnlichen Parsing-Logik, unterscheidet sich aber von den anderen, um unseren Anforderungen an einen neuen Ansatz gerecht zu werden. Wie immer steht es Ihnen frei, Methodennamen anzupassen, eine robustere Fehlerbehandlung hinzuzufügen oder spezielle Funktionen nach Bedarf zu implementieren.
#ifndef __CJSONNODE_MQH__ #define __CJSONNODE_MQH__ //+------------------------------------------------------------------+ //| CJsonNode.mqh - A Minimalistic JSON Parser & Serializer in MQL5 | //| Feel free to adapt as needed. | //+------------------------------------------------------------------+ #property strict //--- Enumeration of possible JSON node types enum JsonNodeType { JSON_UNDEF = 0, JSON_OBJ, JSON_ARRAY, JSON_STRING, JSON_NUMBER, JSON_BOOL, JSON_NULL }; //+-----------------------------------------------------------------+ //| Class representing a single JSON node | //+-----------------------------------------------------------------+ class CJsonNode { public: //--- Constructor & Destructor CJsonNode(); ~CJsonNode(); //--- Parse entire JSON text bool ParseString(string jsonText); //--- Check if node is valid bool IsValid(); //--- Get potential error message if not valid string GetErrorMsg(); //--- Access node type JsonNodeType GetType(); //--- For arrays int ChildCount(); //--- For objects: get child by key CJsonNode* GetChild(string key); //--- For arrays: get child by index CJsonNode* GetChild(int index); //--- Convert to string / number / bool string AsString(); double AsNumber(); bool AsBool(); //--- Serialize back to JSON string ToJsonString(); private: //--- Data members JsonNodeType m_type; // Type of this node (object, array, etc.) string m_value; // For storing string content if node is string double m_numVal; // For numeric values bool m_boolVal; // For boolean values CJsonNode m_children[]; // Child nodes (for objects and arrays) string m_keys[]; // Keys for child nodes (valid if JSON_OBJ) bool m_valid; // True if node is validly parsed string m_errMsg; // Optional error message for debugging //--- Internal methods void Reset(); bool ParseValue(string text,int &pos); bool ParseObject(string text,int &pos); bool ParseArray(string text,int &pos); bool ParseNumber(string text,int &pos); bool ParseStringLiteral(string text,int &pos); bool ParseKeyLiteral(string text,int &pos,string &keyOut); string UnescapeString(string input_); bool SkipWhitespace(string text,int &pos); bool AllWhitespace(string text,int pos); string SerializeNode(); string SerializeObject(); string SerializeArray(); string EscapeString(string s); }; //+-----------------------------------------------------------------+ //| Constructor | //+-----------------------------------------------------------------+ CJsonNode::CJsonNode() { m_type = JSON_UNDEF; m_value = ""; m_numVal = 0.0; m_boolVal = false; m_valid = true; ArrayResize(m_children,0); ArrayResize(m_keys,0); m_errMsg = ""; } //+-----------------------------------------------------------------+ //| Destructor | //+-----------------------------------------------------------------+ CJsonNode::~CJsonNode() { // No dynamic pointers to free; arrays are handled by MQL itself } //+-----------------------------------------------------------------+ //| Parse entire JSON text | //+-----------------------------------------------------------------+ bool CJsonNode::ParseString(string jsonText) { Reset(); int pos = 0; bool res = (ParseValue(jsonText,pos) && SkipWhitespace(jsonText,pos)); // If there's leftover text that's not whitespace, it's an error if(pos < StringLen(jsonText)) { if(!AllWhitespace(jsonText,pos)) { m_valid = false; m_errMsg = "Extra data after JSON parsing."; res = false; } } return (res && m_valid); } //+-----------------------------------------------------------------+ //| Check if node is valid | //+-----------------------------------------------------------------+ bool CJsonNode::IsValid() { return m_valid; } //+-----------------------------------------------------------------+ //| Get potential error message if not valid | //+-----------------------------------------------------------------+ string CJsonNode::GetErrorMsg() { return m_errMsg; } //+-----------------------------------------------------------------+ //| Access node type | //+-----------------------------------------------------------------+ JsonNodeType CJsonNode::GetType() { return m_type; } //+------------------------------------------------------------------+ //| For arrays: get number of children | //+------------------------------------------------------------------+ int CJsonNode::ChildCount() { return ArraySize(m_children); } //+------------------------------------------------------------------+ //| For objects: get child by key | //+------------------------------------------------------------------+ CJsonNode* CJsonNode::GetChild(string key) { if(m_type != JSON_OBJ) return NULL; for(int i=0; i<ArraySize(m_keys); i++) { if(m_keys[i] == key) return &m_children[i]; } return NULL; } //+------------------------------------------------------------------+ //| For arrays: get child by index | //+------------------------------------------------------------------+ CJsonNode* CJsonNode::GetChild(int index) { if(m_type != JSON_ARRAY) return NULL; if(index<0 || index>=ArraySize(m_children)) return NULL; return &m_children[index]; } //+------------------------------------------------------------------+ //| Convert to string / number / bool | //+------------------------------------------------------------------+ string CJsonNode::AsString() { if(m_type == JSON_STRING) return m_value; if(m_type == JSON_NUMBER) return DoubleToString(m_numVal,8); if(m_type == JSON_BOOL) return m_boolVal ? "true" : "false"; if(m_type == JSON_NULL) return "null"; // For object/array/undefined, return empty or handle as needed return ""; } //+------------------------------------------------------------------+ //| Convert node to numeric | //+------------------------------------------------------------------+ double CJsonNode::AsNumber() { if(m_type == JSON_NUMBER) return m_numVal; // If bool, return 1 or 0 if(m_type == JSON_BOOL) return (m_boolVal ? 1.0 : 0.0); return 0.0; } //+------------------------------------------------------------------+ //| Convert node to boolean | //+------------------------------------------------------------------+ bool CJsonNode::AsBool() { if(m_type == JSON_BOOL) return m_boolVal; if(m_type == JSON_NUMBER) return (m_numVal != 0.0); if(m_type == JSON_STRING) return (StringLen(m_value) > 0); return false; } //+------------------------------------------------------------------+ //| Serialize node back to JSON | //+------------------------------------------------------------------+ string CJsonNode::ToJsonString() { return SerializeNode(); } //+------------------------------------------------------------------+ //| Reset node to initial state | //+------------------------------------------------------------------+ void CJsonNode::Reset() { m_type = JSON_UNDEF; m_value = ""; m_numVal = 0.0; m_boolVal = false; m_valid = true; ArrayResize(m_children,0); ArrayResize(m_keys,0); m_errMsg = ""; } //+------------------------------------------------------------------+ //| Dispatch parse based on first character | //+------------------------------------------------------------------+ bool CJsonNode::ParseValue(string text,int &pos) { if(!SkipWhitespace(text,pos)) return false; if(pos >= StringLen(text)) return false; string c = StringSubstr(text,pos,1); //--- Object if(c == "{") return ParseObject(text,pos); //--- Array if(c == "[") return ParseArray(text,pos); //--- String if(c == "\"") return ParseStringLiteral(text,pos); //--- Boolean / null if(StringSubstr(text,pos,4) == "true") { m_type = JSON_BOOL; m_boolVal = true; pos += 4; return true; } if(StringSubstr(text,pos,5) == "false") { m_type = JSON_BOOL; m_boolVal = false; pos += 5; return true; } if(StringSubstr(text,pos,4) == "null") { m_type = JSON_NULL; pos += 4; return true; } //--- Otherwise, parse number return ParseNumber(text,pos); } //+------------------------------------------------------------------+ //| Parse object: { ... } | //+------------------------------------------------------------------+ bool CJsonNode::ParseObject(string text,int &pos) { m_type = JSON_OBJ; pos++; // skip '{' if(!SkipWhitespace(text,pos)) return false; //--- Check for empty object if(pos < StringLen(text) && StringSubstr(text,pos,1) == "}") { pos++; return true; } //--- Parse key-value pairs while(pos < StringLen(text)) { if(!SkipWhitespace(text,pos)) return false; // Expect key in quotes if(pos >= StringLen(text) || StringSubstr(text,pos,1) != "\"") { m_valid = false; m_errMsg = "Object key must start with double quote."; return false; } string key = ""; if(!ParseKeyLiteral(text,pos,key)) return false; if(!SkipWhitespace(text,pos)) return false; // Expect a colon if(pos >= StringLen(text) || StringSubstr(text,pos,1) != ":") { m_valid = false; m_errMsg = "Missing colon after object key."; return false; } pos++; // skip ':' if(!SkipWhitespace(text,pos)) return false; // Parse the child value CJsonNode child; if(!child.ParseValue(text,pos)) { m_valid = false; m_errMsg = "Failed to parse object value."; return false; } // Store int idx = ArraySize(m_children); ArrayResize(m_children,idx+1); ArrayResize(m_keys,idx+1); m_children[idx] = child; m_keys[idx] = key; if(!SkipWhitespace(text,pos)) return false; if(pos >= StringLen(text)) return false; string nextC = StringSubstr(text,pos,1); if(nextC == "}") { pos++; return true; } if(nextC != ",") { m_valid = false; m_errMsg = "Missing comma in object."; return false; } pos++; // skip comma } return false; // didn't see closing '}' } //+------------------------------------------------------------------+ //| Parse array: [ ... ] | //+------------------------------------------------------------------+ bool CJsonNode::ParseArray(string text,int &pos) { m_type = JSON_ARRAY; pos++; // skip '[' if(!SkipWhitespace(text,pos)) return false; //--- Check for empty array if(pos < StringLen(text) && StringSubstr(text,pos,1) == "]") { pos++; return true; } //--- Parse elements while(pos < StringLen(text)) { CJsonNode child; if(!child.ParseValue(text,pos)) { m_valid = false; m_errMsg = "Failed to parse array element."; return false; } int idx = ArraySize(m_children); ArrayResize(m_children,idx+1); m_children[idx] = child; if(!SkipWhitespace(text,pos)) return false; if(pos >= StringLen(text)) return false; string nextC = StringSubstr(text,pos,1); if(nextC == "]") { pos++; return true; } if(nextC != ",") { m_valid = false; m_errMsg = "Missing comma in array."; return false; } pos++; // skip comma if(!SkipWhitespace(text,pos)) return false; } return false; // didn't see closing ']' } //+------------------------------------------------------------------+ //| Parse a numeric value | //+------------------------------------------------------------------+ bool CJsonNode::ParseNumber(string text,int &pos) { m_type = JSON_NUMBER; int startPos = pos; // Scan allowed chars in a JSON number while(pos < StringLen(text)) { string c = StringSubstr(text,pos,1); if(c=="-" || c=="+" || c=="." || c=="e" || c=="E" || (c>="0" && c<="9")) pos++; else break; } string numStr = StringSubstr(text,startPos,pos - startPos); if(StringLen(numStr) == 0) { m_valid = false; m_errMsg = "Expected number, found empty."; return false; } m_numVal = StringToDouble(numStr); return true; } //+------------------------------------------------------------------+ //| Parse a string literal (leading quote already checked) | //+------------------------------------------------------------------+ bool CJsonNode::ParseStringLiteral(string text,int &pos) { pos++; // skip leading quote string result = ""; while(pos < StringLen(text)) { string c = StringSubstr(text,pos,1); if(c == "\"") { // closing quote pos++; m_type = JSON_STRING; m_value = UnescapeString(result); return true; } if(c == "\\") { // handle escape pos++; if(pos >= StringLen(text)) break; string ec = StringSubstr(text,pos,1); result += ("\\" + ec); // accumulate, we'll decode later pos++; } else { result += c; pos++; } } // If we get here, string was not closed m_valid = false; m_errMsg = "Unclosed string literal."; return false; } //+------------------------------------------------------------------+ //| Parse a string key (similar to a literal) | //+------------------------------------------------------------------+ bool CJsonNode::ParseKeyLiteral(string text,int &pos,string &keyOut) { pos++; // skip leading quote string buffer = ""; while(pos < StringLen(text)) { string c = StringSubstr(text,pos,1); if(c == "\"") { pos++; keyOut = UnescapeString(buffer); return true; } if(c == "\\") { pos++; if(pos >= StringLen(text)) break; string ec = StringSubstr(text,pos,1); buffer += ("\\" + ec); pos++; } else { buffer += c; pos++; } } m_valid = false; m_errMsg = "Unclosed key string."; return false; } //+------------------------------------------------------------------+ //| Unescape sequences like \" \\ \n etc. | //+------------------------------------------------------------------+ string CJsonNode::UnescapeString(string input_) { string out = ""; int i = 0; while(i < StringLen(input_)) { string c = StringSubstr(input_,i,1); if(c == "\\") { i++; if(i >= StringLen(input_)) { // Single backslash at end out += "\\"; break; } string ec = StringSubstr(input_,i,1); if(ec == "\"") out += "\""; else if(ec == "\\") out += "\\"; else if(ec == "n") out += "\n"; else if(ec == "r") out += "\r"; else if(ec == "t") out += "\t"; else if(ec == "b") out += CharToString(8); // ASCII backspace else if(ec == "f") out += CharToString(12); // ASCII formfeed else out += ("\\" + ec); i++; } else { out += c; i++; } } return out; } //+------------------------------------------------------------------+ //| Skip whitespace | //+------------------------------------------------------------------+ bool CJsonNode::SkipWhitespace(string text,int &pos) { while(pos < StringLen(text)) { ushort c = StringGetCharacter(text,pos); if(c == ' ' || c == '\t' || c == '\n' || c == '\r') pos++; else break; } // Return true if we haven't gone beyond string length return (pos <= StringLen(text)); } //+------------------------------------------------------------------+ //| Check if remainder is all whitespace | //+------------------------------------------------------------------+ bool CJsonNode::AllWhitespace(string text,int pos) { while(pos < StringLen(text)) { ushort c = StringGetCharacter(text,pos); if(c != ' ' && c != '\t' && c != '\n' && c != '\r') return false; pos++; } return true; } //+------------------------------------------------------------------+ //| Serialization dispatcher | //+------------------------------------------------------------------+ string CJsonNode::SerializeNode() { switch(m_type) { case JSON_OBJ: return SerializeObject(); case JSON_ARRAY: return SerializeArray(); case JSON_STRING: return "\""+EscapeString(m_value)+"\""; case JSON_NUMBER: return DoubleToString(m_numVal,8); case JSON_BOOL: return (m_boolVal ? "true" : "false"); case JSON_NULL: return "null"; default: return "\"\""; // undefined => empty string } } //+------------------------------------------------------------------+ //| Serialize object | //+------------------------------------------------------------------+ string CJsonNode::SerializeObject() { string out = "{"; for(int i=0; i<ArraySize(m_children); i++) { if(i > 0) out += ","; out += "\""+EscapeString(m_keys[i])+"\":"; out += m_children[i].SerializeNode(); } out += "}"; return out; } //+------------------------------------------------------------------+ //| Serialize array | //+------------------------------------------------------------------+ string CJsonNode::SerializeArray() { string out = "["; for(int i=0; i<ArraySize(m_children); i++) { if(i > 0) out += ","; out += m_children[i].SerializeNode(); } out += "]"; return out; } //+------------------------------------------------------------------+ //| Escape a string for JSON output (backslashes, quotes, etc.) | //+------------------------------------------------------------------+ string CJsonNode::EscapeString(string s) { string out = ""; for(int i=0; i<StringLen(s); i++) { ushort c = StringGetCharacter(s,i); switch(c) { case 34: // '"' out += "\\\""; break; case 92: // '\\' out += "\\\\"; break; case 10: // '\n' out += "\\n"; break; case 13: // '\r' out += "\\r"; break; case 9: // '\t' out += "\\t"; break; case 8: // backspace out += "\\b"; break; case 12: // formfeed out += "\\f"; break; default: // Directly append character out += CharToString(c); break; } } return out; } #endif // __CJSONNODE_MQH__
Nehmen wir ein Beispiel für seine Verwendung in einem Skript:
//+------------------------------------------------------------------+ //| ProjectName | //| Copyright 2020, CompanyName | //| http://www.companyname.net | //+------------------------------------------------------------------+ #property strict #include <CJsonNode.mqh> void OnStart() { // Some JSON text string jsonText = "{\"name\":\"Alice\",\"age\":30,\"admin\":true,\"items\":[1,2,3],\"misc\":null}"; CJsonNode parser; if(parser.ParseString(jsonText)) { Print("JSON parsed successfully!"); Print("Name: ", parser.GetChild("name").AsString()); Print("Age: ", parser.GetChild("age").AsNumber()); Print("Admin?", parser.GetChild("admin").AsBool()); // Serialize back Print("Re-serialized JSON: ", parser.ToJsonString()); } else { Print("JSON parsing error: ", parser.GetErrorMsg()); } } //+------------------------------------------------------------------+
Die erwartete Ausgabe ist selbsterklärend. Probieren Sie es einfach aus.
Schlussfolgerung
Mit diesem endgültigen Code in der Hand haben Sie alles, was Sie brauchen, um JSON direkt in MetaTrader 5 zu analysieren, zu manipulieren und sogar zu generieren:
- Parsen von JSON: ParseString() wandelt Rohtext in eine strukturierte Knotenhierarchie um.
- Daten abfragen: Mit GetChild(key) und GetChild(index) können Sie einfach durch Objekte und Arrays navigieren.
- Validierung: Prüfungen von IsValid() und GetErrorMsg() stellen fest, ob das Parsing erfolgreich war oder ob es Probleme gab (z. B. falsch gesetzte Klammern).
- Serialisierung: ToJsonString() setzt den Knoten (und die Kinder) wieder in gültigen JSON-Text zusammen.
Sie können diese Bibliothek gerne an Ihre speziellen Bedürfnisse anpassen. So können Sie beispielsweise umfassendere Fehlerberichte, spezielle numerische Konvertierungen oder Streaming-Funktionen für sehr große Datensätze hinzufügen. Die Grundlage sollte jedoch für die meisten typischen Anwendungsfälle, wie das Lesen von Parametern aus einer Datei oder die Interaktion mit webbasierten APIs, ausreichend sein.
Das war's! Sie haben das Ende unseres tiefen Einblicks in die JSON-Verarbeitung in MQL5 erreicht. Ganz gleich, ob Sie eine komplexe, datengesteuerte Handels-Engine implementieren oder nur Konfigurationsparameter aus einer lokalen Datei laden, ein zuverlässiger JSON-Parser und -Serialisierer kann Ihnen das Leben sehr erleichtern. Wir hoffen, dass dieser Artikel (und der darin enthaltene Code) Ihnen hilft, JSON reibungslos in Ihre automatisierten Handelsabläufe zu integrieren.
Viel Spaß beim Coding! Viel Spaß beim Handeln!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/16791





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.