JsonParserByLeo/Test/Other/CJsonNode.mqh
2026-06-21 09:56:56 -05:00

711 righe
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__