Dominando JSON: Crie Seu Próprio Leitor JSON do Zero em MQL5
Introdução
Olá e bem-vindo! Se você já tentou analisar ou manipular dados JSON em MQL5, pode ter se perguntado se existe uma abordagem direta e flexível para fazer isso. JSON, que significa JavaScript Object Notation, ganhou popularidade como um formato leve de intercâmbio de dados que é ao mesmo tempo legível por humanos e amigável para máquinas. Embora o MQL5 seja predominantemente conhecido por criar Expert Advisors, Indicadores e Scripts para a plataforma MetaTrader 5, ele não possui uma biblioteca JSON nativa. Isso significa que, se você quiser trabalhar com dados JSON — seja de uma API web, um servidor externo ou de seus próprios arquivos locais — provavelmente precisará criar uma solução personalizada ou integrar uma biblioteca existente.
Neste artigo, nosso objetivo é preencher essa lacuna demonstrando como criar seu próprio leitor JSON em MQL5. Ao longo do caminho, exploraremos os conceitos fundamentais da análise de JSON, passando pela criação de uma estrutura de classes flexível capaz de lidar com diferentes tipos de elementos JSON (como objetos, arrays, strings, números, booleanos e valores null). Nosso objetivo final é capacitá-lo a analisar confortavelmente strings JSON e acessar ou modificar os dados dentro delas, tudo a partir da conveniência do seu ambiente MetaTrader 5.
Seguiremos uma estrutura semelhante à que vimos em outros artigos relacionados ao MQL5, mas com foco específico na análise e uso de JSON. Este único artigo será dividido em cinco seções principais: uma introdução (a que você está lendo agora), um mergulho mais profundo nos fundamentos do JSON e como ele se encaixa no MQL5, um guia passo a passo para construir um parser JSON básico do zero, uma exploração de recursos avançados para manipulação de JSON e, por fim, uma listagem completa do código mais considerações finais.
JSON está em toda parte. Seja ao buscar dados de mercado de um serviço de terceiros, enviar seus próprios registros de trading ou experimentar estratégias complexas que exigem uma configuração dinâmica, o JSON permanece um formato quase universal. Alguns dos casos de uso práticos mais comuns do JSON no mundo do trading algorítmico incluem:
-
Busca de Dados de Mercado: Muitos APIs modernas de corretoras ou serviços de dados financeiros oferecem dados em tempo real ou históricos em JSON. Ter um leitor JSON à sua disposição permite analisar rapidamente esses dados e integrá-los à sua estratégia de trading.
-
Configuração de Estratégia: Suponha que você tenha um Expert Advisor que suporte múltiplos parâmetros: spread máximo, nível de risco desejado da conta ou horários permitidos de negociação. Um arquivo JSON pode armazenar essas configurações de forma organizada, e um leitor JSON em MQL5 pode carregar ou atualizar esses parâmetros dinamicamente sem recompilar seu código.
-
Envio de Logs ou Dados: Em determinadas configurações, você pode querer transmitir seus logs de negociação ou mensagens de depuração para um servidor externo para análise. Enviá-los como JSON pode ajudar a manter seus logs consistentes, facilmente analisáveis e integráveis com ferramentas que esperam dados estruturados.
Muitos exemplos online mostram como analisar JSON em linguagens como Python, JavaScript ou C++. No entanto, o MQL5 é uma linguagem especializada com suas próprias restrições. Isso significa que precisamos ter cuidado com certos aspectos: manipulação de memória, uso de arrays, tipos de dados estritos e assim por diante.
Criaremos uma classe personalizada (ou conjunto de classes) dedicada à análise e manipulação de JSON. A ideia é projetá-la para que você possa fazer 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());
Claro, sua abordagem final pode diferir ligeiramente em nomenclatura ou estrutura, mas esse tipo de usabilidade é o objetivo. Ao construir um parser robusto, você terá uma base para expansões — como converter estruturas de dados do MQL5 em JSON para saída ou adicionar lógica de cache para consultas JSON repetidas.
Você pode ter encontrado diferentes bibliotecas JSON disponíveis, incluindo alguns scripts curtos que analisam JSON manipulando arrays de caracteres. Aprenderemos com essas abordagens existentes, mas não copiaremos o código diretamente. Em vez disso, construiremos algo novo com uma ideia semelhante para que seja mais fácil de entender e manter. Analisaremos nosso código trecho por trecho e, ao final deste artigo, você terá acesso a uma implementação final coesa que poderá anexar aos seus próprios programas de trading.
Nossa esperança é que essa abordagem de construir uma biblioteca desde a base — explicando cada segmento em linguagem simples — lhe proporcione uma compreensão mais profunda do que se apenas fornecêssemos uma solução pronta. Ao internalizar como o parser opera, você poderá depurá-lo e personalizá-lo com mais facilidade posteriormente.
Embora o JSON seja um formato baseado em texto, as strings do MQL5 podem conter uma variedade de caracteres especiais, incluindo quebras de linha, retornos de carro ou caracteres Unicode. Nossa implementação considerará algumas dessas nuances e tentará tratá-las adequadamente. Ainda assim, sempre garanta que seus dados de entrada sejam JSON válido. Se você receber JSON malformado ou enfrentar texto aleatório que afirma ser válido, provavelmente precisará adicionar um tratamento de erros mais robusto.
Aqui está uma prévia rápida de como este artigo está organizado:
-
Seção 1 (Você está aqui!) – Introdução
Acabamos de discutir o que é JSON, por que ele importa e como abordaremos a escrita de um parser personalizado em MQL5. Isso estabelece a base para todo o restante. -
Seção 2 – O Básico: Fundamentos de JSON e MQL5
Revisaremos os principais elementos estruturais do JSON, depois os mapearemos para os tipos de dados do MQL5 e mostraremos quais aspectos exigem nossa atenção cuidadosa. -
Seção 3 – Estendendo Nosso Parser com Funcionalidade Avançada
Aqui, falaremos sobre possíveis expansões ou melhorias: como lidar com arrays, como adicionar verificação de erros e como converter dados do MQL5 de volta para JSON caso você precise enviar dados. -
Seção 4 – Código Completo
Por fim, reuniremos toda a nossa biblioteca em um único lugar, fornecendo um único arquivo de referência. -
Seção 5 – Conclusão
Resumiremos as principais lições aprendidas e apontaremos alguns próximos passos que você pode considerar em seus próprios projetos.
Ao final do artigo, você terá uma biblioteca totalmente funcional de análise e manipulação de JSON em MQL5. Além disso, entenderá como tudo funciona internamente, tornando-se mais preparado para integrar JSON às suas soluções automatizadas de trading.
O Básico – Fundamentos de JSON e MQL5
Bem-vindo de volta! Agora que estabelecemos o plano geral para nosso leitor JSON personalizado em MQL5, é hora de aprofundar nos detalhes do JSON e ver como eles se mapeiam para o MQL5. Exploraremos a estrutura do JSON, abordaremos quais tipos de dados são mais fáceis de analisar e identificaremos possíveis armadilhas ao trazer dados JSON para o MetaTrader 5. Ao final desta seção, você terá uma ideia muito mais clara de como lidar com JSON em um ambiente MQL5, preparando o cenário para a codificação prática que virá.
JSON (JavaScript Object Notation) é um formato baseado em texto comumente usado para transmissão e armazenamento de dados. Diferentemente do XML, ele é relativamente leve: os dados são delimitados por chaves ( {} ) para objetos ou colchetes ( [] ) para arrays, e cada campo é organizado em pares simples de chave-valor. Aqui está um pequeno exemplo:
{
"symbol": "EURUSD",
"lots": 0.05,
"enableTrade": true
}
Isso é fácil para um humano ler e direto para uma máquina analisar. Cada informação — como "symbol" ou "enableTrade" — é conhecida como uma chave que contém algum valor. O valor pode ser uma string, um número, um booleano ou até mesmo outro objeto ou array aninhado. Em resumo, o JSON trata de organizar dados em uma estrutura de árvore aninhada, permitindo representar desde parâmetros básicos até dados hierárquicos mais complexos.
JSON versus Tipos de Dados do MQL5:
- Strings: Strings JSON aparecem entre aspas duplas, como "Hello World". No MQL5, também temos o tipo string, mas essas strings podem incluir caracteres especiais, sequências de escape e Unicode. Assim, a primeira nuance que enfrentaremos é garantir que nosso parser lide corretamente com aspas, símbolos escapados (como " ) e possivelmente pontos de código Unicode (por exemplo, \u00A9 ).
- Números: Em JSON, números podem ser inteiros (como 42 ) ou decimais ( 3.14159 ). O MQL5 armazena números principalmente como int (para inteiros) ou double (para valores de ponto flutuante). No entanto, nem todos os valores numéricos em JSON se mapeiam perfeitamente para um int. Por exemplo, 1234567890 é válido, mas em alguns contextos você pode precisar de um long no MQL5 se o número for realmente grande. Precisaremos prestar atenção especial quando o número JSON estiver além do alcance de um inteiro típico de 32 bits. Além disso, pode ser necessário converter um inteiro grande em double se ele ultrapassar o limite de um inteiro padrão, mas isso traz possíveis problemas de arredondamento.
- Booleanos: JSON usa true e false em minúsculas. Enquanto isso, o MQL5 usa bool. Esse é um mapeamento direto, mas teremos que detectar cuidadosamente esses tokens ( true e false ) durante a análise. Há um pequeno detalhe: quaisquer erros de sintaxe — como True ou FALSE em maiúsculas — não são JSON válidos, embora alguns parsers em outras linguagens os permitam. Se seus dados às vezes usam booleanos em maiúsculas, você precisará tratar isso adequadamente ou garantir que seus dados estejam estritamente em conformidade com JSON.
- NULL: Um valor null em JSON frequentemente indica um campo vazio ou ausente. O MQL5 não possui um “tipo null” dedicado.Em vez disso, podemos optar por representar o null do JSON como uma enumeração interna especial (como jtNULL, se definirmos um enum para nossos tipos de elementos JSON) ou tratá-lo como uma string vazia ou valor padrão. Veremos em breve como gerenciar nulls no parser.
- Objetos: Quando você vê chaves, { ... }, isso é um objeto JSON. Essencialmente, é uma coleção de pares chave-valor. No MQL5, não há um tipo dicionário embutido, mas podemos simulá-lo armazenando um array dinâmico de pares ou construindo uma classe personalizada para conter chaves e valores. Normalmente definiremos algo como uma classe CMyJsonObject (ou uma classe de propósito geral com um estado interno de “objeto”) que abriga uma lista de filhos. Cada filho possui uma chave ( string ) e um valor que pode ser qualquer tipo de dado JSON.
- Arrays: Arrays em JSON são listas ordenadas cercadas por colchetes, [ ... ]. Cada item do array pode ser uma string, número, objeto ou até outro array. No MQL5, lidamos com arrays usando a função ArrayResize e indexação direta. Provavelmente armazenaremos um array JSON como um array dinâmico de elementos. Nosso código precisará acompanhar o fato de que um determinado nó é um array, juntamente com os filhos dentro dele.
Vejamos alguns dos desafios potenciais:
- Manipulação de Sequências de Escape: Em JSON, uma barra invertida \ pode preceder caracteres como aspas ou novas linhas. Por exemplo, você pode ver "description": "Line one\nLine two". Precisamos interpretar \n como uma quebra de linha real dentro da string final. Sequências especiais incluem:
- " para aspas duplas
- \\ para barra invertida
- \/ às vezes para barra invertida
- \n para nova linha
- \t para tabulação
- \u para pontos de código Unicode
Precisaremos converter metodicamente essas sequências na string JSON bruta para os caracteres reais que elas representam no MQL5. Caso contrário, o parser pode armazená-las incorretamente ou falhar ao lidar com dados de entrada que utilizem esses padrões de escape padrão.
- Remoção de Espaços em Branco e Caracteres de Controle: Uma string JSON válida pode incluir espaços, tabulações e quebras de linha (especialmente entre elementos). Embora sejam permitidos e não tenham significado semântico na maioria dos casos, podem complicar a lógica de análise se não tivermos cuidado. Um parser robusto normalmente ignora qualquer espaço em branco fora de strings entre aspas. Isso significa que precisaremos ignorá-los ao avançar de um token para o próximo.
- Lidando com Grandes Volumes de Dados: Se sua string JSON for extremamente grande, você pode se preocupar com restrições de memória no MQL5. A linguagem lida razoavelmente bem com arrays, mas existem limites superiores ao se aproximar de dezenas de milhões de elementos. A maioria dos traders raramente precisa de JSON tão grande, mas vale observar que uma abordagem “streaming” ou iterativa pode ser necessária nesses casos. Para uso normal — como leitura de configurações ou conjuntos de dados de tamanho moderado — nossa abordagem direta deve ser suficiente.
-
Nem todo JSON é perfeito. Se seu parser tentar ler uma estrutura inválida — por exemplo, uma aspas ausente ou uma vírgula final — ele precisa lidar com isso de forma adequada. Você pode querer definir códigos de erro ou armazenar internamente uma mensagem de erro para que o código chamador possa detectar e responder a erros de análise. Em um contexto de trading, você poderia:
- Mostrar uma caixa de mensagem ou imprimir um erro no journal.
- Retornar a configurações seguras padrão se o JSON for inválido.
- Interromper a execução do Expert Advisor se dados críticos não puderem ser analisados.
Incorporaremos verificações básicas para capturar erros como colchetes não correspondentes ou tokens não reconhecidos. Relatórios de erro mais avançados também são possíveis, mas isso depende do nível de rigor desejado.
Como o JSON pode ser aninhado, nosso parser provavelmente utilizará uma única classe ou hierarquia de classes em que cada nó pode ser um de vários tipos:
- Objeto – Contém pares chave-valor
- Array – Contém elementos indexados
- String – Contém dados textuais
- Número – Armazena dados numéricos como double ou possivelmente como long
- Booleano – Verdadeiro ou falso
- Null – Sem valor
Podemos implementar um enum para esses possíveis tipos de nó, como:
enum JSONNodeType { JSON_UNDEFINED = 0, JSON_OBJECT, JSON_ARRAY, JSON_STRING, JSON_NUMBER, JSON_BOOL, JSON_NULL };
Em seguida, fornecemos à nossa classe de parser uma variável que mantém qual tipo o nó atual possui. Também armazenamos o conteúdo do nó. Se for um objeto, mantemos um array de nós filhos indexados por string. Se for um array, mantemos uma lista de nós filhos indexados a partir de 0. Se for uma string, mantemos uma string. Se for um número, podemos armazenar um double mais um inteiro interno caso seja integral, etc.
Uma abordagem alternativa é ter uma classe separada para objetos, arrays, strings e assim por diante. Isso pode se tornar confuso no MQL5 porque você precisaria fazer conversões frequentes entre elas. Em vez disso, provavelmente adotaremos uma única classe (ou uma classe principal com algumas estruturas auxiliares) capaz de representar dinamicamente qualquer tipo JSON. Essa abordagem unificada é direta ao lidar com elementos aninhados, pois cada filho é essencialmente o mesmo tipo de nó com um tipo interno diferente. Isso ajuda a manter o código mais curto e generalizado.
Mesmo que seu projeto imediato exija apenas a leitura de JSON, você pode eventualmente querer criar JSON a partir dos seus dados em MQL5. Por exemplo, se você gerar sinais de trade e quiser enviá-los a um servidor como JSON, ou se quiser registrar seus trades em um arquivo JSON estruturado, precisará de um “encoder” ou “serializer”. Nosso parser final poderá ser estendido para fazer isso. O código básico que escreveremos para lidar com strings e arrays também pode ajudar na geração de JSON. Tenha isso em mente ao projetar os métodos da sua classe: “Como posso chamar a mesma lógica ao contrário para produzir texto JSON a partir de dados internos?”
Agora temos uma compreensão sólida de como as estruturas do JSON se correlacionam com o MQL5. Sabemos que precisamos de uma classe flexível capaz de:
- Armazenar o Tipo do Nó – Seja número, string, objeto, array, booleano ou null.
- Analisar – Ler o texto bruto caractere por caractere, interpretando chaves, colchetes, aspas e tokens especiais.
- Acessar – Fornecer métodos convenientes para obter ou definir nós filhos por chave (para objetos) ou por índice (para arrays).
- Converter – Transformar nós numéricos ou booleanos em tipos primitivos do MQL5, como double, int ou bool.
- Escape/Unescape – Converter sequências codificadas em JSON dentro de strings para strings normais do MQL5 (e vice-versa, se adicionarmos futuramente um método “to JSON”).
- Verificação de Erros – Possivelmente detectar entradas malformadas ou tokens desconhecidos e tratá-los adequadamente.
Abordaremos esses recursos passo a passo na próxima seção, onde a jornada real de codificação começa. Se você está preocupado com desempenho ou uso de memória, fique tranquilo: uma abordagem direta geralmente é suficientemente rápida e eficiente em memória para uso normal. Se encontrar gargalos de desempenho ou restrições de memória, você sempre poderá analisar o código ou adotar técnicas de análise parcial.
Na Seção 3, começaremos a construir nosso parser em detalhes. Definiremos a classe principal — algo como CJsonNode — e iniciaremos com as tarefas mais simples: armazenar o tipo e valor de um nó, além de escrever um método “tokenizer” que identifica tokens JSON (como chaves ou aspas). Uma vez estabelecida a base, avançaremos para suportar objetos, arrays, elementos aninhados e extração de dados.
Se você pretende analisar pequenos arquivos JSON de configuração ou recuperar grandes volumes de dados da web, esses mesmos fundamentos se aplicam. Mesmo que você seja novo na leitura de dados externos em MQL5, não se preocupe: quando observar a lógica passo a passo, tudo se torna bastante administrável.
Respire fundo agora; estamos prestes a mergulhar no código. Na próxima seção, trabalharemos diretamente na construção do parser JSON personalizado passo a passo, juntamente com dicas práticas para garantir que seus dados sejam processados com confiabilidade. Vamos fazer o MQL5 “falar” JSON como um especialista!
Classe Central do Parser: O objetivo da nossa classe de parser é representar qualquer parte de dados JSON (às vezes chamada de “nó” em uma árvore). Aqui está um esboço do que podemos precisar:
- Uma Enumeração para Tipos de Nó: Queremos distinguir facilmente entre objeto JSON, array, string, etc. Vamos definir algo como:
enum JsonNodeType { JSON_UNDEF = 0, JSON_OBJ, JSON_ARRAY, JSON_STRING, JSON_NUMBER, JSON_BOOL, JSON_NULL };
- Variáveis de Membro:
Cada CJsonNode armazena - Um JsonNodeType m_typeto para identificar o tipo do nó.
- Para objetos: uma estrutura (como um array) que contém pares chave-valor.
- Para arrays: uma estrutura que contém nós filhos indexados.
- Para strings: uma string m_value.
- Para números: um double m_numVal, possivelmente um long adicional m_intVal se necessário.
- Para booleanos: um bool m_boolVal.
- Métodos de Análise e Utilidade:
- Um método para analisar o texto JSON bruto.
- Métodos para recuperar nós filhos por índice ou chave.
- Possivelmente um método para “tokenizar” a entrada, ajudando a identificar colchetes, chaves, strings, booleanos e assim por diante.
Manteremos essas ideias em mente ao começar a codificar. Abaixo está um trecho ilustrativo que mostra como poderíamos definir essa classe em MQL5 (em um arquivo chamado algo como CJsonNode.mqh). Seguiremos passo a passo.
//+------------------------------------------------------------------+ //| 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 };
No código acima:
- m_children[]: Um array dinâmico que pode armazenar múltiplos objetos filhos CJsonNode. Para arrays, cada filho é indexado, enquanto para objetos, cada filho possui uma chave associada armazenada em m_keys[].
- ParseString(const string jsonText): Este método público é nosso “ponto de entrada” principal.Você fornece uma string JSON, e ele tenta analisá-la, preenchendo os dados internos do nó.
- ParseRoot, ParseObject, ParseArray, ParseValue: Definiremos cada um desses métodos privados para lidar com construções específicas do JSON.
Estamos mostrando um esqueleto agora, mas preencheremos os detalhes em instantes. Ao analisar JSON, lemos da esquerda para a direita, ignorando espaços em branco até encontrar um caractere estrutural. Por exemplo:
- Um '{' significa que temos o início de um objeto.
- Um '[' significa que temos um array.
- Um '"' significa que uma string está prestes a começar.
- Um dígito ou um sinal de menos pode indicar um número.
- As sequências “true”, “false” ou “null” também aparecem no JSON.
Vamos ver uma versão simplificada de como poderíamos analisar um texto inteiro em nosso 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; }
- Reset – Limpamos quaisquer dados anteriores.
- pos=0 – Esta é nossa posição de caractere na string.
- Chamar ParseRoot(jsonText) – Uma função que definiremos para configurar m_type e preencher m_children ou m_value conforme necessário.
- SkipWhitespace(jsonText,pos) – Frequentemente ignoramos quaisquer espaços, tabulações ou quebras de linha que possam aparecer.
- Verificar posição final – Se tudo for analisado corretamente, pos deve estar próximo ao final da string. Caso contrário, pode haver texto residual ou erro.
Agora, vamos observar mais de perto o ParseRoot. Por brevidade, imagine que ele se pareça com isto:
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); }
Para demonstração, verificamos o primeiro caractere que não seja espaço em branco e decidimos se é um objeto ( { ), array ( [ ) ou outra coisa (que pode ser string, número, booleano ou null). Nossa implementação real pode ser mais defensiva, tratando erros se o caractere for inesperado.
Vamos ver como analisamos diferentes casos:
- Analisar um Objeto: Quando vemos uma chave de abertura ( { ), criamos um nó de objeto. Em seguida, procuramos repetidamente pares chave-valor até encontrar uma chave de fechamento ( } ). Aqui está um trecho conceitual de como ParseObject poderia funcionar:
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; }
Explicações:
- Confirmamos que o caractere é {, definimos nosso tipo como JSON_OBJ e incrementamos pos.
- Se } vier em seguida, o objeto está vazio.
- Caso contrário, repetimos até ver } ou um erro. Cada iteração:
- Analisar uma chave de string entre aspas.
- Ignorar espaços, esperar dois-pontos ( : ).
- Analisar o próximo valor (que pode ser string, número, array, objeto etc.).
- Armazenar isso em nossos arrays ( m_children e m_keys).
- Se encontrarmos }, terminamos. Se encontrarmos uma vírgula, continuamos.
Esse loop é central para a leitura de um objeto JSON. A estrutura se repete para arrays, exceto que arrays não possuem chaves — apenas elementos indexados.
-
Analisar um Array: Arrays começam com [. Dentro, encontraremos zero ou mais elementos separados por vírgulas. Algo como:
[ "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; }
Ignoramos [ e quaisquer espaços em branco. Se encontrarmos ], está vazio. Caso contrário, analisamos elementos em um loop até alcançar ]. A principal diferença em relação aos objetos é que não analisamos pares chave-valor — apenas valores, em sequência.
-
Analisar um Valor, Valores em JSON podem ser uma string, número, objeto, array, booleano ou null. Nosso ParseValue pode fazer 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); }
Aqui nós:
- Ignoramos espaços em branco.
- Observamos o caractere atual (ou substring) para verificar se é {, [, ", etc.
- Chamamos a função de análise correspondente.
- Se encontrarmos “true”, “false” ou “null”, tratamos diretamente.
- Se nada mais corresponder, assumimos que é um número.
Dependendo das suas necessidades, você pode adicionar um tratamento de erros melhor. Por exemplo, se a substring não corresponder a um padrão reconhecido, você pode definir um erro.
-
Analisar um Número, precisamos analisar algo que pareça numérico, como 123, 3.14 ou -0.001. Podemos implementar uma abordagem rápida percorrendo até encontrar um caractere não 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, um sinal opcional (- ou +), pontos decimais e notação exponencial (e ou E). Quando encontramos outro caractere — como espaço, vírgula ou colchete — paramos. Em seguida, analisamos a substring como double. Se o seu código precisar diferenciar inteiros de decimais, você pode adicionar verificações extras.
Estendendo Nosso Parser com Funcionalidade Avançada
Até agora, temos um parser JSON funcional em MQL5 que pode lidar com objetos, arrays, strings, números, booleanos e valores null. Nesta seção, exploraremos recursos adicionais e melhorias. Discutiremos como recuperar elementos filhos de maneira mais conveniente, como lidar com possíveis erros de forma adequada e até como converter dados de volta para texto JSON. Ao adicionar essas melhorias ao parser que construímos, você obterá uma ferramenta mais robusta e flexível — capaz de atender a diversas necessidades do mundo real. -
Recuperando Filhos por Chave ou Índice
Se nosso parser deve ser realmente útil, queremos obter facilmente o valor de uma determinada chave em um objeto ou o valor em um índice específico de um array. Por exemplo, suponha que temos este JSON:
{ "symbol": "EURUSD", "lots": 0.02, "settings": { "slippage": 2, "retries": 3 } }Vamos imaginar que já o analisamos em um objeto raiz CJsonNode chamado rootNode. Gostaríamos de fazer coisas como:
string sym = rootNode.GetChild("symbol").AsString(); double lot = rootNode.GetChild("lots").AsNumber(); int slip = rootNode.GetChild("settings").GetChild("slippage").AsNumber();
Nossa estrutura de código atual pode permitir isso se definirmos GetChild(const string key) no parser. Veja como tal método pode parecer em sua classe 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; }
Dessa forma, se o nó atual não for um objeto, simplesmente retornamos NULL. Caso contrário, percorremos todos os m_keys para encontrar um correspondente. Se encontrarmos, retornamos um ponteiro para o filho correspondente.
Da mesma forma, podemos definir um método para arrays:
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]; }
Se o nó for um array, apenas verificamos os limites e retornamos o elemento apropriado. Se não for um array — ou se o índice estiver fora do intervalo — retornamos NULL. Verificar NULL é crucial no seu código real antes de desreferenciar.
-
Tratamento de Erros de Forma Adequada
Em muitos cenários do mundo real, o JSON pode chegar malformado (por exemplo, aspas ausentes, vírgulas finais ou símbolos inesperados). Um parser robusto deve detectar e relatar esses erros. Você pode fazer isso por meio de:
-
Retornar um Booleano: A maioria dos nossos métodos de análise já retorna bool. Se algo falhar, retornamos false. Mas também podemos armazenar uma mensagem de erro interna como m_errorMsg, para que o código chamador veja o que deu errado.
-
Continuar Analisando ou Abortar?: Ao detectar um erro fatal de análise — como um caractere inesperado ou uma chave não fechada — você pode decidir abortar toda a análise e manter o nó em um estado “inválido”. Alternativamente, pode tentar ignorar ou recuperar, mas isso é mais avançado.
Aqui está um ajuste conceitual: dentro de ParseArray ou ParseObject, se você encontrar algo inesperado (como uma chave sem aspas ou ausência de dois-pontos), pode escrever:
Print("Parse Error: Missing colon after key at position ", pos); return false;
Então, no seu código chamador, você pode fazer:
CJsonNode root; if(!root.ParseString(jsonText)) { Print("Failed to parse JSON data. Check structure and try again."); // Perhaps handle defaults or stop execution }Cabe a você decidir o nível de detalhamento dessas mensagens. Às vezes, uma única mensagem “parse failed” é suficiente em um cenário de trading. Outras vezes, você pode querer mais nuances para depurar sua entrada JSON.
-
-
Convertendo Dados do MQL5 de Volta para JSON
Ler JSON é apenas metade da história. E se você precisar enviar dados de volta para um servidor ou escrever seus próprios logs em formato JSON? Você pode estender sua classe CJsonNode com um método “serializer” que percorre os dados do nó e reconstrói o texto JSON. Vamos chamá-lo de ToJsonString(), por exemplo:
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 } }
Então você pode definir, por exemplo, 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; }
E, de forma semelhante, para 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; }
Você notará que usamos uma função EscapeString. Podemos reutilizar o código que lida com sequências de escape de strings JSON — como converter caracteres especiais em ", \, \n, etc. Isso garante que a saída seja JSON válido se contiver aspas ou quebras de linha.
Se você quiser JSON “formatado”, basta inserir algumas quebras de linha ( "\n") e indentação. Uma abordagem é construir uma pequena string de espaços com base na profundidade, para que a estrutura JSON fique visualmente mais organizada:
string indentation = ""; for(int d=0; d<depth; d++) indentation += " ";
Em seguida, inserir essa indentação antes de cada linha ou elemento. Isso é opcional, mas útil se você precisa ler ou depurar manualmente a saída JSON com frequência.
Se seus dados JSON forem enormes, por exemplo dezenas de milhares de linhas, talvez seja necessário considerar desempenho:
-
Operações de String Eficientes
Esteja atento ao fato de que operações repetidas de substring ( StringSubstr ) podem ser custosas. O MQL5 é relativamente eficiente, mas se seus dados forem realmente massivos, você pode considerar análise em blocos ou uma abordagem iterativa. -
Streaming vs. Parsing DOM
Nossa estratégia é uma abordagem “semelhante a DOM”, o que significa que analisamos toda a entrada em uma estrutura de árvore. Se os dados forem tão grandes que não caibam confortavelmente na memória, você precisará de um parser de streaming que processe uma parte por vez. Isso é mais complexo, mas pode ser necessário para conjuntos de dados extremamente grandes. -
Armazenamento em Cache
Se você consulta frequentemente o mesmo objeto para as mesmas chaves, pode armazená-las em um pequeno mapa ou manter ponteiros diretos para acelerar buscas repetidas. Para tarefas típicas de trading, isso raramente é necessário, mas é uma opção se o desempenho for crítico.
-
-
Boas Práticas
Abaixo estão algumas boas práticas para manter seu código seguro e sustentável:
-
Sempre Verifique NULL
Sempre que chamar GetChild(...), verifique se o resultado não é NULL. Tentar acessar um ponteiro nulo no MQL5 pode causar falhas ou comportamentos inesperados. -
Validar Tipos
Se você espera um número, mas o filho é na verdade uma string, isso pode causar problemas. Considere verificar GetType() ou usar código defensivo, por exemplo:
CJsonNode* node = parent.GetChild("lots"); if(node != NULL && node.GetType() == JSON_NUMBER) double myLots = node.AsNumber();
Isso ajuda a garantir que seus dados sejam o que você espera.
Valores Padrão
Frequentemente, você deseja um fallback seguro se o JSON não contiver uma chave. Você pode escrever uma função 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(); }
Dessa forma, seu código pode lidar de forma adequada com campos ausentes ou inválidos.
-
Atenção às Limitações de Strings e Arrays do MQL5
O MQL5 consegue lidar com strings grandes, mas monitore o uso de memória. Se seu JSON for extremamente grande, teste cuidadosamente.
Da mesma forma, arrays podem ser redimensionados, mas arrays extremamente grandes (centenas de milhares de elementos) podem se tornar difíceis de gerenciar. -
Testando
Assim como você testaria a lógica de um EA com dados históricos, teste seu parser JSON com uma variedade de entradas de exemplo:- Objetos simples
- Objetos aninhados
- Arrays com dados mistos
- Números grandes, números negativos
- Booleanos e null
- Strings com caracteres especiais ou sequências de escape
Quanto mais variações você testar, maior será sua confiança de que seu parser é robusto.
-
Neste ponto, transformamos nosso parser básico em uma poderosa utilidade JSON. Podemos analisar strings JSON em uma estrutura hierárquica, recuperar dados por chave ou índice, lidar com falhas de análise e até serializar nós de volta para texto JSON. Isso é suficiente para muitos casos de uso em MQL5 — como ler um arquivo de configuração, buscar dados da web (se houver integração com requisições HTTP) ou gerar seus próprios logs em JSON.
Na seção final, apresentaremos uma listagem completa de código que reúne tudo o que discutimos. Você poderá colá-la no seu editor MQL5 como um único arquivo .mqh ou script .mq5, adaptá-la às suas convenções de nomenclatura e começar a usar dados JSON imediatamente. Junto ao código final, ofereceremos considerações finais e algumas sugestões para estender ainda mais a biblioteca, caso você tenha requisitos especializados.
Código Completo
Parabéns por chegar até aqui! Você aprendeu os fundamentos do JSON em MQL5, construiu um parser passo a passo, estendeu-o com funcionalidades avançadas e explorou boas práticas para uso no mundo real. Agora é hora de compartilhar uma única listagem de código integrada que reúne todos os trechos em um módulo coerente. Você pode colocar esse código final em um arquivo .mqh (ou diretamente em seu arquivo .mq5) e incluí-lo onde precisar de manipulação de JSON em seus projetos MetaTrader 5.
Abaixo está um exemplo de implementação chamado CJsonNode.mqh. Ele unifica análise de objetos/arrays, verificação de erros, serialização de volta para JSON e recuperação por chave ou índice.
Importante: Este código é original e não é uma cópia do trecho de referência apresentado anteriormente. Ele segue lógica de análise semelhante, mas é distinto para atender ao requisito de uma abordagem nova. Como sempre, sinta-se à vontade para adaptar nomes de métodos, adicionar tratamento de erros mais robusto ou implementar recursos especializados conforme necessário.
#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__
Vamos ver um exemplo de uso em um 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()); } } //+------------------------------------------------------------------+
A saída esperada é autoexplicativa. Sinta-se à vontade para testá-la.
Conclusão
Com esse código final em mãos, você tem tudo o que precisa para analisar, manipular e até gerar JSON diretamente no MetaTrader 5:
- Análise de JSON: ParseString() transforma texto bruto em uma hierarquia estruturada de nós.
- Consulta de Dados: GetChild(key) e GetChild(index) permitem navegar facilmente por objetos e arrays.
- Validação: Verifique IsValid() e GetErrorMsg() para saber se a análise foi bem-sucedida ou se houve problemas (como chaves não correspondentes).
- Serialização: ToJsonString() reconstrói o nó (e seus filhos) de volta para texto JSON válido.
Sinta-se à vontade para adaptar essa biblioteca às suas necessidades específicas. Você pode, por exemplo, adicionar relatórios de erro mais completos, conversões numéricas especializadas ou capacidades de streaming para conjuntos de dados muito grandes. Mas a base apresentada aqui deve ser suficiente para a maioria dos casos de uso típicos, como leitura de parâmetros de um arquivo ou interação com APIs baseadas na web.
É isso! Você chegou ao final do nosso mergulho profundo no tratamento de JSON em MQL5. Seja implementando um mecanismo de trading complexo orientado por dados ou apenas carregando parâmetros de configuração de um arquivo local, um parser e serializer JSON confiável pode facilitar muito sua vida. Esperamos que este artigo (e o código apresentado) ajude você a integrar JSON de forma fluida aos seus fluxos de trading automatizado.
Boa Programação! Bons trades!
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/16791
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Técnicas do MQL5 Wizard que você deve conhecer (Parte 54): Aprendizado por Reforço com SAC híbrido e Tensores
Automatizando Estratégias de Trading em MQL5 (Parte 6): Dominando a Detecção de Order Blocks para Trading com Smart Money
Redes neurais em trading: Framework de previsão cross-domain de séries temporais (Conclusão)
Desenvolvimento do Kit de Ferramentas de Análise de Ação de Preço (Parte 12): Fluxo Externo (III) Mapa de Tendências
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Você poderia postar a cadeia de caracteres ou o arquivo json, por favor?
https://fapi.binance.com/fapi/v1/exchangeInfo
https://eapi.binance.com/eapi/v1/exchangeInfo
778 KB (796.729 bytes).
Você poderia publicar a cadeia de caracteres ou o arquivo json, por favor?
h ttps:// www.mql5.com/ru/code/68596
Fórum sobre negociação, sistemas de negociação automatizados e teste de estratégias de negociação
Bibliotecas: MQL4/5-JsonLib
Alain Verleyen, 2025.12.20 22:49
2025.12.20 17:46:39.187 toyjson_bench (EURUSD,H1) Deserialise 107652093 bytes JSON in 1101111 microseconds.
2025.12.20 17:46:39.187 toyjson_bench (EURUSD,H1) Check value = "Richard Sullivan"
2025.12.20 17:46:39.187 toyjson_bench (EURUSD,H1) Terminal memory used = 2738, MQL memory used = 2493 MB