Dominando JSON: Crea tu propio lector JSON desde cero en MQL5
Introducción
¡Hola y bienvenidos! Si alguna vez intentó analizar o manipular datos JSON en MQL5, es posible que se haya preguntado si existe un enfoque sencillo y flexible para hacerlo. JSON, que significa Notación de objetos JavaScript, ha crecido en popularidad como un formato liviano de intercambio de datos que es legible para humanos y amigable para máquinas. Si bien MQL5 es conocido principalmente por crear asesores expertos, indicadores y scripts para la plataforma MetaTrader 5, no tiene una biblioteca JSON nativa. Esto significa que si desea trabajar con datos JSON, ya sea desde una API web, un servidor externo o desde sus propios archivos locales, probablemente necesitará diseñar una solución personalizada o integrar una biblioteca existente.
En este artículo, pretendemos llenar ese vacío demostrando cómo crear su propio lector JSON en MQL5. A lo largo del camino, exploraremos los conceptos fundamentales del análisis de JSON y recorreremos la creación de una estructura de clase flexible capaz de manejar diferentes tipos de elementos JSON (como objetos, matrices, cadenas, números, valores booleanos y valores nulos). Nuestro objetivo final es permitirle analizar cómodamente cadenas JSON y acceder o modificar los datos dentro de ellas, todo desde la comodidad de su entorno MetaTrader 5.
Seguiremos una estructura similar a la que hemos visto en otros artículos relacionados con MQL5 pero con un enfoque específico en el análisis y uso de JSON. Este único artículo se dividirá en cinco secciones principales: una introducción (la que estás leyendo ahora), un análisis más profundo de los fundamentos de JSON y cómo encaja en MQL5, una guía paso a paso para construir un analizador JSON básico desde cero, una exploración de características avanzadas para el manejo de JSON y, finalmente, una lista de código completa más ideas finales.
JSON está en todas partes. Ya sea que obtenga datos de mercado de un servicio de terceros, cargue sus propios registros comerciales o experimente con estrategias complejas que requieren una configuración dinámica, JSON sigue siendo un formato casi universal. Algunos de los casos de uso prácticos más comunes de JSON en el mundo del comercio algorítmico incluyen:
-
Obtención de datos del mercado: muchas API de corredores modernos o servicios de datos financieros ofrecen datos históricos o en tiempo real en JSON. Tener un lector JSON a su disposición le permitirá analizar rápidamente esos datos e integrarlos en su estrategia comercial.
-
Configuración de la estrategia: supongamos que tiene un asesor experto que admite múltiples parámetros: spread máximo, nivel de riesgo de cuenta deseado o tiempos de negociación permitidos. Un archivo JSON puede almacenar estas configuraciones de forma ordenada y un lector JSON en MQL5 puede cargar o actualizar dinámicamente estos parámetros sin tener que volver a compilar el código.
-
Envío de registros o datos: en ciertas configuraciones, es posible que desee transmitir sus registros comerciales o mensajes de depuración a un servidor externo para realizar análisis. Enviarlos como JSON puede ayudar a mantener sus registros consistentes, fácilmente analizables e integrables con herramientas que esperan datos estructurados.
Muchos ejemplos en línea muestran cómo analizar JSON en lenguajes como Python, JavaScript o C++. Sin embargo, MQL5 es un lenguaje especializado con sus propias restricciones. Esto significa que debemos tener cuidado con ciertos aspectos: manejo de memoria, uso de matrices, tipos de datos estrictos, etc.
Crearemos una clase personalizada (o un conjunto de clases) dedicada al análisis y manipulación de JSON. La idea es diseñarlo para que puedas hacer algo como:
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());
Por supuesto, su enfoque final puede diferir ligeramente en cuanto a nombre o estructura, pero este tipo de usabilidad es el objetivo. Al construir un analizador sólido, tendrá una base para expansiones, como convertir estructuras de datos MQL5 en JSON para salida o agregar lógica de almacenamiento en caché para consultas JSON repetidas.
Es posible que haya encontrado diferentes bibliotecas JSON, incluidos algunos scripts cortos que analizan JSON manejando matrices de caracteres. Aprenderemos de estos enfoques existentes, pero no copiaremos el código directamente. En lugar de eso, construiremos algo nuevo con una idea similar para que sea más fácil de entender y mantener para usted. Analizaremos nuestro código fragmento por fragmento y, al final de este artículo, tendrás acceso a una implementación final y cohesiva que podrás adjuntar a tus propios programas comerciales.
Nuestra esperanza es que este enfoque de construir una biblioteca desde cero, explicando cada segmento en un lenguaje sencillo, le brinde una comprensión más profunda que si simplemente le hubiéramos dado una solución terminada. Al internalizar cómo funciona el analizador, podrá depurarlo y personalizarlo más fácilmente más adelante.
Si bien JSON es un formato basado en texto, las cadenas MQL5 pueden contener una variedad de caracteres especiales, incluidas nuevas líneas, retornos de carro o caracteres Unicode. Nuestra implementación considerará algunos de estos matices y tratará de abordarlos con elegancia. Aún así, asegúrese siempre de que sus datos de entrada sean JSON válidos. Si recibe un JSON mal formado o se enfrenta a un texto aleatorio que dice ser válido, probablemente necesitará agregar un manejo de errores más sólido.
A continuación se muestra una vista previa rápida de cómo está organizado este artículo:
-
Sección 1 (¡Estás aquí!) – Introducción
Acabamos de discutir qué es JSON, por qué es importante y cómo abordaremos la escritura de un analizador personalizado en MQL5. Esto prepara el escenario para todo lo demás. -
Sección 2: Conceptos básicos: Fundamentos de JSON y MQL5
Revisaremos los elementos estructurales clave de JSON, luego los asignaremos a tipos de datos MQL5 y mostraremos qué aspectos necesitan nuestra atención cuidadosa. -
Sección 3: Ampliación de nuestro analizador con funciones avanzadas
Aquí hablaremos sobre posibles expansiones o mejoras: cómo manejar matrices, cómo agregar verificación de errores y cómo convertir datos MQL5 nuevamente a JSON si necesita enviar datos. -
Sección 4 – Código completo
Finalmente, reuniremos toda nuestra biblioteca en un solo lugar, brindándole un único archivo de referencia. -
Sección 5 – Conclusión
Resumiremos las lecciones clave aprendidas y señalaremos algunos pasos siguientes que podría considerar en sus propios proyectos.
Al final del artículo, tendrá una biblioteca de manipulación y análisis de JSON completamente funcional en MQL5. Más allá de eso, comprenderá cómo funciona todo bajo el capó, lo que lo preparará mejor para integrar JSON en sus soluciones comerciales automatizadas.
Conceptos básicos: Fundamentos de JSON y MQL5
¡Bienvenido de nuevo! Ahora que hemos establecido el plan general para nuestro lector JSON MQL5 personalizado, es momento de profundizar en los puntos más finos de JSON y ver cómo se asignan a MQL5. Exploraremos la estructura de JSON, cubriremos qué tipos de datos son más fáciles de analizar e identificaremos posibles dificultades a medida que incorporamos datos JSON a MetaTrader 5. Al final de esta sección, tendrás una idea mucho más clara de cómo abordar JSON en un entorno MQL5, preparando el escenario para la codificación práctica que vendrá después.
JSON (JavaScript Object Notation) es un formato basado en texto comúnmente utilizado para la transmisión y almacenamiento de datos. A diferencia de XML, es relativamente liviano: los datos se encierran entre llaves ( {} ) para objetos o corchetes ( [] ) para matrices, y cada campo se presenta en pares clave-valor simples. He aquí un pequeño ejemplo:
{
"symbol": "EURUSD",
"lots": 0.05,
"enableTrade": true
}
Esto es fácil de leer para un humano y sencillo de analizar para una máquina. Cada pieza de información, como "symbol" o "enableTrade", se conoce como una clave que contiene algún valor. El valor puede ser una cadena, un número, un valor booleano o incluso otro objeto o matriz anidados. En resumen, JSON consiste en organizar datos en una estructura de árbol anidada, lo que permite representar todo, desde parámetros básicos hasta datos jerárquicos más complejos.
Tipos de datos JSON versus MQL5:
- Cadenas: Las cadenas JSON aparecen entre comillas dobles, como «Hola mundo». En MQL5, también tenemos el tipo de cadena, pero estas cadenas pueden incluir caracteres especiales, secuencias de escape y Unicode. Por lo tanto, el primer matiz al que nos enfrentaremos es asegurarnos de que nuestro analizador sintáctico maneje correctamente las comillas, los símbolos escapados (como \") y, posiblemente, los puntos de código Unicode (por ejemplo, \u00A9).
- Números: En JSON, los números pueden ser enteros (como 42) o decimales (3,14159). MQL5 almacena los números principalmente como int (para números enteros) o double (para valores de coma flotante). Sin embargo, no todos los valores numéricos en JSON se asignarán correctamente a un entero. Por ejemplo, 1234567890 es válido, pero en algunos contextos, es posible que necesite un long en MQL5 si el número es realmente grande. Deberemos prestar especial atención cuando el número JSON supere el rango de un entero típico de 32 bits. Además, es posible que tengas que convertir un número entero grande en un doble si supera el límite de un número entero estándar, pero eso conlleva posibles problemas de redondeo.
- Booleanos: JSON utiliza las minúsculas true y false. Por su parte, MQL5 utiliza booleanos (bool). Este es un mapeo sencillo, pero tendremos que detectar cuidadosamente estos tokens (true y false) durante el análisis. Hay un pequeño problema: cualquier error de sintaxis (como True o FALSE en mayúsculas) no es un JSON válido, aunque algunos analizadores en otros lenguajes los permiten. Si sus datos a veces utilizan valores booleanos en mayúsculas, deberá gestionarlo con cuidado o asegurarse de que sus datos cumplan estrictamente con JSON.
- Nulos: Un valor nulo en JSON suele indicar un campo vacío o que falta. MQL5 no tiene un «tipo nulo» específico. En su lugar, podemos optar por representar el valor nulo JSON como una enumeración interna especial (como jtNULL si definimos una enumeración para nuestros tipos de elementos JSON) o tratarlo como una cadena vacía o un valor predeterminado. Pronto veremos cómo gestionar los valores nulos en el analizador sintáctico.
- Objetos: Cuando veas llaves, { ... }, se trata de un objeto JSON. Es esencialmente una colección de pares clave-valor. En MQL5, no hay un tipo de diccionario incorporado, pero podemos simular uno almacenando una matriz dinámica de pares o creando una clase personalizada para contener claves y valores. Normalmente definiremos algo como una clase CMyJsonObject (o una clase de propósito general con un estado interno «object») que alberga una lista de hijos. Cada hijo tiene una clave (string) y un valor que puede ser cualquier tipo de datos JSON.
- Matrices: Las matrices en JSON son listas ordenadas entre corchetes, [ ... ] . Cada elemento de la matriz puede ser una cadena, un número, un objeto o incluso otra matriz. En MQL5, manejamos matrices con la función ArrayResize e indexación directa. Probablemente almacenaremos una matriz JSON como una matriz dinámica de elementos. Nuestro código deberá realizar un seguimiento del hecho de que un nodo particular es una matriz, junto con los hijos dentro de él.
Veamos algunos de los desafíos potenciales:
- Manejo de secuencias de escape: en JSON, una barra invertida \ puede preceder a caracteres como comillas o nuevas líneas. Por ejemplo, podría ver "description": "Line one\\nLine two". Necesitamos interpretar \\n como una nueva línea real dentro de la cadena final. Las secuencias especiales incluyen:
- \" para comillas dobles
- \\ para barra invertida
- \/ a veces para barra diagonal
- \n para nueva línea
- \t para pestaña
- \u para puntos de código Unicode
Tendremos que convertir metódicamente estas secuencias en la cadena JSON sin procesar en los caracteres reales que representan en MQL5. De lo contrario, el analizador podría almacenarlos incorrectamente o fallar con datos de entrada que utilicen estos patrones de escape estándar.
- Recorte de espacios en blanco y caracteres de control: una cadena JSON válida puede incluir espacios, tabulaciones y nuevas líneas (especialmente entre elementos). Aunque estos están permitidos y no tienen significado semántico en la mayoría de los lugares, pueden complicar la lógica del análisis si no tenemos cuidado. Un analizador robusto generalmente ignora cualquier espacio en blanco fuera de las cadenas entre comillas. Esto significa que querremos omitirlos a medida que pasamos de un token al siguiente.
- Cómo trabajar con datos de gran tamaño: si su cadena JSON es extremadamente grande, es posible que le preocupen las limitaciones de memoria en MQL5. El lenguaje puede manejar matrices bastante bien, pero hay límites superiores si se aproxima a decenas de millones de elementos. La mayoría de los traders rara vez necesitan un JSON tan grande, pero vale la pena señalar que podría ser necesario un enfoque de "transmisión" o iterativo si lo necesita. Para la mayoría de los usos normales (como configuraciones de lectura o conjuntos de datos de tamaño moderado), nuestro enfoque sencillo debería ser suficiente.
-
No todos los JSON son perfectos. Si su analizador intenta leer una estructura no válida (por ejemplo, una comilla faltante o una coma final), debe manejarlo con elegancia. Es posible que desee definir códigos de error o almacenar un mensaje de error internamente para que el código de llamada pueda detectar y responder a los errores de análisis. En un contexto comercial, usted podría:
- Mostrar un cuadro de mensaje o imprimir un error en el diario.
- Regrese a algunas configuraciones seguras si JSON no es válido.
- Detenga la ejecución del Asesor Experto si no se pueden analizar datos críticos.
Incorporaremos comprobaciones básicas para detectar errores como corchetes no coincidentes o tokens no reconocidos. También es posible realizar informes de errores más avanzados, pero eso depende de cuán riguroso desee ser.
Dado que JSON se puede anidar, nuestro analizador probablemente utilizará una sola clase o jerarquía de clases donde cada nodo puede ser de varios tipos:
- Objeto: contiene pares clave-valor
- Matriz: contiene elementos indexados
- Cadena: contiene datos textuales
- Número: almacena datos numéricos con un tamaño doble (double) o posiblemente largo (long)
- Booleano – true o false
- Null – Sin valor
Podríamos implementar una enumeración para estos posibles tipos de nodos, como:
enum JSONNodeType { JSON_UNDEFINED = 0, JSON_OBJECT, JSON_ARRAY, JSON_STRING, JSON_NUMBER, JSON_BOOL, JSON_NULL };
Luego le damos a nuestra clase analizador una variable que contiene qué tipo es el nodo actual. También almacenamos el contenido del nodo. Si es un objeto, mantenemos una matriz de nodos secundarios codificados por cadena. Si es una matriz, mantenemos una lista de nodos secundarios indexados desde 0 en adelante. Si es una cadena, mantenemos una cadena. Si es un número, podríamos almacenar un doble más un entero interno si es entero, etc.
Un enfoque alternativo es tener una clase separada para objetos, matrices, cadenas, etc. Esto puede resultar complicado en MQL5 porque tendrías que realizar conversiones entre ellos con frecuencia. En lugar de ello, probablemente adoptaremos una sola clase (o una sola clase principal más algunas estructuras auxiliares) que pueda representar dinámicamente cualquier tipo JSON. Este enfoque unificado es sencillo cuando se trabaja con elementos anidados, ya que cada hijo es esencialmente el mismo tipo de nodo con un tipo interno diferente. Eso nos ayuda a mantener el código más corto y más generalizado.
Incluso si su proyecto inmediato solo requiere leer JSON, es posible que eventualmente desee crear JSON a partir de sus datos MQL5. Por ejemplo, si genera señales de trading y desea enviarlas a un servidor como JSON, o si desea registrar sus operaciones en un archivo JSON estructurado, necesitará un codificador o serializador. Nuestro analizador puede extenderse para lograr esto. El código básico que escribiremos para manejar cadenas y matrices también puede ayudar a generar JSON. Simplemente ten esto en cuenta mientras diseñas los métodos de tu clase: "¿Cómo puedo llamar a la misma lógica a la inversa para producir texto JSON a partir de datos internos?"
Ahora tenemos una comprensión sólida de cómo las estructuras de JSON se correlacionan con MQL5. Sabemos que necesitamos una clase flexible que pueda hacer lo siguiente:
- Tipo de nodo de almacenamiento: ya sea un número, una cadena, un objeto, una matriz, un valor booleano o nulo.
- Analizar: leer el texto sin formato carácter por carácter, interpretar llaves, corchetes, comillas y tokens especiales.
- Acceso: proporciona métodos convenientes para obtener o establecer nodos secundarios por clave (para objetos) o por índice (para matrices).
- Convertir: convierte nodos numéricos o booleanos en primitivas MQL5, como double, int o bool.
- Escape/Unescape: convierte secuencias codificadas en JSON en cadenas en cadenas MQL5 normales (y viceversa si añadimos un método «a JSON» en el futuro).
- Comprobación de errores: detectar posibles entradas malformadas o tokens desconocidos y gestionarlos adecuadamente.
Abordaremos estas características paso a paso en la siguiente sección, donde comienza el verdadero viaje por la programación. Si le preocupa el rendimiento o el uso de la memoria, tenga la seguridad de que un enfoque sencillo suele ser lo suficientemente rápido y eficiente en cuanto a memoria para un uso normal. Si le preocupa el rendimiento o el uso de la memoria, tenga la seguridad de que un enfoque sencillo suele ser lo suficientemente rápido y eficiente en cuanto a memoria para un uso normal.
En la sección 3, comenzaremos a crear nuestro analizador sintáctico en detalle. Definiremos la clase general, algo así como CJsonNode, y comenzaremos con las tareas más sencillas: almacenar el tipo y el valor de un nodo, además de escribir un método «tokenizador» que identifique los tokens JSON (como llaves o comillas). Una vez sentadas las bases, trabajaremos hacia arriba para dar soporte a objetos, matrices, elementos anidados y extracción de datos.
Ya sea que planee analizar pequeños archivos de configuración JSON o recuperar datos extensos de la web, se aplican estos mismos fundamentos. Incluso si eres nuevo en la lectura de datos externos en MQL5, no temas: una vez que veas la lógica paso a paso, todo se vuelve bastante manejable.
Tómate un respiro ahora; estamos a punto de sumergirnos en el código. En la siguiente sección, trabajaremos en la creación del analizador JSON personalizado paso a paso, junto con consejos prácticos para garantizar que sus datos se procesen de manera confiable. ¡Hagamos que MQL5 “hable” JSON como un campeón!
Clase principal del analizador: El objetivo de nuestra clase de analizador es representar cualquier fragmento de datos JSON (a veces denominado «node» en un árbol). Aquí hay un boceto de lo que podríamos necesitar:
- Una enumeración para los tipos de nodos: Queremos distinguir fácilmente entre objetos JSON, matrices, cadenas, etc. Definamos algo como:
enum JsonNodeType { JSON_UNDEF = 0, JSON_OBJ, JSON_ARRAY, JSON_STRING, JSON_NUMBER, JSON_BOOL, JSON_NULL };
- Variables miembro:
Cada CJsonNode almacena - Un JsonNodeType m_typeto identifica el tipo de nodo.
- Para objetos: una estructura (como una matriz) que contiene pares clave-valor.
- Para matrices: una estructura que contiene nodos secundarios indexados.
- Para cadenas: una cadena m_value.
- Para números: un m_numVal doble, posiblemente un m_intVal largo adicional si es necesario.
- Para booleanos: un bool m_boolVal.
- Métodos de análisis y utilidad:
- Un método para analizar el texto JSON sin procesar.
- Métodos para recuperar nodos secundarios por índice o clave.
- Posiblemente un método para «tokenizar» la entrada, lo que nos ayudaría a identificar corchetes, llaves, cadenas, booleanos, etc.
Tendremos en cuenta estas ideas cuando comencemos a programar. A continuación se muestra un fragmento ilustrativo que muestra cómo podríamos definir esta clase en MQL5 (en un archivo con un nombre similar a CJsonNode.mqh). Iremos paso a paso.
//+------------------------------------------------------------------+ //| 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 };
En el código anterior:
- m_children[]: Una matriz dinámica que puede almacenar múltiples objetos CJsonNode secundarios. En el caso de las matrices, cada elemento secundario está indexado, mientras que en el caso de los objetos, cada elemento secundario tiene una clave asociada almacenada en m_keys[].
- ParseString(const string jsonText): Este método público es nuestro «punto de entrada principal». Se le proporciona una cadena JSON y este intenta analizarla, rellenando los datos internos del nodo.
- ParseRoot, ParseObject, ParseArray, ParseValue: Definiremos cada uno de estos métodos privados para gestionar construcciones JSON concretas.
Ahora estamos mostrando un esqueleto, pero en un momento añadiremos los detalles. Al analizar JSON, leemos de izquierda a derecha, ignorando los espacios en blanco hasta que vemos un carácter estructural. Por ejemplo:
- Un '{' significa que tenemos un objeto inicial.
- Un '[' significa que tenemos una matriz.
- Un '\"' significa que una cadena está a punto de comenzar.
- Un dígito o un signo menos podría significar un número.
- Las secuencias «true», «false» o «null» también aparecen en JSON.
Veamos una versión simplificada de cómo podríamos analizar un texto completo en nuestro método ParseString :
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; }
- Restablecer: borramos todos los datos anteriores.
- pos=0 – Esta es la posición de nuestro carácter en la cadena.
- Call ParseRoot(jsonText) – Una función que definiremos y que establece m_type y rellena m_children o m_value según sea necesario.
- SkipWhitespace(jsonText,pos): a menudo omitimos los espacios, tabulaciones o saltos de línea que puedan aparecer.
- Comprueba la posición final: si todo se ha analizado correctamente, pos debería estar cerca del final de la cadena. De lo contrario, podría aparecer texto adicional o un error.
Ahora, veamos más de cerca ParseRoot. Para abreviar, imagina que se ve así:
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); }
A modo de demostración, comprobamos el primer carácter que no sea un espacio en blanco y decidimos si se trata de un objeto ( {), una matriz ( [), o algo más (que podría ser una cadena, un número, un booleano o un valor nulo). Nuestra implementación real puede ser más defensiva, gestionando los errores si el carácter es inesperado.
Veamos cómo analizamos los diferentes casos:
- Analizar un objeto: Cuando vemos un corchete de apertura ( {), creamos un nodo de objeto. A continuación, buscamos repetidamente pares clave-valor hasta que encontramos un corchete de cierre ( }). Aquí hay un fragmento conceptual de cómo podría funcionar ParseObject:
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; }
Explicaciones:
- Confirmamos que el carácter es {, establecemos nuestro tipo en JSON_OBJ e incrementamos pos.
- Si } sigue, el objeto está vacío.
- De lo contrario, repetimos el bucle hasta que veamos un } o un error. Cada iteración:
- Analizar una clave de cadena entre comillas.
- Omita los espacios, espere dos puntos ( :).
- Analiza el siguiente valor (que puede ser una cadena, un número, una matriz, un objeto, etc.).
- Almacénalo en nuestras matrices ( m_children y m_keys).
- Si vemos }, hemos terminado. Si vemos una coma, continuamos.
Este bucle es fundamental para leer un objeto JSON. La estructura se repite para las matrices, excepto que las matrices no tienen claves, solo elementos indexados.
-
Analizar una matriz: Las matrices comienzan con [. En el interior, encontraremos cero o más elementos separados por comas. Algo así como:
[ "Hello", 123, false, {"nestedObj": 1}, [10, 20] ]
Código:
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; }
Omitimos [ y cualquier espacio en blanco. Si vemos ], está vacío. De lo contrario, analizamos los elementos en un bucle hasta llegar a ]. La diferencia clave con respecto a los objetos es que no analizamos pares clave-valor, sino solo valores, en secuencia.
-
Analizar un valor, Los valores en JSON pueden ser una cadena, un número, un objeto, una matriz, un booleano o nulo. Nuestro ParseValue podría hacer algo como:
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); }
Aquí nosotros:
- Ignora los espacios en blanco.
- Mira el carácter actual (o subcadena) para ver si es {, [, ", etc..
- Llama a la función de análisis correspondiente.
- Si encontramos «true», «false» o «null», trátelos directamente.
- Si nada más coincide, asumimos que es un número.
Dependiendo de sus necesidades, puede agregar un mejor manejo de errores. Por ejemplo, si la subcadena no coincide con un patrón reconocido, puede establecer un error.
-
Analizar un número, necesitamos analizar algo que parezca numérico, como 123, 3,14 o -0,001. Podemos implementar un enfoque rápido escaneando hasta llegar a un carácter no numérico:
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; }
Permitimos dígitos, un signo opcional (- o +), puntos decimales y notación de exponente (e o E). Una vez que encontramos algo más (como un espacio, una coma o un corchete), nos detenemos. Luego analizamos la subcadena en doble. Si su código necesita diferenciar números enteros de decimales, puede agregar comprobaciones adicionales.
Ampliando nuestro analizador con funcionalidad avanzada
Actualmente, tenemos un analizador JSON funcional en MQL5 que puede manejar objetos, matrices, cadenas, números, valores booleanos y nulos. En esta sección, exploraremos características y mejoras adicionales. Discutiremos cómo recuperar elementos secundarios de una manera más conveniente, cómo manejar errores potenciales con elegancia e incluso cómo convertir datos nuevamente en texto JSON. Al incorporar estas mejoras al analizador que hemos creado, obtendrá una herramienta más sólida y flexible, que puede satisfacer una variedad de necesidades del mundo real. -
Recuperación de hijos por clave o índice
Para que nuestro analizador sea realmente útil, queremos obtener fácilmente el valor de una clave dada en un objeto, o el valor en un índice particular en una matriz. Por ejemplo, digamos que tenemos este JSON:
{ "symbol": "EURUSD", "lots": 0.02, "settings": { "slippage": 2, "retries": 3 } }Imaginemos que lo hemos analizado y lo hemos convertido en un objeto CJsonNode raíz llamado rootNode. Nos gustaría hacer cosas como:
string sym = rootNode.GetChild("symbol").AsString(); double lot = rootNode.GetChild("lots").AsNumber(); int slip = rootNode.GetChild("settings").GetChild("slippage").AsNumber();
Nuestra estructura de código actual podría permitir esto si definimos GetChild(const string key) en el analizador. Así es como podría verse dicho método en su clase CJsonNode:
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; }
De esa manera, si el nodo actual no es un objeto, simplemente devolvemos NULL. De lo contrario, escaneamos todas las m_keys para encontrar una que coincida. Si es así, devolvemos un puntero al hijo correspondiente.
Del mismo modo, podemos definir un método para matrices:
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]; }
Si el nodo es una matriz, simplemente verificamos los límites y devolvemos el elemento apropiado. Si no es una matriz (o el índice está fuera de rango), devolvemos NULL. Comprobar si hay valores NULL es fundamental en el código real antes de desreferenciarlo.
-
Manejo elegante de errores
En muchos escenarios del mundo real, JSON puede llegar mal formado (por ejemplo, con comillas faltantes, comas finales o símbolos inesperados). Un analizador robusto debería detectar y reportar estos errores. Puedes hacer esto:
-
Devuelve un valor booleano: la mayoría de nuestros métodos de análisis ya devuelven un valor booleano. Si algo falla, devolvemos false. Pero también podemos almacenar un mensaje de error interno como m_errorMsg, para que el código de llamada pueda ver qué salió mal.
-
¿Seguir analizando o abortar?: Una vez que detecte un error de análisis fatal (por ejemplo, un carácter inesperado o una llave sin cerrar), puede decidir abortar todo el análisis y mantener su nodo en un estado "inválido". Alternativamente, puedes intentar saltar o recuperarte, pero eso es más avanzado.
Aquí hay un ajuste conceptual: dentro de ParseArray o ParseObject, si ves algo inesperado (como una clave sin comillas o dos puntos faltantes), puedes escribir:
Print("Parse Error: Missing colon after key at position ", pos); return false;
Luego, en tu código de llamada, podrías hacer:
CJsonNode root; if(!root.ParseString(jsonText)) { Print("Failed to parse JSON data. Check structure and try again."); // Perhaps handle defaults or stop execution }Depende de usted hasta qué punto desea detallar estos mensajes. A veces, un solo «error de análisis» es suficiente para un escenario comercial. En otras ocasiones, es posible que desees más matices para depurar tu entrada JSON.
-
-
Conversión de datos MQL5 a JSON
Leer JSON es solo la mitad de la historia. ¿Qué sucede si necesita enviar datos a un servidor o escribir sus propios registros en formato JSON? Puedes ampliar tu CJsonNodeclass con un método «serializer» que recorre los datos del nodo y reconstruye el texto JSON. Llamémoslo ToJsonString(), por ejemplo:
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 } }
A continuación, puede definir, por ejemplo, SerializeObject:
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; }
Y lo mismo ocurre con las matrices:
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; }
Notarás que hemos utilizado una función EscapeString. Podemos reutilizar el código que gestiona los escapes de cadenas JSON, como convertir caracteres especiales en \", \\, \n, etc. Esto garantiza que el resultado sea JSON válido si contiene comillas o saltos de línea.
Si desea un JSON "bien impreso", simplemente inserte algunos saltos de línea ("\n") y sangría. Un enfoque es construir una cadena corta de espacios en función de la profundidad, de modo que la estructura JSON se vuelva visualmente más ordenada:
string indentation = ""; for(int d=0; d<depth; d++) indentation += " ";
Luego inserte esa sangría antes de cada línea o elemento. Esto es opcional pero útil si regularmente necesitas leer o depurar la salida JSON manualmente.
Si sus datos JSON son enormes, digamos decenas de miles de líneas, es posible que deba considerar el rendimiento:
-
Operaciones de cadenas eficientes
Tenga en cuenta que las operaciones de subcadena repetidas (StringSubstr) pueden ser costosas. MQL5 es bastante eficiente, pero si sus datos son realmente masivos, puede considerar el análisis basado en fragmentos o un enfoque iterativo. -
Streaming frente a análisis DOM
Nuestra estrategia es un enfoque «similar al DOM», lo que significa que analizamos toda la entrada en una estructura de árbol. Si los datos son tan grandes que no caben cómodamente en la memoria, necesitará un analizador de transmisión que procese una parte a la vez. Esto es más complicado pero puede ser necesario para conjuntos de datos extremadamente grandes. -
Almacenamiento en caché
Si consulta con frecuencia el mismo objeto para obtener las mismas claves, puede almacenarlas en un mapa pequeño o mantener punteros directos para acelerar las búsquedas repetidas. Para tareas comerciales típicas, esto rara vez es necesario, pero es una opción si el rendimiento es fundamental.
-
-
Mejores prácticas
A continuación se presentan algunas prácticas recomendadas para mantener su código seguro y mantenible:
-
Siempre comprobar si hay valores NULL
Siempre que llame a GetChild(...), verifique que el resultado no sea NULL. Intentar acceder a un puntero nulo en MQL5 puede provocar fallos o un comportamiento extraño. -
Validar tipos
Si espera un número pero el hijo en realidad es una cadena, eso podría causar un problema. Considere verificar GetType() o usar código defensivo, por ejemplo:
CJsonNode* node = parent.GetChild("lots"); if(node != NULL && node.GetType() == JSON_NUMBER) double myLots = node.AsNumber();
Esto ayuda a garantizar que sus datos sean lo que usted cree que son.
Valores predeterminados
A menudo, desea una alternativa segura si al JSON le falta una clave. Puedes escribir una función auxiliar:
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(); }
De esta manera, su código puede manejar con elegancia los campos faltantes o no válidos.
-
Tenga en cuenta las limitaciones de cadenas y matrices de MQL5
MQL5 puede manejar cadenas grandes pero tiene en cuenta el uso de la memoria. Si su JSON es extremadamente grande, pruébelo con cuidado.
De manera similar, las matrices se pueden redimensionar, pero las matrices extremadamente grandes (cientos de miles de elementos) pueden volverse difíciles de manejar. -
Pruebas
Así como probarías la lógica de un EA con datos históricos, prueba tu analizador JSON con una variedad de entradas de muestra:- Objetos simples
- Objetos anidados
- Matrices de datos mixtos
- Números grandes, números negativos
- Booleano y nulo
- Cadenas con caracteres especiales o secuencias de escape
Cuanto más variaciones pruebes, más seguro estarás de que tu analizador es robusto.
-
En este punto, hemos convertido nuestro analizador básico en una poderosa utilidad JSON. Podemos analizar cadenas JSON en una estructura jerárquica, recuperar datos por clave o índice, manejar errores de análisis e incluso serializar nodos nuevamente en texto JSON. Esto es suficiente para muchos casos de uso de MQL5, como leer un archivo de configuración, obtener datos de la web (si tiene un puente a solicitudes HTTP) o generar sus propios registros JSON.
En la sección final, presentaremos una lista de códigos completa que reúne todo lo que hemos discutido. Podrás pegarlo en tu editor MQL5 como un único archivo .mqh o .mq5script, adaptarlo a tus convenciones de nombres y comenzar a usar datos JSON de inmediato. Junto con el código final, ofreceremos ideas finales y algunos consejos para ampliar aún más la biblioteca si tiene requisitos especializados.
Código completo
¡Felicitaciones por haber llegado hasta aquí! Aprendió los conceptos básicos de JSON en MQL5, creó un analizador paso a paso, lo extendió con funcionalidad avanzada y exploró las mejores prácticas para el uso en el mundo real. Ahora es el momento de compartir una lista de código única e integrada que fusiona todos los fragmentos en un módulo coherente. Puede colocar este código final en un archivo .mqh (o directamente en su archivo .mq5) e incluirlo donde necesite manejo de JSON en sus proyectos MetaTrader 5.
A continuación se muestra un ejemplo de implementación de código llamado CJsonNode.mqh. Unifica el análisis de objetos/matrices, la comprobación de errores, la serialización a JSON y la recuperación por clave o índice.
Importante: Este código es original y no una copia del fragmento de referencia proporcionado anteriormente. Sigue una lógica de análisis similar pero es distinta para satisfacer nuestro requisito de tener un enfoque nuevo. Como siempre, siéntase libre de adaptar los nombres de los métodos, agregar un manejo de errores más sólido o implementar funciones especializadas según sea necesario.
#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__
Tomemos un ejemplo de su uso en un script:
//+------------------------------------------------------------------+ //| 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()); } } //+------------------------------------------------------------------+
El resultado esperado se explica por sí solo. No dudes en probarlo.
Conclusión
Con este código final en la mano, tienes todo lo que necesitas para analizar, manipular e incluso generar JSON directamente en MetaTrader 5:
- Análisis de JSON: ParseString() transforma el texto sin formato en una jerarquía de nodos estructurada.
- Consulta de datos: GetChild(key) y GetChild(index) le permiten navegar por objetos y matrices fácilmente.
- Validación: verifique IsValid() y GetErrorMsg() para ver si el análisis se realizó correctamente o si hubo problemas (como llaves no coincidentes).
- Serialización: ToJsonString() vuelve a ensamblar el nodo (y los hijos) en texto JSON válido.
Siéntete libre de adaptar esta biblioteca a tus necesidades específicas. Podría, por ejemplo, agregar informes de errores más completos, conversiones numéricas especializadas o capacidades de transmisión para conjuntos de datos muy grandes. Pero la base aquí debería ser suficiente para la mayoría de los casos de uso típicos, como leer parámetros de un archivo o interactuar con API basadas en web.
¡Eso es todo! Ha llegado al final de nuestro análisis profundo del manejo de JSON en MQL5. Ya sea que esté implementando un motor comercial complejo basado en datos o simplemente cargando parámetros de configuración desde un archivo local, un analizador y serializador JSON confiable puede hacerle la vida mucho más fácil. Esperamos que este artículo (y el código que contiene) le ayude a integrar JSON sin problemas en sus flujos de trabajo de trading automatizado.
¡Feliz codificación! ¡Feliz trading!
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16791
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Características del Wizard MQL5 que debe conocer (Parte 54): Aprendizaje por refuerzo con SAC híbrido y tensores
Automatización de estrategias de trading en MQL5 (Parte 6): Dominar la detección de bloques de órdenes para el comercio inteligente con dinero
Redes neuronales en el trading: Integración de la teoría del caos en la previsión de series temporales (Attraos)
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 12): Flujo externo (III) TrendMap
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Bueno, cuando lo tenga en mis manos, haré una comparativa.
He comparado la velocidad de 4 librerías, incluyendo ToyJson3 de MQL5Book. Como json de muestra tomé la respuesta de Binance a "exchangeInfo" con el tamaño de 768 Kb. Cuando la biblioteca lo lee de una cadena, se analiza en su totalidad, a continuación, selecciono un símbolo y leer todos sus datos. En un bucle 100 veces.
Resultado (tiempo de procesamiento de la consulta):
99,5 ms - JAson 1.12(https://www.mql5.com/es/code/13663)
85,5 ms - JAson 1.13
46,9 ms - ToyJson3 (https://www.mql5.com/ru/forum/459079/page4#comment_57805801)
41 ms - JSON(https://www.mql5.com/es/code/53107)
1132 ms - JsonNode (esta biblioteca)
38 ms - mi implementación basada en JSON
PD: Érase una vez otra librería, muy simplificada, que parecía aparecer por aquí. Pero le he perdido la pista.
PPS: No publico el script para medirlo. El código está en una forma completamente antiestética.
He comparado la velocidad de 4 librerías, incluyendo ToyJson3 de MQL5Book. Como json de muestra tomé la respuesta de Binance a "exchangeInfo" con el tamaño de 768 Kb. Cuando la biblioteca lo lee de una cadena, se analiza en su totalidad, a continuación, selecciono un símbolo y leer todos sus datos. En un bucle 100 veces.
Resultado (tiempo de procesamiento de la consulta):
99,5 ms - JAson 1.12(https://www.mql5.com/es/code/13663)
85,5 ms - JAson 1.13
46,9 ms - ToyJson3 (https://www.mql5.com/ru/forum/459079/page4#comment_57805801)
41 ms - JSON(https://www.mql5.com/es/code/53107)
1132 ms - JsonNode (esta biblioteca)
38 ms - mi implementación basada en JSON
PD: Érase una vez otra librería, muy simplificada, que parecía aparecer por aquí. Pero le he perdido la pista.
PPS: No publico el script para medirlo. El código está en una forma completamente antiestético.
¿Podría publicar la cadena o el archivo json, por favor?
https://fapi.binance.com/fapi/v1/exchangeInfo
https://eapi.binance.com/eapi/v1/exchangeInfo
778 KB (796.729 bytes).
¿Podría publicar la cadena o el archivo json, por favor?