JSON 从入门到精通: 创建自己的 MQL5 版本 JSON 解读器·综合运用
🧩

JSON 从入门到精通: 创建自己的 MQL5 版本 JSON 解读器·综合运用

(3/3)· 当标准库缺席,如何用九步在 MT5 里造出能读能改的 JSON 引擎

含代码示例实战向 第 3/3 篇
多数 MQL5 开发者遇到 Web API 返回的 JSON 就卡在字符串截取,手写正则迟早漏掉嵌套括号。本篇把解析做成类结构,让你直接按键取值而不是数逗号。

「JSON 节点的取值与解析入口」

在 MT5 里做 AIGC 行情接口对接时,CJsonNode 这类封装决定了你从返回报文里能掏出什么。AsNumber 方法只认 JSON_NUMBER 类型,若节点是布尔则返 1.0 或 0.0,其余一律 0.0——这意味着乱用类型转换会静默吃掉你的数据。 AsBool 的兜底逻辑更野:数字非零即真,字符串长度大于 0 也为真。如果你拿它去判一个空数组节点,这里不会返回真,因为类型既不是 BOOL、NUMBER 也不是 STRING,直接落到了 false。 Reset 把节点回到 JSON_UNDEF 初始态,顺手用 ArrayResize(m_children,0) 和 ArrayResize(m_keys,0) 清空子节点与键表。每次重新解析前不清 Reset,旧 children 可能残留导致结构错乱。 ParseValue 是分发器:先 SkipWhitespace 跳过空白,越界直接返 false;再看首字符——'{' 走对象、'[' 走数组、'"' 走字符串,遇到子串 'true' 就原地置布尔真并 pos+=4。注意它只处理了 true,false 和 null 在同节未列,实际解析需确认后续分支。

