JsonParserByLeo/Test/Other/MinMax.mqh
2026-06-28 21:34:54 -05:00

1578 righe
54 KiB
MQL5

//+------------------------------------------------------------------+
//| MinMax.mqh |
//| Copyright 2026, Niquel Mendoza. |
//| https://www.mql5.com/ |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Niquel Mendoza."
#property link "https://www.mql5.com/"
#property strict
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
/*
Otro modelo de ia descarto creo qeu casi todos estos modelos chinos
Se confunden mucho con c++ como lo dije cosa que gpt\claude ya lo superarn en 2025 por ahi..
Arpatri de ahi los errores ya eran conocimiento no torpes
.. Aqui sigue pasando lo mismo con MinMax 3 .... usa referencias cuando en MQL5
Eso no existe.. (solo paso por funcionfuera de eso no..)
Bueno igual aqui lo dejo..
*/
//+------------------------------------------------------------------+
//| Json.mqh |
//| High-Performance JSON Parser for MQL5 |
//| 100% Pure MQL5 - No DLLs |
//+------------------------------------------------------------------+
//| Features: |
//| - Single-pass iterative parser (no recursion) |
//| - Arena-allocated node array (cache-friendly, contiguous) |
//| - Decoded-string arena (zero-copy access via offsets) |
//| - Manual parse stack with bounded depth |
//| - Branchless fast-path string scan via lookup table |
//| - Cached number/integer/bool values per node |
//| - Zero-copy input: user owns the source buffer |
//| - Full JSON spec: objects, arrays, strings (with all escapes), |
//| numbers (int / frac / exp), true / false / null |
//| - Full UTF-8 / \uXXXX / surrogate-pair support |
//+------------------------------------------------------------------+
//| Usage example: |
//| #include "Json.mqh" |
//| |
//| uchar raw[] = "{\"symbol\":\"EURUSD\",\"bid\":1.0850}"; |
//| CJson js; |
//| ArrayCopy(js.Buffer, raw); // zero-copy, or assign directly |
//| if(js.Parse()) |
//| { |
//| Print(js["symbol"].ToString()); // "EURUSD" |
//| Print(js["bid"].ToDouble()); // 1.0850 |
//| js["ticks"][5].ToDouble(); // chained access |
//| } |
//| else |
//| Print("Error: ", js.GetErrorMessage(), |
//| " at line ", js.GetLine(), |
//| " col ", js.GetColumn()); |
//+------------------------------------------------------------------+
#property copyright "Mavis Agent"
#property version "1.00"
#property strict
#ifndef JSON_MQH_INCLUDED
#define JSON_MQH_INCLUDED
//+------------------------------------------------------------------+
//| Tunable parameters (override with -D flag at compile time) |
//+------------------------------------------------------------------+
#ifndef JSON_MAX_DEPTH
#define JSON_MAX_DEPTH 256
#endif
#ifndef JSON_INITIAL_NODES
#define JSON_INITIAL_NODES 128
#endif
#ifndef JSON_INITIAL_STRINGS
#define JSON_INITIAL_STRINGS 4096
#endif
//+------------------------------------------------------------------+
//| Public JSON node types |
//+------------------------------------------------------------------+
enum ENUM_JSON_TYPE
{
JSON_TYPE_INVALID = 0,
JSON_TYPE_NULL = 1,
JSON_TYPE_BOOL = 2,
JSON_TYPE_NUMBER = 3,
JSON_TYPE_STRING = 4,
JSON_TYPE_ARRAY = 5,
JSON_TYPE_OBJECT = 6
};
//+------------------------------------------------------------------+
//| Public error codes (return value of GetError) |
//+------------------------------------------------------------------+
enum ENUM_JSON_ERROR
{
JSON_OK = 0,
JSON_ERR_EMPTY = -1,
JSON_ERR_UNEXPECTED_CHAR = -2,
JSON_ERR_UNEXPECTED_END = -3,
JSON_ERR_INVALID_NUMBER = -4,
JSON_ERR_INVALID_STRING = -5,
JSON_ERR_INVALID_ESCAPE = -6,
JSON_ERR_INVALID_UNICODE = -7,
JSON_ERR_EXPECTED_COLON = -8,
JSON_ERR_EXPECTED_COMMA = -9,
JSON_ERR_EXPECTED_KEY = -10,
JSON_ERR_EXPECTED_VALUE = -11,
JSON_ERR_MAX_DEPTH = -12,
JSON_ERR_INVALID_TOKEN = -13,
JSON_ERR_TRAILING_DATA = -14,
JSON_ERR_INVALID_ROOT = -15,
JSON_ERR_OUT_OF_MEMORY = -16,
JSON_ERR_INVALID_INDEX = -17
};
//+------------------------------------------------------------------+
//| Internal: parse state machine |
//+------------------------------------------------------------------+
enum ENUM_PARSE_STATE
{
ST_EXPECT_VALUE = 0, // inside array / right after [ / right after :
ST_EXPECT_KEY_OR_END = 1, // inside object, right after { or ,
ST_EXPECT_COLON = 2, // inside object, right after key
ST_EXPECT_VALUE_OR_END = 3, // inside array, right after [ or ,
ST_EXPECT_COMMA_OR_END = 4 // inside any container, right after a value
};
//+------------------------------------------------------------------+
//| Internal: compact node structure |
//| Intentionally kept small to maximize cache friendliness. |
//+------------------------------------------------------------------+
struct JsonNode
{
char type; // ENUM_JSON_TYPE
uchar flags; // bit 0: has cached number, bit 1: has cached int
int parent; // parent index (-1 for root)
int next; // next sibling index (-1 if none)
int child; // first child index (-1 if none)
int name_off; // offset of key in Strings[] (-1 if none)
int name_len; // length of key in bytes
int val_off; // offset of value in Strings[] (or in Buffer for raw numbers)
int val_len; // length of value
double num_value; // cached numeric value (numbers)
long int_value; // cached integer value (numbers)
bool bool_value; // cached bool value
};
//+------------------------------------------------------------------+
//| Internal: parse stack frame |
//+------------------------------------------------------------------+
struct JsonFrame
{
int parent_node; // index of container node in Nodes[]
int last_child; // last child appended (for O(1) sibling link)
char state; // ENUM_PARSE_STATE
char container; // JSON_TYPE_ARRAY or JSON_TYPE_OBJECT
};
//+------------------------------------------------------------------+
//| Forward declarations |
//+------------------------------------------------------------------+
class CJson;
class CJsonNode;
//+------------------------------------------------------------------+
//| Branchless lookup table for the hot string scan loop. |
//| Cells with value 1 force the loop to stop. |
//+------------------------------------------------------------------+
uchar g_json_qtbl[256];
bool g_json_qtbl_ready = false;
//+------------------------------------------------------------------+
//| One-time table initialization. |
//+------------------------------------------------------------------+
void JsonInitTables()
{
if(g_json_qtbl_ready)
return;
ArrayInitialize(g_json_qtbl, 0);
g_json_qtbl['"'] = 1;
g_json_qtbl['\\'] = 1;
g_json_qtbl_ready = true;
}
//+------------------------------------------------------------------+
//| CJsonNode - non-owning view of a node inside a CJson document. |
//| Supports chainable operator[]. |
//+------------------------------------------------------------------+
class CJsonNode
{
private:
CJson *m_owner; // owning CJson (must outlive the view)
int m_index; // node index in m_owner.Nodes[] (-1 = invalid)
public:
CJsonNode(): m_owner(NULL), m_index(-1) {}
CJsonNode(CJson *owner, int index): m_owner(owner), m_index(index) {}
// Validity / type queries
bool IsValid() const;
bool IsObject() const;
bool IsArray() const;
bool IsString() const;
bool IsNumber() const;
bool IsBool() const;
bool IsNull() const;
ENUM_JSON_TYPE GetType() const;
// Container queries
int Size() const;
int Count() const { return Size(); }
bool Empty() const;
bool Exists(const string key) const;
// Value accessors (with default values)
string ToString(const string def = "") const;
double ToDouble(const double def = 0.0) const;
long ToInteger(const long def = 0) const;
bool ToBool(const bool def = false) const;
// Key / parent / siblings
string Key() const;
CJsonNode Parent() const;
CJsonNode FirstChild() const;
CJsonNode NextSibling() const;
CJsonNode GetChild(int i) const;
// Chainable navigation
CJsonNode operator[](const string key) const;
CJsonNode operator[](int index) const;
// Internal access (used by CJson)
int RawIndex() const { return m_index; }
};
//+------------------------------------------------------------------+
//| CJson - high-performance JSON parser / DOM. |
//+------------------------------------------------------------------+
class CJson
{
public:
//--- Public input buffer; the user fills this directly (zero copy).
//--- The parser does NOT free Buffer - the caller owns its lifetime.
//--- Use ArrayFree(json.Buffer) explicitly if the buffer was created
//--- locally and is no longer needed.
uchar Buffer[];
//--- Construction / destruction
CJson();
~CJson();
//--- Main API
bool Parse();
void Clear(); // release all storage
void Reset(); // reinitialize for reuse (keeps allocations)
//--- Error reporting
bool HasError() const { return m_last_error != JSON_OK; }
int GetError() const { return (int)m_last_error; }
string GetErrorMessage() const;
int GetErrorPosition()const { return m_error_pos; }
int GetLine() const { return m_error_line; }
int GetColumn() const { return m_error_col; }
//--- Root type queries
bool IsObject() const;
bool IsArray() const;
bool IsString() const;
bool IsNumber() const;
bool IsBool() const;
bool IsNull() const;
ENUM_JSON_TYPE GetType() const;
//--- Root container queries
int Size() const;
int Count() const { return Size(); }
bool Exists(const string key) const;
//--- Root value accessors
string ToString(const string def = "") const;
double ToDouble(const double def = 0.0) const;
long ToInteger(const long def = 0) const;
bool ToBool(const bool def = false) const;
//--- Root navigation
CJsonNode operator[](const string key);
CJsonNode operator[](int index);
CJsonNode GetRoot() { return CJsonNode((CJson*)GetPointer(this), m_root); }
//--- Statistics (for diagnostics / benchmarks)
int NodeCount() const { return m_nodes_count; }
int StringBytes() const { return m_strings_len; }
int CapacityNodes() const { return m_nodes_cap; }
private:
//--- Node arena (flat, cache-friendly)
JsonNode m_nodes[];
int m_nodes_count;
int m_nodes_cap;
//--- Decoded-string arena
uchar m_strings[];
int m_strings_len;
int m_strings_cap;
//--- Parse state
int m_root;
int m_last_error;
int m_error_pos;
int m_error_line;
int m_error_col;
int m_pos;
int m_line;
int m_col;
//--- Internal helpers
int AllocNode();
bool ReserveNodes(int need);
bool ReserveStrings(int need);
int AppendString(const uchar &src[], int off, int len);
int AppendChar(uchar b);
int AppendCharAt(int pos, uchar b);
int AddChild(int parent, int prev_sibling, char type);
int SkipWs(int p);
int ParseValueInto(int node);
int ParseStringAt(int start, int &out_off, int &out_len);
int ParseNumberAt(int start, double &out_num, long &out_int, int &out_len);
int ParseLiteralAt(int start, const uchar &lit[], int lit_len);
int DecodeHex4(int p, int &codepoint);
int EncodeUtf8(int codepoint, int dst_pos);
bool SetError(int err, int pos);
int NodeFindKey(int parent, const string key) const;
bool KeyMatch(const JsonNode &cn, const string key) const;
//--- Friends
friend class CJsonNode;
};
//+------------------------------------------------------------------+
//| CJsonNode - implementation |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Check whether this view is valid (refers to a real node) |
//+------------------------------------------------------------------+
bool CJsonNode::IsValid() const
{
return (m_owner != NULL && m_index >= 0 && m_index < m_owner.m_nodes_count);
}
//+------------------------------------------------------------------+
//| Type queries |
//+------------------------------------------------------------------+
ENUM_JSON_TYPE CJsonNode::GetType() const
{
if(!IsValid())
return JSON_TYPE_INVALID;
return (ENUM_JSON_TYPE)m_owner.m_nodes[m_index].type;
}
bool CJsonNode::IsObject() const { return GetType() == JSON_TYPE_OBJECT; }
bool CJsonNode::IsArray() const { return GetType() == JSON_TYPE_ARRAY; }
bool CJsonNode::IsString() const { return GetType() == JSON_TYPE_STRING; }
bool CJsonNode::IsNumber() const { return GetType() == JSON_TYPE_NUMBER; }
bool CJsonNode::IsBool() const { return GetType() == JSON_TYPE_BOOL; }
bool CJsonNode::IsNull() const { return GetType() == JSON_TYPE_NULL; }
//+------------------------------------------------------------------+
//| Number of children (objects/arrays) or 1 (scalars) |
//+------------------------------------------------------------------+
int CJsonNode::Size() const
{
if(!IsValid())
return 0;
int c = m_owner.m_nodes[m_index].child;
if(c < 0)
return 0;
int n = 0;
while(c >= 0)
{
n++;
c = m_owner.m_nodes[c].next;
}
return n;
}
bool CJsonNode::Empty() const { return Size() == 0; }
//+------------------------------------------------------------------+
//| Check existence of a key in an object |
//+------------------------------------------------------------------+
bool CJsonNode::Exists(const string key) const
{
if(!IsValid() || m_owner.m_nodes[m_index].type != JSON_TYPE_OBJECT)
return false;
return m_owner.NodeFindKey(m_index, key) >= 0;
}
//+------------------------------------------------------------------+
//| Return the key (object context) or "" (array/root context) |
//+------------------------------------------------------------------+
string CJsonNode::Key() const
{
if(!IsValid())
return "";
const JsonNode &n = m_owner.m_nodes[m_index];
if(n.name_off < 0 || n.name_len <= 0)
return "";
return CharArrayToString(m_owner.m_strings, n.name_off, n.name_len, CP_UTF8);
}
//+------------------------------------------------------------------+
//| Parent / sibling navigation |
//+------------------------------------------------------------------+
CJsonNode CJsonNode::Parent() const
{
if(!IsValid())
return CJsonNode();
int p = m_owner.m_nodes[m_index].parent;
if(p < 0)
return CJsonNode();
return CJsonNode(m_owner, p);
}
CJsonNode CJsonNode::FirstChild() const
{
if(!IsValid())
return CJsonNode();
int c = m_owner.m_nodes[m_index].child;
if(c < 0)
return CJsonNode();
return CJsonNode(m_owner, c);
}
CJsonNode CJsonNode::NextSibling() const
{
if(!IsValid())
return CJsonNode();
int n = m_owner.m_nodes[m_index].next;
if(n < 0)
return CJsonNode();
return CJsonNode(m_owner, n);
}
CJsonNode CJsonNode::GetChild(int i) const
{
if(!IsValid() || i < 0)
return CJsonNode();
int c = m_owner.m_nodes[m_index].child;
while(c >= 0 && i > 0)
{
c = m_owner.m_nodes[c].next;
i--;
}
if(c < 0)
return CJsonNode();
return CJsonNode(m_owner, c);
}
//+------------------------------------------------------------------+
//| Get string value. For numbers, returns the raw source text. |
//+------------------------------------------------------------------+
string CJsonNode::ToString(const string def) const
{
if(!IsValid())
return def;
const JsonNode &n = m_owner.m_nodes[m_index];
if(n.type == JSON_TYPE_STRING)
{
if(n.val_len <= 0)
return "";
return CharArrayToString(m_owner.m_strings, n.val_off, n.val_len, CP_UTF8);
}
if(n.type == JSON_TYPE_NUMBER)
{
if(n.val_len <= 0)
return def;
return CharArrayToString(m_owner.Buffer, n.val_off, n.val_len);
}
return def;
}
//+------------------------------------------------------------------+
//| Get double value |
//+------------------------------------------------------------------+
double CJsonNode::ToDouble(const double def) const
{
if(!IsValid())
return def;
const JsonNode &n = m_owner.m_nodes[m_index];
if(n.type != JSON_TYPE_NUMBER)
return def;
return n.num_value;
}
//+------------------------------------------------------------------+
//| Get integer value |
//+------------------------------------------------------------------+
long CJsonNode::ToInteger(const long def) const
{
if(!IsValid())
return def;
const JsonNode &n = m_owner.m_nodes[m_index];
if(n.type != JSON_TYPE_NUMBER)
return def;
return n.int_value;
}
//+------------------------------------------------------------------+
//| Get bool value |
//+------------------------------------------------------------------+
bool CJsonNode::ToBool(const bool def) const
{
if(!IsValid())
return def;
const JsonNode &n = m_owner.m_nodes[m_index];
if(n.type != JSON_TYPE_BOOL)
return def;
return n.bool_value;
}
//+------------------------------------------------------------------+
//| operator[] by key (object) |
//+------------------------------------------------------------------+
CJsonNode CJsonNode::operator[](const string key) const
{
if(!IsValid() || m_owner.m_nodes[m_index].type != JSON_TYPE_OBJECT)
return CJsonNode();
int found = m_owner.NodeFindKey(m_index, key);
if(found < 0)
return CJsonNode();
return CJsonNode(m_owner, found);
}
//+------------------------------------------------------------------+
//| operator[] by index (array) |
//+------------------------------------------------------------------+
CJsonNode CJsonNode::operator[](int index) const
{
if(!IsValid() || m_owner.m_nodes[m_index].type != JSON_TYPE_ARRAY || index < 0)
return CJsonNode();
int c = m_owner.m_nodes[m_index].child;
while(c >= 0 && index > 0)
{
c = m_owner.m_nodes[c].next;
index--;
}
if(c < 0)
return CJsonNode();
return CJsonNode(m_owner, c);
}
//+------------------------------------------------------------------+
//| CJson - implementation |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CJson::CJson()
: m_nodes_count(0),
m_nodes_cap(0),
m_strings_len(0),
m_strings_cap(0),
m_root(-1),
m_last_error(JSON_OK),
m_error_pos(0),
m_error_line(0),
m_error_col(0),
m_pos(0),
m_line(1),
m_col(1)
{
JsonInitTables();
ReserveNodes(JSON_INITIAL_NODES);
ReserveStrings(JSON_INITIAL_STRINGS);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CJson::~CJson()
{
Clear();
}
//+------------------------------------------------------------------+
//| Release all storage |
//| Note: Buffer is the user-owned input buffer and is never freed |
//| by the parser. Use ArrayFree(json.Buffer) explicitly if needed. |
//+------------------------------------------------------------------+
void CJson::Clear()
{
ArrayFree(m_nodes);
ArrayFree(m_strings);
m_nodes_count = 0;
m_nodes_cap = 0;
m_strings_len = 0;
m_strings_cap = 0;
m_root = -1;
m_last_error = JSON_OK;
m_error_pos = 0;
m_error_line = 0;
m_error_col = 0;
m_pos = 0;
m_line = 1;
m_col = 1;
}
//+------------------------------------------------------------------+
//| Reinitialize but keep allocations |
//+------------------------------------------------------------------+
void CJson::Reset()
{
m_nodes_count = 0;
m_strings_len = 0;
m_root = -1;
m_last_error = JSON_OK;
m_error_pos = 0;
m_error_line = 0;
m_error_col = 0;
m_pos = 0;
m_line = 1;
m_col = 1;
// Do not release Buffer; user owns it.
}
//+------------------------------------------------------------------+
//| Reserve capacity in the node arena |
//+------------------------------------------------------------------+
bool CJson::ReserveNodes(int need)
{
if(need <= m_nodes_cap)
return true;
int new_cap = (m_nodes_cap == 0) ? JSON_INITIAL_NODES : m_nodes_cap;
while(new_cap < need) new_cap *= 2;
if(ArraySize(m_nodes) >= new_cap)
{
m_nodes_cap = ArraySize(m_nodes);
return true;
}
ArrayResize(m_nodes, new_cap);
m_nodes_cap = ArraySize(m_nodes);
return (m_nodes_cap >= need);
}
//+------------------------------------------------------------------+
//| Reserve capacity in the string arena |
//+------------------------------------------------------------------+
bool CJson::ReserveStrings(int need)
{
if(need <= m_strings_cap)
return true;
int new_cap = (m_strings_cap == 0) ? JSON_INITIAL_STRINGS : m_strings_cap;
while(new_cap < need) new_cap *= 2;
if(ArraySize(m_strings) >= new_cap)
{
m_strings_cap = ArraySize(m_strings);
return true;
}
ArrayResize(m_strings, new_cap);
m_strings_cap = ArraySize(m_strings);
return (m_strings_cap >= need);
}
//+------------------------------------------------------------------+
//| Allocate one node from the arena |
//+------------------------------------------------------------------+
int CJson::AllocNode()
{
if(!ReserveNodes(m_nodes_count + 1))
return -1;
int idx = m_nodes_count++;
JsonNode &n = m_nodes[idx];
n.type = JSON_TYPE_INVALID;
n.flags = 0;
n.parent = -1;
n.next = -1;
n.child = -1;
n.name_off = -1;
n.name_len = 0;
n.val_off = -1;
n.val_len = 0;
n.num_value = 0.0;
n.int_value = 0;
n.bool_value= false;
return idx;
}
//+------------------------------------------------------------------+
//| Append bytes to the string arena |
//+------------------------------------------------------------------+
int CJson::AppendString(const uchar &src[], int off, int len)
{
if(len <= 0)
return m_strings_len;
if(!ReserveStrings(m_strings_len + len))
return -1;
int dst = m_strings_len;
for(int i = 0; i < len; i++)
m_strings[dst + i] = src[off + i];
m_strings_len = dst + len;
return dst;
}
//+------------------------------------------------------------------+
//| Append a single byte to the string arena |
//+------------------------------------------------------------------+
int CJson::AppendChar(uchar b)
{
if(!ReserveStrings(m_strings_len + 1))
return -1;
int dst = m_strings_len;
m_strings[dst] = b;
m_strings_len = dst + 1;
return dst;
}
//+------------------------------------------------------------------+
//| Overwrite a single byte in the string arena |
//+------------------------------------------------------------------+
int CJson::AppendCharAt(int pos, uchar b)
{
if(pos < 0 || pos >= m_strings_len)
return -1;
m_strings[pos] = b;
return pos;
}
//+------------------------------------------------------------------+
//| Append a child to a container node. |
//| prev_sibling is the tail of the sibling chain (-1 for first). |
//+------------------------------------------------------------------+
int CJson::AddChild(int parent, int prev_sibling, char type)
{
int child = AllocNode();
if(child < 0)
return -1;
JsonNode &c = m_nodes[child];
c.type = type;
c.parent = parent;
c.next = -1;
if(prev_sibling < 0)
m_nodes[parent].child = child;
else
m_nodes[prev_sibling].next = child;
return child;
}
//+------------------------------------------------------------------+
//| Skip ASCII whitespace. |
//| Line / column are computed lazily on error in SetError. |
//+------------------------------------------------------------------+
int CJson::SkipWs(int p)
{
const int len = ArraySize(Buffer);
while(p < len)
{
uchar c = Buffer[p];
if(c == ' ' || c == '\t') { p++; continue; }
if(c == '\n') { p++; continue; }
if(c == '\r')
{
p++;
if(p < len && Buffer[p] == '\n') p++;
continue;
}
break;
}
return p;
}
//+------------------------------------------------------------------+
//| Set the error and return a code |
//+------------------------------------------------------------------+
bool CJson::SetError(int err, int pos)
{
m_last_error = err;
m_error_pos = pos;
m_error_line = 1;
m_error_col = 1;
// Compute line/column by scanning from start to pos.
for(int i = 0; i < pos && i < ArraySize(Buffer); i++)
{
uchar c = Buffer[i];
if(c == '\n')
{
m_error_line++;
m_error_col = 1;
}
else if(c != '\r')
{
m_error_col++;
}
}
return false;
}
//+------------------------------------------------------------------+
//| Parse a JSON literal (true / false / null) |
//+------------------------------------------------------------------+
int CJson::ParseLiteralAt(int start, const uchar &lit[], int lit_len)
{
const int len = ArraySize(Buffer);
if(start + lit_len > len)
return SetError(JSON_ERR_UNEXPECTED_END, start);
for(int i = 0; i < lit_len; i++)
{
if(Buffer[start + i] != lit[i])
return SetError(JSON_ERR_INVALID_TOKEN, start);
}
m_pos = start + lit_len;
return JSON_OK;
}
//+------------------------------------------------------------------+
//| Decode 4 hex digits at position p. |
//| On success returns JSON_OK and stores the codepoint. |
//+------------------------------------------------------------------+
int CJson::DecodeHex4(int p, int &codepoint)
{
const int len = ArraySize(Buffer);
if(p + 4 > len)
return SetError(JSON_ERR_UNEXPECTED_END, p);
int v = 0;
for(int i = 0; i < 4; i++)
{
uchar c = Buffer[p + i];
int d;
if(c >= '0' && c <= '9') d = c - '0';
else if(c >= 'a' && c <= 'f') d = c - 'a' + 10;
else if(c >= 'A' && c <= 'F') d = c - 'A' + 10;
else return SetError(JSON_ERR_INVALID_UNICODE, p);
v = (v << 4) | d;
}
codepoint = v;
return JSON_OK;
}
//+------------------------------------------------------------------+
//| Encode a Unicode codepoint as UTF-8 starting at dst_pos. |
//| Returns the number of bytes written. |
//+------------------------------------------------------------------+
int CJson::EncodeUtf8(int cp, int dst_pos)
{
if(!ReserveStrings(dst_pos + 4))
return -1;
int n = 0;
if(cp < 0x80)
{
m_strings[dst_pos + 0] = (uchar)cp;
n = 1;
}
else if(cp < 0x800)
{
m_strings[dst_pos + 0] = (uchar)(0xC0 | (cp >> 6));
m_strings[dst_pos + 1] = (uchar)(0x80 | (cp & 0x3F));
n = 2;
}
else if(cp < 0x10000)
{
m_strings[dst_pos + 0] = (uchar)(0xE0 | (cp >> 12));
m_strings[dst_pos + 1] = (uchar)(0x80 | ((cp >> 6) & 0x3F));
m_strings[dst_pos + 2] = (uchar)(0x80 | (cp & 0x3F));
n = 3;
}
else
{
m_strings[dst_pos + 0] = (uchar)(0xF0 | (cp >> 18));
m_strings[dst_pos + 1] = (uchar)(0x80 | ((cp >> 12) & 0x3F));
m_strings[dst_pos + 2] = (uchar)(0x80 | ((cp >> 6) & 0x3F));
m_strings[dst_pos + 3] = (uchar)(0x80 | (cp & 0x3F));
n = 4;
}
if(dst_pos + n > m_strings_len)
m_strings_len = dst_pos + n;
return n;
}
//+------------------------------------------------------------------+
//| Parse a JSON string. |
//| Decodes the string into m_strings[]; the node references it |
//| via out_off / out_len. |
//+------------------------------------------------------------------+
int CJson::ParseStringAt(int start, int &out_off, int &out_len)
{
const int len = ArraySize(Buffer);
if(start >= len || Buffer[start] != '"')
return SetError(JSON_ERR_INVALID_STRING, start);
int p = start + 1; // current scan position in Buffer
int src_pos = p; // start of the current unescaped span
out_off = -1;
out_len = 0;
while(true)
{
// Branchless scan to next " or \.
while(p < len && g_json_qtbl[Buffer[p]] == 0) p++;
if(p >= len)
return SetError(JSON_ERR_UNEXPECTED_END, p);
uchar c = Buffer[p];
if(c == '"')
{
// Closing quote: flush the final unescaped span.
if(p > src_pos)
{
int r = AppendString(Buffer, src_pos, p - src_pos);
if(r < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p);
if(out_off < 0) out_off = r;
}
else if(out_off < 0)
{
// Empty string: remember the current arena end.
out_off = m_strings_len;
}
out_len = m_strings_len - out_off;
m_pos = p + 1; // past closing quote
return JSON_OK;
}
// c == '\\' : flush the unescaped span, then handle the escape.
if(p + 1 >= len)
return SetError(JSON_ERR_UNEXPECTED_END, p + 1);
if(p > src_pos)
{
int r = AppendString(Buffer, src_pos, p - src_pos);
if(r < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p);
if(out_off < 0) out_off = r;
}
else if(out_off < 0)
{
out_off = m_strings_len;
}
uchar e = Buffer[p + 1];
int consumed = 2;
if(e == '"') { if(AppendChar('"') < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
else if(e == '\\') { if(AppendChar('\\') < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
else if(e == '/') { if(AppendChar('/') < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
else if(e == 'b') { if(AppendChar(0x08) < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
else if(e == 'f') { if(AppendChar(0x0C) < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
else if(e == 'n') { if(AppendChar(0x0A) < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
else if(e == 'r') { if(AppendChar(0x0D) < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
else if(e == 't') { if(AppendChar(0x09) < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p); }
else if(e == 'u')
{
int cp;
int err = DecodeHex4(p + 2, cp);
if(err != JSON_OK) return err;
consumed = 6;
if(cp >= 0xD800 && cp <= 0xDBFF)
{
if(p + 12 > len || Buffer[p + 6] != '\\' || Buffer[p + 7] != 'u')
return SetError(JSON_ERR_INVALID_UNICODE, p);
int cp2;
err = DecodeHex4(p + 8, cp2);
if(err != JSON_OK) return err;
if(cp2 < 0xDC00 || cp2 > 0xDFFF)
return SetError(JSON_ERR_INVALID_UNICODE, p);
cp = 0x10000 + ((cp - 0xD800) << 10) + (cp2 - 0xDC00);
consumed = 12;
}
else if(cp >= 0xDC00 && cp <= 0xDFFF)
{
return SetError(JSON_ERR_INVALID_UNICODE, p);
}
int dst = m_strings_len;
int n = EncodeUtf8(cp, dst);
if(n < 0) return SetError(JSON_ERR_OUT_OF_MEMORY, p);
}
else
{
return SetError(JSON_ERR_INVALID_ESCAPE, p + 1);
}
p += consumed;
src_pos = p; // next unescaped span starts here
}
return JSON_OK;
}
//+------------------------------------------------------------------+
//| Parse a JSON number. |
//| Uses a single pass: collects the byte range, then converts. |
//+------------------------------------------------------------------+
int CJson::ParseNumberAt(int start, double &out_num, long &out_int, int &out_len)
{
const int len = ArraySize(Buffer);
int p = start;
// Optional sign
if(p < len && Buffer[p] == '-') p++;
if(p >= len) return SetError(JSON_ERR_INVALID_NUMBER, p);
// Integer part: 0 or [1-9][0-9]* (no leading zeros)
if(p >= len) return SetError(JSON_ERR_INVALID_NUMBER, p);
if(Buffer[p] == '0')
{
p++;
// Reject leading zeros like "01", "02.5"
if(p < len && Buffer[p] >= '0' && Buffer[p] <= '9')
return SetError(JSON_ERR_INVALID_NUMBER, p);
}
else if(Buffer[p] >= '1' && Buffer[p] <= '9')
{
p++;
while(p < len && Buffer[p] >= '0' && Buffer[p] <= '9') p++;
}
else
return SetError(JSON_ERR_INVALID_NUMBER, p);
// Fraction
bool is_int = true;
if(p < len && Buffer[p] == '.')
{
is_int = false;
p++;
if(p >= len || Buffer[p] < '0' || Buffer[p] > '9')
return SetError(JSON_ERR_INVALID_NUMBER, p);
while(p < len && Buffer[p] >= '0' && Buffer[p] <= '9') p++;
}
// Exponent
if(p < len && (Buffer[p] == 'e' || Buffer[p] == 'E'))
{
is_int = false;
p++;
if(p < len && (Buffer[p] == '+' || Buffer[p] == '-')) p++;
if(p >= len || Buffer[p] < '0' || Buffer[p] > '9')
return SetError(JSON_ERR_INVALID_NUMBER, p);
while(p < len && Buffer[p] >= '0' && Buffer[p] <= '9') p++;
}
// Build substring and convert.
int n = p - start;
if(n <= 0) return SetError(JSON_ERR_INVALID_NUMBER, start);
string s = CharArrayToString(Buffer, start, n);
out_num = StringToDouble(s);
out_int = (long)StringToInteger(s);
out_len = n;
m_pos = p;
return JSON_OK;
}
//+------------------------------------------------------------------+
//| Parse one value (object / array / string / number / bool / null) |
//| into the given node. |
//+------------------------------------------------------------------+
int CJson::ParseValueInto(int node)
{
int p = SkipWs(m_pos);
m_pos = p;
const int len = ArraySize(Buffer);
if(p >= len) return SetError(JSON_ERR_UNEXPECTED_END, p);
uchar c = Buffer[p];
if(c == '{')
{
m_nodes[node].type = JSON_TYPE_OBJECT;
m_nodes[node].child = -1;
m_pos = p + 1;
return JSON_OK;
}
if(c == '[')
{
m_nodes[node].type = JSON_TYPE_ARRAY;
m_nodes[node].child = -1;
m_pos = p + 1;
return JSON_OK;
}
if(c == '"')
{
int off, len_out;
int err = ParseStringAt(p, off, len_out);
if(err != JSON_OK) return err;
m_nodes[node].type = JSON_TYPE_STRING;
m_nodes[node].val_off = off;
m_nodes[node].val_len = len_out;
return JSON_OK;
}
if(c == 't')
{
uchar lit[4];
lit[0]='t'; lit[1]='r'; lit[2]='u'; lit[3]='e';
int err = ParseLiteralAt(p, lit, 4);
if(err != JSON_OK) return err;
m_nodes[node].type = JSON_TYPE_BOOL;
m_nodes[node].bool_value = true;
m_nodes[node].val_len = 4;
return JSON_OK;
}
if(c == 'f')
{
uchar lit[5];
lit[0]='f'; lit[1]='a'; lit[2]='l'; lit[3]='s'; lit[4]='e';
int err = ParseLiteralAt(p, lit, 5);
if(err != JSON_OK) return err;
m_nodes[node].type = JSON_TYPE_BOOL;
m_nodes[node].bool_value = false;
m_nodes[node].val_len = 5;
return JSON_OK;
}
if(c == 'n')
{
uchar lit[4];
lit[0]='n'; lit[1]='u'; lit[2]='l'; lit[3]='l';
int err = ParseLiteralAt(p, lit, 4);
if(err != JSON_OK) return err;
m_nodes[node].type = JSON_TYPE_NULL;
m_nodes[node].val_len = 4;
return JSON_OK;
}
if(c == '-' || (c >= '0' && c <= '9'))
{
double num;
long iv;
int nlen;
int err = ParseNumberAt(p, num, iv, nlen);
if(err != JSON_OK) return err;
m_nodes[node].type = JSON_TYPE_NUMBER;
m_nodes[node].num_value = num;
m_nodes[node].int_value = iv;
// For numbers we keep the raw text range in Buffer (zero-copy).
m_nodes[node].val_off = p;
m_nodes[node].val_len = nlen;
m_nodes[node].flags = 0x01; // has cached num
return JSON_OK;
}
return SetError(JSON_ERR_UNEXPECTED_CHAR, p);
}
//+------------------------------------------------------------------+
//| Main entry point |
//+------------------------------------------------------------------+
bool CJson::Parse()
{
Reset();
const int len = ArraySize(Buffer);
if(len == 0)
{
m_last_error = JSON_ERR_EMPTY;
m_error_pos = 0;
m_error_line = 1;
m_error_col = 1;
return false;
}
// Allocate root.
m_root = AllocNode();
if(m_root < 0)
{
m_last_error = JSON_ERR_OUT_OF_MEMORY;
return false;
}
m_nodes[m_root].parent = -1;
// Parse the root value.
int err = ParseValueInto(m_root);
if(err != JSON_OK)
return false;
// If root is a container, process its contents.
JsonFrame frames[JSON_MAX_DEPTH];
int depth = 0;
char rtype = m_nodes[m_root].type;
if(rtype == JSON_TYPE_OBJECT || rtype == JSON_TYPE_ARRAY)
{
frames[0].parent_node = m_root;
frames[0].last_child = -1;
frames[0].container = rtype;
frames[0].state = (rtype == JSON_TYPE_OBJECT)
? (char)ST_EXPECT_KEY_OR_END
: (char)ST_EXPECT_VALUE_OR_END;
depth = 1;
}
// Main parse loop. We keep going while there is at least one open
// container on the stack. After the last container closes we exit
// and verify there is no trailing data.
while(depth > 0)
{
int p = SkipWs(m_pos);
m_pos = p;
if(p >= len)
{
SetError(JSON_ERR_UNEXPECTED_END, p);
return false;
}
uchar c = Buffer[p];
JsonFrame &f = frames[depth - 1];
char state = f.state;
if(state == ST_EXPECT_KEY_OR_END)
{
if(c == '}')
{
depth--;
m_pos = p + 1;
if(depth > 0)
frames[depth - 1].state = (char)ST_EXPECT_COMMA_OR_END;
continue;
}
if(c == '"')
{
int off, lenk;
err = ParseStringAt(p, off, lenk);
if(err != JSON_OK) return false;
// Add a new key-value pair child.
int child = AddChild(f.parent_node, f.last_child, JSON_TYPE_INVALID);
if(child < 0)
{
SetError(JSON_ERR_OUT_OF_MEMORY, p);
return false;
}
m_nodes[child].name_off = off;
m_nodes[child].name_len = lenk;
f.last_child = child;
f.state = (char)ST_EXPECT_COLON;
continue;
}
SetError(JSON_ERR_EXPECTED_KEY, p);
return false;
}
else if(state == ST_EXPECT_COLON)
{
if(c != ':')
{
SetError(JSON_ERR_EXPECTED_COLON, p);
return false;
}
m_pos = p + 1;
f.state = (char)ST_EXPECT_VALUE;
continue;
}
else if(state == ST_EXPECT_VALUE)
{
// Object: fill the last_child node (created for the key).
// Array : create a new child.
int target;
if(f.container == JSON_TYPE_OBJECT)
target = f.last_child;
else
{
target = AddChild(f.parent_node, f.last_child, JSON_TYPE_INVALID);
if(target < 0)
{
SetError(JSON_ERR_OUT_OF_MEMORY, p);
return false;
}
f.last_child = target;
}
err = ParseValueInto(target);
if(err != JSON_OK) return false;
// If the value is itself a container, descend into it.
char vtype = m_nodes[target].type;
if(vtype == JSON_TYPE_OBJECT || vtype == JSON_TYPE_ARRAY)
{
if(depth >= JSON_MAX_DEPTH)
{
SetError(JSON_ERR_MAX_DEPTH, m_pos);
return false;
}
JsonFrame &nf = frames[depth];
nf.parent_node = target;
nf.last_child = -1;
nf.container = vtype;
nf.state = (vtype == JSON_TYPE_OBJECT)
? (char)ST_EXPECT_KEY_OR_END
: (char)ST_EXPECT_VALUE_OR_END;
depth++;
}
else
{
f.state = (char)ST_EXPECT_COMMA_OR_END;
}
continue;
}
else if(state == ST_EXPECT_VALUE_OR_END)
{
if(c == ']')
{
depth--;
m_pos = p + 1;
if(depth > 0)
frames[depth - 1].state = (char)ST_EXPECT_COMMA_OR_END;
continue;
}
int target = AddChild(f.parent_node, f.last_child, JSON_TYPE_INVALID);
if(target < 0)
{
SetError(JSON_ERR_OUT_OF_MEMORY, p);
return false;
}
f.last_child = target;
err = ParseValueInto(target);
if(err != JSON_OK) return false;
char vtype = m_nodes[target].type;
if(vtype == JSON_TYPE_OBJECT || vtype == JSON_TYPE_ARRAY)
{
if(depth >= JSON_MAX_DEPTH)
{
SetError(JSON_ERR_MAX_DEPTH, m_pos);
return false;
}
JsonFrame &nf = frames[depth];
nf.parent_node = target;
nf.last_child = -1;
nf.container = vtype;
nf.state = (vtype == JSON_TYPE_OBJECT)
? (char)ST_EXPECT_KEY_OR_END
: (char)ST_EXPECT_VALUE_OR_END;
depth++;
}
else
{
f.state = (char)ST_EXPECT_COMMA_OR_END;
}
continue;
}
else /* ST_EXPECT_COMMA_OR_END */
{
if(c == ',')
{
m_pos = p + 1;
f.state = (f.container == JSON_TYPE_OBJECT)
? (char)ST_EXPECT_KEY_OR_END
: (char)ST_EXPECT_VALUE_OR_END;
continue;
}
if(c == '}' || c == ']')
{
char expected = (f.container == JSON_TYPE_OBJECT) ? '}' : ']';
if(c != expected)
{
SetError(JSON_ERR_UNEXPECTED_CHAR, p);
return false;
}
depth--;
m_pos = p + 1;
if(depth > 0)
frames[depth - 1].state = (char)ST_EXPECT_COMMA_OR_END;
continue;
}
SetError(JSON_ERR_EXPECTED_COMMA, p);
return false;
}
}
// Reject trailing data.
int tail = SkipWs(m_pos);
if(tail < len)
{
SetError(JSON_ERR_TRAILING_DATA, tail);
return false;
}
m_pos = tail;
m_last_error = JSON_OK;
return true;
}
//+------------------------------------------------------------------+
//| Compare a stored UTF-8 key against a MQL5 string key. |
//| Returns true if they are equal. Handles multi-byte UTF-8 and |
//| UTF-16 surrogate pairs in the input key. |
//+------------------------------------------------------------------+
bool CJson::KeyMatch(const JsonNode &cn, const string key) const
{
int stored_len = cn.name_len;
int key_len = StringLen(key);
int si = 0; // index into stored UTF-8
int ki = 0; // index into MQL5 string (UTF-16 code units)
while(si < stored_len)
{
if(ki >= key_len) return false;
uchar b = m_strings[cn.name_off + si];
// Fast path: ASCII
if(b < 0x80)
{
ushort k = StringGetCharacter(key, ki);
if(k >= 0x80) return false;
if(b != (uchar)k) return false;
si++;
ki++;
continue;
}
// 2-byte UTF-8
if((b & 0xE0) == 0xC0)
{
if(si + 1 >= stored_len) return false;
ushort k = StringGetCharacter(key, ki);
if(k < 0x80 || k >= 0x800) return false;
int cp = ((b & 0x1F) << 6) | (m_strings[cn.name_off + si + 1] & 0x3F);
if(cp != (int)k) return false;
si += 2;
ki++;
continue;
}
// 3-byte UTF-8 (BMP)
if((b & 0xF0) == 0xE0)
{
if(si + 2 >= stored_len) return false;
ushort k = StringGetCharacter(key, ki);
if(k < 0x800) return false;
if(k >= 0xD800 && k <= 0xDFFF) return false; // unpaired surrogate
int cp = ((b & 0x0F) << 12)
| ((m_strings[cn.name_off + si + 1] & 0x3F) << 6)
| (m_strings[cn.name_off + si + 2] & 0x3F);
if(cp != (int)k) return false;
si += 3;
ki++;
continue;
}
// 4-byte UTF-8 (surrogate pair)
if((b & 0xF8) == 0xF0)
{
if(si + 3 >= stored_len) return false;
if(ki + 1 >= key_len) return false;
ushort k1 = StringGetCharacter(key, ki);
ushort k2 = StringGetCharacter(key, ki + 1);
if(!(k1 >= 0xD800 && k1 <= 0xDBFF && k2 >= 0xDC00 && k2 <= 0xDFFF))
return false;
int cp = 0x10000 + ((k1 - 0xD800) << 10) + (k2 - 0xDC00);
int cp2 = ((b & 0x07) << 18)
| ((m_strings[cn.name_off + si + 1] & 0x3F) << 12)
| ((m_strings[cn.name_off + si + 2] & 0x3F) << 6)
| (m_strings[cn.name_off + si + 3] & 0x3F);
if(cp != cp2) return false;
si += 4;
ki += 2;
continue;
}
return false; // invalid UTF-8 lead byte
}
return ki == key_len;
}
//+------------------------------------------------------------------+
//| Internal: linear scan for a child with the given key |
//+------------------------------------------------------------------+
int CJson::NodeFindKey(int parent, const string key) const
{
if(parent < 0 || parent >= m_nodes_count)
return -1;
const JsonNode &p = m_nodes[parent];
if(p.type != JSON_TYPE_OBJECT)
return -1;
int c = p.child;
while(c >= 0)
{
const JsonNode cn = m_nodes[c];
if(KeyMatch(cn, key))
return c;
c = cn.next;
}
return -1;
}
//+------------------------------------------------------------------+
//| Root-level wrappers |
//+------------------------------------------------------------------+
ENUM_JSON_TYPE CJson::GetType() const
{
if(m_root < 0) return JSON_TYPE_INVALID;
return (ENUM_JSON_TYPE)m_nodes[m_root].type;
}
bool CJson::IsObject() const { return GetType() == JSON_TYPE_OBJECT; }
bool CJson::IsArray() const { return GetType() == JSON_TYPE_ARRAY; }
bool CJson::IsString() const { return GetType() == JSON_TYPE_STRING; }
bool CJson::IsNumber() const { return GetType() == JSON_TYPE_NUMBER; }
bool CJson::IsBool() const { return GetType() == JSON_TYPE_BOOL; }
bool CJson::IsNull() const { return GetType() == JSON_TYPE_NULL; }
int CJson::Size() const
{
if(m_root < 0) return 0;
int c = m_nodes[m_root].child;
if(c < 0) return 0;
int n = 0;
while(c >= 0)
{
n++;
c = m_nodes[c].next;
}
return n;
}
bool CJson::Exists(const string key) const
{
if(m_root < 0) return false;
return NodeFindKey(m_root, key) >= 0;
}
string CJson::ToString(const string def) const
{
if(m_root < 0) return def;
const JsonNode &n = m_nodes[m_root];
if(n.type == JSON_TYPE_STRING)
{
if(n.val_len <= 0) return "";
return CharArrayToString(m_strings, n.val_off, n.val_len, CP_UTF8);
}
if(n.type == JSON_TYPE_NUMBER)
{
if(n.val_len <= 0) return def;
return CharArrayToString(Buffer, n.val_off, n.val_len);
}
return def;
}
double CJson::ToDouble(const double def) const
{
if(m_root < 0) return def;
const JsonNode &n = m_nodes[m_root];
if(n.type != JSON_TYPE_NUMBER) return def;
return n.num_value;
}
long CJson::ToInteger(const long def) const
{
if(m_root < 0) return def;
const JsonNode &n = m_nodes[m_root];
if(n.type != JSON_TYPE_NUMBER) return def;
return n.int_value;
}
bool CJson::ToBool(const bool def) const
{
if(m_root < 0) return def;
const JsonNode &n = m_nodes[m_root];
if(n.type != JSON_TYPE_BOOL) return def;
return n.bool_value;
}
CJsonNode CJson::operator[](const string key)
{
if(m_root < 0) return CJsonNode();
if(m_nodes[m_root].type != JSON_TYPE_OBJECT) return CJsonNode();
int found = NodeFindKey(m_root, key);
if(found < 0) return CJsonNode();
return CJsonNode((CJson*)GetPointer(this), found);
}
CJsonNode CJson::operator[](int index)
{
if(m_root < 0) return CJsonNode();
if(m_nodes[m_root].type != JSON_TYPE_ARRAY || index < 0) return CJsonNode();
int c = m_nodes[m_root].child;
while(c >= 0 && index > 0)
{
c = m_nodes[c].next;
index--;
}
if(c < 0) return CJsonNode();
return CJsonNode((CJson*)GetPointer(this), c);
}
//+------------------------------------------------------------------+
//| Human-readable error message |
//+------------------------------------------------------------------+
string CJson::GetErrorMessage() const
{
switch(m_last_error)
{
case JSON_OK: return "No error";
case JSON_ERR_EMPTY: return "Empty document";
case JSON_ERR_UNEXPECTED_CHAR: return "Unexpected character";
case JSON_ERR_UNEXPECTED_END: return "Unexpected end of document";
case JSON_ERR_INVALID_NUMBER: return "Invalid number";
case JSON_ERR_INVALID_STRING: return "Invalid string";
case JSON_ERR_INVALID_ESCAPE: return "Invalid escape sequence";
case JSON_ERR_INVALID_UNICODE: return "Invalid unicode escape";
case JSON_ERR_EXPECTED_COLON: return "Expected ':'";
case JSON_ERR_EXPECTED_COMMA: return "Expected ','";
case JSON_ERR_EXPECTED_KEY: return "Expected object key";
case JSON_ERR_EXPECTED_VALUE: return "Expected value";
case JSON_ERR_MAX_DEPTH: return "Maximum nesting depth exceeded";
case JSON_ERR_INVALID_TOKEN: return "Invalid token";
case JSON_ERR_TRAILING_DATA: return "Trailing data after JSON document";
case JSON_ERR_INVALID_ROOT: return "Invalid root value";
case JSON_ERR_OUT_OF_MEMORY: return "Out of memory";
case JSON_ERR_INVALID_INDEX: return "Invalid index";
default: return "Unknown error";
}
}
#endif // JSON_MQH_INCLUDED
//+------------------------------------------------------------------+