711 行
42 KiB
MQL5
711 行
42 KiB
MQL5
#ifndef __CJSONNODE_MQH__
|
|
#define __CJSONNODE_MQH__
|
|
|
|
// Nota el autor original pasaba todos los strigns como copia lo cambia a const string& (text) para que sea mas justo..
|
|
// https://www.mql5.com/en/articles/16791
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| 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();
|
|
|
|
//---
|
|
string __bjson; // json a parsear..
|
|
|
|
//--- Parse entire JSON text
|
|
bool ParseString();
|
|
|
|
//--- 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(const string& text,int &pos);
|
|
bool ParseObject(const string& text,int &pos);
|
|
bool ParseArray(const string& text,int &pos);
|
|
bool ParseNumber(const string& text,int &pos);
|
|
bool ParseStringLiteral(const string& text,int &pos);
|
|
bool ParseKeyLiteral(const string& text,int &pos,string &keyOut);
|
|
string UnescapeString(const string& input_);
|
|
bool SkipWhitespace(const string& text,int &pos);
|
|
bool AllWhitespace(const string& text,int pos);
|
|
string SerializeNode();
|
|
string SerializeObject();
|
|
string SerializeArray();
|
|
string EscapeString(const 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()
|
|
{
|
|
Reset();
|
|
int pos = 0;
|
|
bool res = (ParseValue(__bjson,pos) && SkipWhitespace(__bjson,pos));
|
|
|
|
// If there's leftover text that's not whitespace, it's an error
|
|
if(pos < StringLen(__bjson))
|
|
{
|
|
if(!AllWhitespace(__bjson,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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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(const 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 += ShortToString(c);
|
|
break;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
#endif // __CJSONNODE_MQH__
|