MQL5 / C++
class="type">class="kw">double CJsonNode::AsNumber()
  {
   if(m_type == JSON_NUMBER) class="kw">return m_numVal;
   class=class="str">"cmt">// If class="type">bool, class="kw">return class="num">1 or class="num">0
   if(m_type == JSON_BOOL)  class="kw">return (m_boolVal ? class="num">1.0 : class="num">0.0);
   class="kw">return class="num">0.0;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Convert node to boolean                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CJsonNode::AsBool()
  {
   if(m_type == JSON_BOOL)  class="kw">return m_boolVal;
   if(m_type == JSON_NUMBER) class="kw">return (m_numVal != class="num">0.0);
   if(m_type == JSON_STRING) class="kw">return (StringLen(m_value) > class="num">0);
   class="kw">return false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Serialize node back to JSON                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CJsonNode::ToJsonString()
  {
   class="kw">return SerializeNode();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Reset node to initial state                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CJsonNode::Reset()
  {
   m_type    = JSON_UNDEF;
   m_value   = "";
   m_numVal  = class="num">0.0;
   m_boolVal = false;
   m_valid   = true;
   ArrayResize(m_children,class="num">0);
   ArrayResize(m_keys,class="num">0);
   m_errMsg  = "";
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Dispatch parse based on first character                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CJsonNode::ParseValue(class="type">class="kw">string text,class="type">int &pos)
  {
   if(!SkipWhitespace(text,pos)) class="kw">return false;
   if(pos >= StringLen(text))    class="kw">return false;
   class="type">class="kw">string c = StringSubstr(text,pos,class="num">1);
   class=class="str">"cmt">//--- Object
   if(c == "{")
      class="kw">return ParseObject(text,pos);
   class=class="str">"cmt">//--- Array
   if(c == "[")
      class="kw">return ParseArray(text,pos);
   class=class="str">"cmt">//--- String
   if(c == "\"")
      class="kw">return ParseStringLiteral(text,pos);
   class=class="str">"cmt">//--- Boolean / null
   if(StringSubstr(text,pos,class="num">4) == "true")
     {
      m_type    = JSON_BOOL;
      m_boolVal = true;
      pos       += class="num">4;
      class="kw">return true;
     }

◍ JSON 对象节点怎么在 MT5 里逐字符啃下来

在自写的 CJsonNode 类里,ParseObject 负责把 {...} 这一段从字符串里抠成树节点。它先吃掉开头的 { 并后移 pos,再调 SkipWhitespace 跳过空格;若下一个字符就是 },直接 pos++ 返回,说明是个空对象,不建任何子节点。 非空对象走 while 循环:每轮先跳过空白,然后强制要求键以双引号开头,否则把 m_valid 置 false 并报 Object key must start with double quote.。键名由 ParseKeyLiteral 读出,随后必须出现冒号,缺失就抛 Missing colon after object key.——这两个报错信息在调试 EA 接收的 WebRequest 回包时很实用。 冒号跳过后再跳过空白,用局部 CJsonNode child 调 ParseValue 递归解析值;只要子值解析失败,本层就标记 Failed to parse object value. 并退出。整段没有用正则,纯靠 StringSubstr(text,pos,1) 做单字符比对,在 30 KB 以内的 JSON 回包上开销可忽略,但你拿去解析超长 tick 流时要留意 StringLen 的反复调用次数。 别把正态当圣经 这套手写解析若遇到键里带转义双引号,得确认 ParseKeyLiteral 自己处理了反斜杠,否则 pos 会错位到值解析阶段才爆错,排错成本翻倍。

MQL5 / C++
class="type">bool CJsonNode::ParseObject(class="type">class="kw">string text,class="type">int &pos)
  {
  m_type = JSON_OBJ;
  pos++; class=class="str">"cmt">// skip &class="macro">#x27;{&class="macro">#x27;
  if(!SkipWhitespace(text,pos)) class="kw">return false;
  class=class="str">"cmt">//--- Check for empty object
  if(pos < StringLen(text) && StringSubstr(text,pos,class="num">1) == "}")
    {
    pos++;
    class="kw">return true;
    }
  class=class="str">"cmt">//--- Parse key-value pairs
  class="kw">while(pos < StringLen(text))
    {
    if(!SkipWhitespace(text,pos)) class="kw">return false;
    class=class="str">"cmt">// Expect key in quotes
    if(pos >= StringLen(text) || StringSubstr(text,pos,class="num">1) != "\"")
      {
      m_valid  = false;
      m_errMsg = "Object key must start with class="type">class="kw">double quote.";
      class="kw">return false;
      }
    class="type">class="kw">string key = "";
    if(!ParseKeyLiteral(text,pos,key))
      class="kw">return false;
    if(!SkipWhitespace(text,pos)) class="kw">return false;
    class=class="str">"cmt">// Expect a colon
    if(pos >= StringLen(text) || StringSubstr(text,pos,class="num">1) != ":")
      {
      m_valid  = false;
      m_errMsg = "Missing colon after object key.";
      class="kw">return false;
      }
    pos++; class=class="str">"cmt">// skip &class="macro">#x27;:&class="macro">#x27;
    if(!SkipWhitespace(text,pos)) class="kw">return false;
    class=class="str">"cmt">// Parse the child value
    CJsonNode child;
    if(!child.ParseValue(text,pos))
      {
      m_valid  = false;
      m_errMsg = "Failed to parse object value.";
      class="kw">return false;
      }

对象与数组的节点挂载差异

在 JSON 解析器里,对象节点和数组节点都靠同一个 m_children 动态数组挂子项,但键的处理完全不同。对象每挂一个子节点,会同步在 m_keys 里存一个 key,数组则只压 child、m_keys 保持空位。 下面这段是对象写入子节点的核心片段:先取当前数组长度 idx,把 m_children 和 m_keys 都扩容 1,再按 idx 写入 child 与 key。随后跳空白、判结尾——遇到 '}' 直接 pos++ 返回 true,遇到非 ',' 且非 '}' 就置 m_valid=false 并报 Missing comma in object。

MQL5 / C++
class=class="str">"cmt">// Store
class="type">int idx = ArraySize(m_children);
ArrayResize(m_children,idx+class="num">1);
ArrayResize(m_keys,idx+class="num">1);
m_children[idx] = child;
m_keys[idx]     = key;
if(!SkipWhitespace(text,pos)) class="kw">return false;
if(pos >= StringLen(text))      class="kw">return false;
class="type">class="kw">string nextC = StringSubstr(text,pos,class="num">1);
if(nextC == "}")
  {
   pos++;
   class="kw">return true;
  }
if(nextC != ",")
  {
   m_valid  = false;
   m_errMsg = "Missing comma in object.";
   class="kw">return false;
  }
pos++; class=class="str">"cmt">// skip comma
}
class="kw">return false; class=class="str">"cmt">// didn&class="macro">#x27;t see closing &class="macro">#x27;}&class="macro">#x27;
数组解析逻辑结构一致,但首行就 m_type=JSON_ARRAY,且空数组用 StringSubstr(text,pos,1)=="]" 提前返回,不进循环。元素解析失败时报的是 Failed to parse array element,而不是对象里的缺逗号错误。 实跑验证点:丢一段 {"a":1,"b":2} 给 ParseObject,m_children 大小应为 2、m_keys[1] 内容是 "b";丢 [1,2,] 这种尾逗号非法串,会卡在 nextC!=',' 分支、m_valid 变 false。外汇/贵金属 EA 里接 REST 行情若用这套解析,非法 JSON 可能让策略静默停摆,属高风险环节。

MQL5 / C++
class=class="str">"cmt">// Store
class="type">int idx = ArraySize(m_children);
ArrayResize(m_children,idx+class="num">1);
ArrayResize(m_keys,idx+class="num">1);
m_children[idx] = child;
m_keys[idx]     = key;
if(!SkipWhitespace(text,pos)) class="kw">return false;
if(pos >= StringLen(text))      class="kw">return false;
class="type">class="kw">string nextC = StringSubstr(text,pos,class="num">1);
if(nextC == "}")
  {
   pos++;
   class="kw">return true;
  }
if(nextC != ",")
  {
   m_valid  = false;
   m_errMsg = "Missing comma in object.";
   class="kw">return false;
  }
pos++; class=class="str">"cmt">// skip comma
}
class="kw">return false; class=class="str">"cmt">// didn&class="macro">#x27;t see closing &class="macro">#x27;}&class="macro">#x27;

「数值与字符串的逐字符解析逻辑」

在自写 JSON 解析类里,ParseNumber 靠一个白名单循环吃字符:只要碰到 -、+、.、e、E 或 0–9 就继续推进 pos,否则立即 break。实测若传入 "12.5E-3" 这类字符串,循环会完整吞掉 7 个字符再交給 StringToDouble,转换后得到 double 值 0.0125,类型标记为 JSON_NUMBER。 ParseStringLiteral 假设调用前已确认首字符是双引号,进门先 pos++ 跳过它。随后逐字符扫描:遇到 " 就闭合、把累积结果丢给 UnescapeString 解码并返回 true;遇到 \ 则把反斜杠和下一个字符原样拼进 result,留待后续统一反转义。若文本在反斜杠后直接截断(pos 越界),循环 break 且不会返回 true,调用方需自行判 m_valid。 两个函数都直接修改入参 int &pos,这意味着上层解析器可以靠同一个下标变量在 text 上滑动,不必每层重新计算偏移。开 MT5 把这段塞进 EA 的 include 里,用 Print(ParseNumber("12.5E-3",pos)) 验证返回值,能直观看到浮点解析路径是否符合预期。

MQL5 / C++
class="type">bool CJsonNode::ParseNumber(class="type">class="kw">string text,class="type">int &pos)
  {
  m_type = JSON_NUMBER;
  class="type">int startPos = pos;
  class=class="str">"cmt">// Scan allowed chars in a JSON number
  class="kw">while(pos < StringLen(text))
    {
    class="type">class="kw">string c = StringSubstr(text,pos,class="num">1);
    if(c=="-" || c=="+" || c=="." || c=="e" || c=="E" || (c>="class="num">0" && c<="class="num">9"))
      pos++;
    else
      break;
    }
  class="type">class="kw">string numStr = StringSubstr(text,startPos,pos - startPos);
  if(StringLen(numStr) == class="num">0)
    {
    m_valid  = false;
    m_errMsg = "Expected number, found empty.";
    class="kw">return false;
    }
  m_numVal = StringToDouble(numStr);
  class="kw">return true;
  }

class="type">bool CJsonNode::ParseStringLiteral(class="type">class="kw">string text,class="type">int &pos)
  {
  pos++;  class=class="str">"cmt">// skip leading quote
  class="type">class="kw">string result = "";
  class="kw">while(pos < StringLen(text))
    {
    class="type">class="kw">string c = StringSubstr(text,pos,class="num">1);
    if(c == "\"")
      {
      class=class="str">"cmt">// closing quote
      pos++;
      m_type  = JSON_STRING;
      m_value = UnescapeString(result);
      class="kw">return true;
      }
    if(c == "\\")
      {
      class=class="str">"cmt">// handle escape
      pos++;
      if(pos >= StringLen(text))
        break;
      class="type">class="kw">string ec = StringSubstr(text,pos,class="num">1);
      result += ("\\" + ec); class=class="str">"cmt">// accumulate, we&class="macro">#x27;ll decode later
      pos++;
      }
    else
      {
      result += c;

◍ JSON 键名与转义字符的解析落点

在 MT5 里手写 JSON 解析器时,键名(key)的提取逻辑和字符串字面量几乎同构,差别只在用途:ParseKeyLiteral 跳过前导双引号后,用 buffer 逐字符拼接,遇到 " 即闭合并返回 UnescapeString 处理后的 keyOut。若遍历到文本末尾仍未碰到闭合引号,则置 m_valid=false 并抛出 "Unclosed key string.",此时上层调用方应当终止解析。 转义处理集中在 UnescapeString:遇到 \ 时下标 i 自增,若已越界则补一个裸反斜杠并 break;否则取紧随其后的字符 ec,目前仅对 " 与 \ 做还原映射(即 out += "\" 或 out += "\\")。这意味着 \n、\t 这类序列在该实现里不会被展开成换行或制表符,若你的 EA 配置含此类字符,读取后可能仍是字面量。 直接验证方式:把上面两段代码塞进 CJsonNode 类,构造一个含未闭合键名的 string 如 {"key: 1},调用 ParseKeyLiteral 后应拿到 m_valid=false;再喂入 "a\\b" 做 UnescapeString,返回串长度应为 3 而非 4。外汇与贵金属 EA 加载外部 JSON 配置属高风险操作,解析失败静默继续可能让仓位参数失真。

MQL5 / C++
  pos++;
    }
  }
  class=class="str">"cmt">// If we get here, class="type">class="kw">string was not closed
  m_valid  = false;
  m_errMsg = "Unclosed class="type">class="kw">string literal.";
  class="kw">return false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Parse a class="type">class="kw">string key(similar to a literal)                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CJsonNode::ParseKeyLiteral(class="type">class="kw">string text,class="type">int &pos,class="type">class="kw">string &keyOut)
  {
  pos++;  class=class="str">"cmt">// skip leading quote
  class="type">class="kw">string buffer = "";
  class="kw">while(pos < StringLen(text))
    {
    class="type">class="kw">string c = StringSubstr(text,pos,class="num">1);
    if(c == "\"")
      {
      pos++;
      keyOut = UnescapeString(buffer);
      class="kw">return true;
      }
    if(c == "\\")
      {
      pos++;
      if(pos >= StringLen(text))
        break;
      class="type">class="kw">string ec = StringSubstr(text,pos,class="num">1);
      buffer += ("\\" + ec);
      pos++;
      }
    else
      {
      buffer += c;
      pos++;
      }
    }
  m_valid  = false;
  m_errMsg = "Unclosed key class="type">class="kw">string.";
  class="kw">return false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Unescape sequences like \" \\ \n etc.                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CJsonNode::UnescapeString(class="type">class="kw">string input_)
  {
  class="type">class="kw">string out = "";
  class="type">int i       = class="num">0;
  class="kw">while(i < StringLen(input_))
    {
    class="type">class="kw">string c = StringSubstr(input_,i,class="num">1);
    if(c == "\\")
      {
      i++;
      if(i >= StringLen(input_))
        {
        class=class="str">"cmt">// Single backslash at end
        out += "\\";
        break;
        }
      class="type">class="kw">string ec = StringSubstr(input_,i,class="num">1);
      if(ec == "\"")       out += "\"";
      else if(ec == "\\") out += "\\";

转义字符与空白跳过的底层处理

在 MQL5 里手写 JSON 解析器时,反斜杠后的字符必须逐一映射为控制符:n 转换行、r 转回车、t 转制表,b 和 f 则分别用 CharToString(8) 与 CharToString(12) 补上 ASCII 退格和换页。若反斜杠后不是预定字母,就原样拼回 "\"+ec,避免误吞非法转义。 SkipWhitespace 从 pos 起扫描,遇到空格(32)、制表(9)、换行(10)、回车(13) 便自增下标,直到撞上非空白或越界;返回 pos<=StringLen(text) 表示尚未彻底超出。AllWhitespace 则反向判定:只要剩余段出现任一非四种空白字符立即返回 false,全段干净才返回 true。 这两段是序列化前置的依赖:SerializeNode 按 m_type 分发到 SerializeObject / SerializeArray,而在递归拼串过程中,跳过空白与校验尾随空白直接决定了解析容错度。实盘跑 EA 前,可在 MetaEditor 里把 SkipWhitespace 单独挂个脚本,喂入带缩进的 JSON 字符串,观察 pos 推进是否符合预期。外汇与贵金属行情高波动,任何配置解析偏差都可能让风控参数静默失效,建议仅用历史回测数据验证此类工具函数。

MQL5 / C++
else if(ec == "n")  out += "\n";
else if(ec == "r")  out += "\r";
else if(ec == "t")  out += "\t";
else if(ec == "b")  out += CharToString(class="num">8);   class=class="str">"cmt">// ASCII backspace
else if(ec == "f")  out += CharToString(class="num">12);  class=class="str">"cmt">// ASCII formfeed
else                 out += ("\\" + ec);
      i++;
      }
   else
      {
      out += c;
      i++;
      }
   }
 class="kw">return out;
 }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Skip whitespace                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CJsonNode::SkipWhitespace(class="type">class="kw">string text,class="type">int &pos)
  {
  class="kw">while(pos < StringLen(text))
    {
    class="type">class="kw">ushort c = StringGetCharacter(text,pos);
    if(c == &class="macro">#x27; &class="macro">#x27; || c == &class="macro">#x27;\t&class="macro">#x27; || c == &class="macro">#x27;\n&class="macro">#x27; || c == &class="macro">#x27;\r&class="macro">#x27;)
      pos++;
    else
      break;
    }
   class=class="str">"cmt">// Return true if we haven&class="macro">#x27;t gone beyond class="type">class="kw">string length
   class="kw">return (pos <= StringLen(text));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check if remainder is all whitespace                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CJsonNode::AllWhitespace(class="type">class="kw">string text,class="type">int pos)
  {
  class="kw">while(pos < StringLen(text))
    {
    class="type">class="kw">ushort c = StringGetCharacter(text,pos);
    if(c != &class="macro">#x27; &class="macro">#x27; && c != &class="macro">#x27;\t&class="macro">#x27; && c != &class="macro">#x27;\n&class="macro">#x27; && c != &class="macro">#x27;\r&class="macro">#x27;)
      class="kw">return false;
    pos++;
    }
  class="kw">return true;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Serialization dispatcher                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CJsonNode::SerializeNode()
  {
  class="kw">switch(m_type)
    {
    case JSON_OBJ:     class="kw">return SerializeObject();
    case JSON_ARRAY:   class="kw">return SerializeArray();

「正文」

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> JSON_STRING: <span class="keyword">return</span> <span class="string">"\""</span>+EscapeString(m_value)+<span class="string">"\""</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> JSON_NUMBER: <span class="keyword">return</span> <span class="functions">DoubleToString</span>(m_numVal,<span class="number">8</span>); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> JSON_BOOL:&nbsp;&nbsp; <span class="keyword">return</span> (m_boolVal ? <span class="string">"true"</span> : <span class="string">"false"</span>); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">case</span> JSON_NULL:&nbsp;&nbsp; <span class="keyword">return</span> <span class="string">"null"</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">default</span>:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="string">"\"\""</span>; <span class="comment">// undefined =&gt; empty string</span> &nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;} <span class="comment">//+------------------------------------------------------------------+</span> <span class="comment">//| Serialize object&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb

◍ 控制字符转义与一段可用的 JSON 解析样例

上面这段 switch 的收尾分支,是在把不可见控制字符翻成 JSON 标准转义串:13 对应回车 \r,9 对应制表 \t,8 是退格 \b,12 是换页 \f;default 分支则直接把普通字符用 CharToString(c) 拼回字符串,不再额外处理。 这段逻辑嵌在 CJsonNode 的序列化出口里,意味着你从 MT5 日志里拷出来的字符串,控制字符已经被显式转义,不会悄悄截断。 下面附的 OnStart 是一段能直接跑的验证代码:用 {"name":"Alice","age":30,"admin":true,"items":[1,2,3],"misc":null} 做输入,ParseString 成功后能分别用 GetChild(...).AsString() / AsNumber() / AsBool() 取字段,再用 ToJsonString() 反向序列化。 在 MT5 里建个脚本把这段代码贴进去,编译跑一次,若日志打出 'JSON parsed successfully!' 和 Name: Alice / Age: 30.0 / Admin?true,就说明你的 CJsonNode.mqh 版本转义与解析链路是通的。外汇与贵金属行情数据走 JSON 接口时,这类解析失败往往源于控制字符未转义,属高风险接入点,建议每次对接新源都先跑一遍该样例。

MQL5 / C++
   case class="num">13:  class=class="str">"cmt">// &class="macro">#x27;\r&class="macro">#x27;
         out += "\\r";
         break;
      case class="num">9:   class=class="str">"cmt">// &class="macro">#x27;\t&class="macro">#x27;
         out += "\\t";
         break;
      case class="num">8:   class=class="str">"cmt">// backspace
         out += "\\b";
         break;
      case class="num">12:  class=class="str">"cmt">// formfeed
         out += "\\f";
         break;
      class="kw">default:
         class=class="str">"cmt">// Directly append character
         out += CharToString(c);
         break;
      }
   }
   class="kw">return out;
}
class="macro">#endif class=class="str">"cmt">// __CJSONNODE_MQH__
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  ProjectName |
class=class="str">"cmt">//|                                       Copyright class="num">2020, CompanyName |
class=class="str">"cmt">//|                                         http://www.companyname.net |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property strict
class="macro">#include <CJsonNode.mqh>
class="type">void OnStart()
  {
   class=class="str">"cmt">// Some JSON text
   class="type">class="kw">string jsonText = "{\"name\":\"Alice\",\"age\":class="num">30,\"admin\":true,\"items\":[class="num">1,class="num">2,class="num">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());
      class=class="str">"cmt">// Serialize back
      Print("Re-serialized JSON: ", parser.ToJsonString());
     }
   else
     {
      Print("JSON parsing error: ", parser.GetErrorMsg());
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

画得少,看得清

把前面几节攒下的 CJsonNode 函数库跑通后,你在 MT5 里已经能直接干四件事:用 ParseString() 把原生文本变成节点树,用 GetChild(key) 或 GetChild(index) 在对象与数组之间来回切,用 CheckIsValid() 和 GetErrorMsg() 抓括号不匹配之类的解析错误,再用 ToJsonString() 把节点重新拼回合法 JSON。 社区里有人拿这个思路做过压力测试:在 EURUSD H1 上解析 107,652,093 字节的 JSON 耗时约 1,101,111 微秒,峰值 MQL 内存吃到 2493 MB——这说明海量行情或交易所 API 回传一旦进 MT5,就得掂量终端内存余量。 真要接 Binance 这类 700KB+ 的 exchangeInfo,建议先在小样本上验证解析路径,再决定要不要上流式处理。外汇和贵金属杠杆高、滑点突变频繁,任何自动读取参数的环节都可能因格式漂移直接断线,上线前务必在策略测试器里跑一遍异常 JSON。 函数库本身留了口子:更细的错误码、数值强转、大文件流式读写都能自己补。基础骨架够覆盖从本地配置文件加载到网页 API 交互的典型场景,剩下的是你按交易逻辑往上叠。

交给小布盯盘看外部数据
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到第三方 JSON 行情的结构化解析结果,把重复劳动交给小布,你专注决策。

常见问题

合理设计递归解析与释放逻辑,控制临时数组作用域,在 MT5 严格类型下可稳定处理中等规模 JSON,超大报文建议分块。
把最大点差、风险比例等写进本地 JSON,解读器在 OnInit 读取并赋值,修改文件重启 EA 即可生效,无需动源码。
可以,小布盯盘品种页已内置结构化解析视图,省去你自己写解读器的步骤,适合快速验证数据字段。
用枚举或专用包装结构承接,避免直接赋给数值变量,否则编译期即报错,解析层做显式分支最稳。