JsonParserByLeo/Test/Other/Json.mqh
2026-06-28 08:33:56 -05:00

1602 lines
39 KiB
MQL5

//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
/*
# Especificación: Parser JSON de Alto Rendimiento para MQL5
Necesito que desarrolles un **parser JSON de alto rendimiento (High Performance JSON Parser)** completamente escrito en **MQL5 puro**.
## Objetivo principal
La prioridad absoluta es el **rendimiento**. El parser debe estar diseñado para ser lo más rápido posible sin sacrificar la compatibilidad con el estándar JSON.
No busco una implementación sencilla o académica; quiero una implementación optimizada para producción utilizando todas las técnicas de optimización que conozcas.
---
# Requisitos generales
* Todo el código debe estar contenido en **un único archivo `.mqh`**.
* No utilizar DLL.
* No utilizar librerías externas.
* No depender de componentes adicionales.
* Código 100% MQL5.
---
# Compatibilidad JSON
Debe soportar completamente la especificación JSON:
* Objetos (`{}`)
* Arrays (`[]`)
* Strings
* Numbers
* enteros
* decimales
* exponentes
* Boolean (`true`, `false`)
* `null`
* Secuencias de escape:
* `\"`
* `\\`
* `\/`
* `\b`
* `\f`
* `\n`
* `\r`
* `\t`
* `\uXXXX`
No quiero un parser parcial; debe aceptar cualquier JSON válido.
---
# Diseño de la arquitectura
Quiero que tú decidas la arquitectura interna.
Puedes utilizar cualquier técnica que mejore el rendimiento, por ejemplo:
* parser iterativo (evitar recursión si aporta velocidad)
* stacks manuales
* pools de memoria
* arenas de memoria
* estructuras contiguas
* tablas de índices
* minimización de asignaciones dinámicas
* reducción de copias de memoria
* acceso cache-friendly
* optimización de ramas (branch prediction)
* optimización de comparaciones de caracteres
* cualquier otra técnica de optimización que consideres adecuada.
No me interesa que la implementación sea "bonita"; me interesa que sea extremadamente rápida.
---
# Entrada de datos
El parser debe trabajar directamente sobre un buffer de bytes.
Ejemplo:
```cpp
uchar Buffer[];
```
La clase debe exponer públicamente este buffer para que yo pueda llenarlo directamente desde mi código sin copias adicionales.
Ejemplo:
```cpp
CJson json;
json.Buffer = MiBuffer;
json.Parse();
```
O una interfaz equivalente que no implique copiar datos.
---
# Clase principal
Quiero una clase similar a:
```cpp
class CJson
{
public:
uchar Buffer[];
bool Parse();
...
};
```
---
# API
Diseña una API limpia y orientada al rendimiento.
Debe incluir funciones útiles como:
* Parse()
* Clear()
* Reset()
* GetError()
* GetErrorMessage()
* HasError()
* IsObject()
* IsArray()
* GetType()
* Exists()
* Size()
* Count()
y cualquier otra función que facilite el uso del parser.
---
# Acceso a valores
Debe permitir acceder fácilmente a los nodos.
Ejemplos:
```cpp
json["symbol"];
json["price"];
json["ticks"][5];
json["account"]["balance"];
```
o cualquier interfaz equivalente que sea eficiente.
---
# Representación interna
Quiero que el parser almacene una representación eficiente del documento JSON.
No quiero que vuelva a parsear el texto cada vez que se consulta un valor.
La estructura interna debe permitir consultas rápidas.
---
# Manejo de memoria
La implementación debe minimizar:
* asignaciones dinámicas
* fragmentación
* copias innecesarias
* uso excesivo de memoria
Si es posible, reutilizar buffers mediante `Reset()` o `Clear()`.
---
# Manejo de errores
Debe disponer de un sistema completo de errores.
Por ejemplo:
```cpp
enum JsonError
{
JSON_OK,
JSON_UNEXPECTED_CHARACTER,
JSON_UNEXPECTED_END,
JSON_INVALID_NUMBER,
JSON_INVALID_STRING,
JSON_INVALID_ESCAPE,
JSON_INVALID_UNICODE,
JSON_EXPECTED_COLON,
JSON_EXPECTED_COMMA,
JSON_EXPECTED_OBJECT,
JSON_EXPECTED_ARRAY,
JSON_MAX_DEPTH,
...
};
```
Y funciones como:
```cpp
GetError();
GetErrorMessage();
GetErrorPosition();
GetLine();
GetColumn();
```
---
# Calidad del código
El código debe ser:
* modular
* documentado
* fácil de mantener
* altamente optimizado
* preparado para proyectos profesionales
---
# Optimización
Durante el desarrollo, prioriza siempre el rendimiento sobre la simplicidad.
Si una técnica mejora significativamente la velocidad aunque aumente la complejidad del código, prefiero la versión más rápida.
Quiero un parser que pueda competir en rendimiento con implementaciones de alto nivel adaptadas a las limitaciones de MQL5.
---
# Entregable
El resultado final debe ser un único archivo `.mqh`, completamente funcional, autocontenido y listo para incluir mediante:
```cpp
#include "Json.mqh"
```
sin depender de ningún otro archivo.
El objetivo final es obtener el parser JSON más rápido posible que pueda implementarse en MQL5 puro.
*/
//+------------------------------------------------------------------+
//| Json.mqh |
//| High Performance JSON Parser for MQL5 |
//| v1.0 - Pure MQL5 Implementation |
//+------------------------------------------------------------------+
// |
// CARACTERISTICAS |
// - Parser iterativo de una sola pasada sobre buffer uchar[] |
// - Almacenamiento contiguo de nodos (cache-friendly) |
// - Tabla de clasificacion de caracteres (O(1) por byte) |
// - Stacks manuales para profundidad (sin recursion del lenguaje) |
// - Strings referenciados por offset/length en el buffer original |
// - Numeros parseados y almacenados como double durante la tokenizacion |
// - Flag de escape detection para extraccion rapida de strings |
// - Sin DLL, sin librerias externas, sin dependencias |
// - API tipo documento + CJsonValue lightweight |
// |
//+------------------------------------------------------------------+
// USO BASICO |
// #include "Json.mqh" |
// |
// CJson json; |
// uchar buffer[] = ... ; // llenar buffer |
// json.Buffer = buffer; |
// if (json.Parse()) { |
// string s = json["name"].ToString(); |
// int i = json["count"].ToInt(); |
// double d = json["price"].ToDouble(); |
// bool b = json["active"].ToBool(); |
// int sz = json["items"].Count(); |
// string k = json["obj"].KeyAt(0); |
// string v = json["arr"][3].ToString(); |
// } else { |
// Print("Error: ", json.GetErrorMessage()); |
// } |
//+------------------------------------------------------------------+
#property strict
//+------------------------------------------------------------------+
//| ENUM: Codigos de error |
//+------------------------------------------------------------------+
enum JsonError
{
JERR_OK = 0,
JERR_UNEXPECTED_CHAR = 1,
JERR_UNEXPECTED_END = 2,
JERR_INVALID_NUMBER = 3,
JERR_INVALID_STRING = 4,
JERR_INVALID_ESCAPE = 5,
JERR_INVALID_UNICODE = 6,
JERR_EXPECTED_COLON = 7,
JERR_EXPECTED_COMMA = 8,
JERR_EXPECTED_KEY = 9,
JERR_EXPECTED_VALUE = 10,
JERR_EXPECTED_OBJ_END = 11,
JERR_EXPECTED_ARR_END = 12,
JERR_DEPTH_EXCEEDED = 13,
JERR_NODE_LIMIT = 14,
JERR_TRAILING_DATA = 15,
JERR_NOT_PARSED = 16,
JERR_INDEX_OOB = 17,
JERR_KEY_NOT_FOUND = 18,
JERR_TYPE_MISMATCH = 19,
JERR_BUFFER_EMPTY = 20,
};
//+------------------------------------------------------------------+
//| ENUM: Tipos de nodo JSON |
//+------------------------------------------------------------------+
enum JsonType
{
JT_NULL = 0,
JT_BOOL = 1,
JT_INT = 2,
JT_DOUBLE = 3,
JT_STRING = 4,
JT_ARRAY = 5,
JT_OBJECT = 6
};
//+------------------------------------------------------------------+
//| FORWARD DECLARATIONS |
//+------------------------------------------------------------------+
class CJson;
class CJsonValue;
//+------------------------------------------------------------------+
//| CONSTANTES DE CONFIGURACION |
//+------------------------------------------------------------------+
#define JSON_MAX_DEPTH 1024
#define JSON_INIT_NODES 8192
#define JSON_GROW_FACTOR 2
//+------------------------------------------------------------------+
//| TABLA DE CLASIFICACION DE CARACTERES |
//+------------------------------------------------------------------+
#define _JC_CTRL 0
#define _JC_WS 1
#define _JC_QUOTE 2
#define _JC_DIGIT 3
#define _JC_MINUS 4
#define _JC_LIT 5
#define _JC_STRUCT 6
#define _JC_BSLASH 7
#define _JC_OTHER 8
static uchar s_char_class[256];
static bool s_class_init = false;
void Json_InitCharClass()
{
if(s_class_init) return;
s_class_init = true;
for(int i = 0; i < 256; i++)
{
uchar cls = _JC_OTHER;
if(i < 0x20)
cls = _JC_CTRL;
else if(i == ' ' || i == 0x09 || i == 0x0A || i == 0x0D)
cls = _JC_WS;
else if(i == '"')
cls = _JC_QUOTE;
else if(i >= '0' && i <= '9')
cls = _JC_DIGIT;
else if(i == '-')
cls = _JC_MINUS;
else if(i == 't' || i == 'f' || i == 'n')
cls = _JC_LIT;
else if(i == '{' || i == '}' || i == '[' || i == ']' || i == ':' || i == ',')
cls = _JC_STRUCT;
else if(i == '\\')
cls = _JC_BSLASH;
s_char_class[i] = cls;
}
}
//+------------------------------------------------------------------+
//| STRUCT: Nodo JSON interno |
//+------------------------------------------------------------------+
struct SJsonNode
{
int parent;
int child;
int next;
int key_off;
int key_len;
int val_off;
int val_len;
double num_val;
uchar type;
uchar flags;
ushort child_cnt;
};
//+------------------------------------------------------------------+
//| CLASE: CJsonValue |
//+------------------------------------------------------------------+
class CJsonValue
{
protected:
CJson* m_doc;
int m_node;
public:
CJsonValue();
CJsonValue(CJson* doc, int node);
CJsonValue(const CJsonValue& other);
void operator=(const CJsonValue& other);
bool IsValid() const;
int NodeIndex() const;
bool IsNull() const;
bool IsBool() const;
bool IsInt() const;
bool IsDouble() const;
bool IsNumber() const;
bool IsString() const;
bool IsArray() const;
bool IsObject() const;
int GetType() const;
string ToString() const;
int ToInt() const;
double ToDouble() const;
bool ToBool() const;
CJsonValue operator[](string key) const;
CJsonValue operator[](int index) const;
int Count() const;
bool Exists(string key) const;
string KeyAt(int index) const;
CJsonValue ValueAt(int index) const;
};
//+------------------------------------------------------------------+
//| CLASE PRINCIPAL: CJson |
//+------------------------------------------------------------------+
class CJson
{
protected:
SJsonNode m_nodes[];
int m_node_count;
int m_node_cap;
int m_pos;
int m_length;
int m_error;
int m_error_pos;
int m_stack[];
int m_stack_size;
int m_stack_cap;
bool m_parsed;
public:
uchar Buffer[];
CJson();
~CJson();
bool Parse();
void Clear();
void Reset();
bool HasError() const;
int GetError() const;
string GetErrorMessage() const;
int GetErrorPosition() const;
int GetLine() const;
int GetColumn() const;
CJsonValue Root();
bool IsValid() const;
CJsonValue operator[](string key);
CJsonValue operator[](int index);
bool Exists(string key);
int NodeCount() const;
//--- Acceso a nodos para CJsonValue
int Node_GetParent(int idx) const;
int Node_GetChild(int idx) const;
int Node_GetNext(int idx) const;
int Node_GetKeyOff(int idx) const;
int Node_GetKeyLen(int idx) const;
int Node_GetValOff(int idx) const;
int Node_GetValLen(int idx) const;
double Node_GetNumVal(int idx) const;
uchar Node_GetType(int idx) const;
uchar Node_GetFlags(int idx) const;
ushort Node_GetChildCnt(int idx) const;
bool Node_HasEscapes(int idx) const;
string ExtractNodeString(int node_idx);
string ExtractJsonString(int offset, int length, bool has_escapes);
int FindChildByKey(int node_idx, string key);
int FindChildByIndex(int node_idx, int index);
string GetChildKey(int node_idx, int index);
protected:
int AddNode(int parent, uchar type);
void SkipWhitespace();
void SetError(int err);
bool ParseValue(int parent);
bool ParseObject(int parent);
bool ParseArray(int parent);
bool ParseString(int parent, bool is_key);
bool ParseNumber(int parent);
bool ParseLiteral(int parent, uchar first);
};
//+------------------------------------------------------------------+
//| IMPLEMENTACION CJsonValue |
//+------------------------------------------------------------------+
CJsonValue::CJsonValue()
: m_doc(NULL),
m_node(-1)
{
}
CJsonValue::CJsonValue(CJson* doc, int node)
: m_doc(doc),
m_node(node)
{
}
CJsonValue::CJsonValue(const CJsonValue& other)
: m_doc(other.m_doc),
m_node(other.m_node)
{
}
void CJsonValue::operator=(const CJsonValue& other)
{
m_doc = other.m_doc;
m_node = other.m_node;
}
bool CJsonValue::IsValid() const
{
return(m_doc != NULL && m_node >= 0);
}
int CJsonValue::NodeIndex() const
{
return(m_node);
}
bool CJsonValue::IsNull() const
{
return(GetType() == JT_NULL);
}
bool CJsonValue::IsBool() const
{
return(GetType() == JT_BOOL);
}
bool CJsonValue::IsInt() const
{
return(GetType() == JT_INT);
}
bool CJsonValue::IsDouble() const
{
return(GetType() == JT_DOUBLE);
}
bool CJsonValue::IsNumber() const
{
int t = GetType();
return(t == JT_INT || t == JT_DOUBLE);
}
bool CJsonValue::IsString() const
{
return(GetType() == JT_STRING);
}
bool CJsonValue::IsArray() const
{
return(GetType() == JT_ARRAY);
}
bool CJsonValue::IsObject() const
{
return(GetType() == JT_OBJECT);
}
int CJsonValue::GetType() const
{
if(!IsValid()) return(-1);
return((int)m_doc.Node_GetType(m_node));
}
string CJsonValue::ToString() const
{
if(m_doc == NULL) return("");
return(m_doc.ExtractNodeString(m_node));
}
int CJsonValue::ToInt() const
{
return((int)ToDouble());
}
double CJsonValue::ToDouble() const
{
if(!IsValid()) return(0.0);
int t = m_doc.Node_GetType(m_node);
if(t == JT_INT || t == JT_DOUBLE)
return(m_doc.Node_GetNumVal(m_node));
if(t == JT_BOOL)
return(m_doc.Node_GetNumVal(m_node) != 0.0 ? 1.0 : 0.0);
if(t == JT_STRING)
{
string s = m_doc.ExtractNodeString(m_node);
return(StringToDouble(s));
}
return(0.0);
}
bool CJsonValue::ToBool() const
{
if(!IsValid()) return(false);
int t = m_doc.Node_GetType(m_node);
if(t == JT_BOOL)
return(m_doc.Node_GetNumVal(m_node) != 0.0);
if(t == JT_INT || t == JT_DOUBLE)
return(m_doc.Node_GetNumVal(m_node) != 0.0);
if(t == JT_STRING)
{
string s = m_doc.ExtractNodeString(m_node);
return(s == "true" ? true : false);
}
return(false);
}
CJsonValue CJsonValue::operator[](string key) const
{
if(!IsValid()) return(CJsonValue(NULL, -1));
int child = m_doc.FindChildByKey(m_node, key);
if(child < 0) return(CJsonValue(NULL, -1));
return(CJsonValue(m_doc, child));
}
CJsonValue CJsonValue::operator[](int index) const
{
if(!IsValid()) return(CJsonValue(NULL, -1));
int child = m_doc.FindChildByIndex(m_node, index);
if(child < 0) return(CJsonValue(NULL, -1));
return(CJsonValue(m_doc, child));
}
int CJsonValue::Count() const
{
if(!IsValid()) return(0);
return((int)m_doc.Node_GetChildCnt(m_node));
}
bool CJsonValue::Exists(string key) const
{
if(!IsValid()) return(false);
return(m_doc.FindChildByKey(m_node, key) >= 0);
}
string CJsonValue::KeyAt(int index) const
{
if(!IsValid()) return("");
return(m_doc.GetChildKey(m_node, index));
}
CJsonValue CJsonValue::ValueAt(int index) const
{
if(!IsValid()) return(CJsonValue(NULL, -1));
int child = m_doc.FindChildByIndex(m_node, index);
if(child < 0) return(CJsonValue(NULL, -1));
return(CJsonValue(m_doc, child));
}
//+------------------------------------------------------------------+
//| IMPLEMENTACION CJson |
//+------------------------------------------------------------------+
CJson::CJson()
: m_node_count(0),
m_node_cap(0),
m_pos(0),
m_length(0),
m_error(0),
m_error_pos(-1),
m_stack_size(0),
m_stack_cap(0),
m_parsed(false)
{
Json_InitCharClass();
}
CJson::~CJson()
{
Clear();
}
//+------------------------------------------------------------------+
//| NODE ACCESS METHODS |
//+------------------------------------------------------------------+
int CJson::Node_GetParent(int idx) const
{
if(idx < 0 || idx >= m_node_count) return(-1);
return(m_nodes[idx].parent);
}
int CJson::Node_GetChild(int idx) const
{
if(idx < 0 || idx >= m_node_count) return(-1);
return(m_nodes[idx].child);
}
int CJson::Node_GetNext(int idx) const
{
if(idx < 0 || idx >= m_node_count) return(-1);
return(m_nodes[idx].next);
}
int CJson::Node_GetKeyOff(int idx) const
{
if(idx < 0 || idx >= m_node_count) return(-1);
return(m_nodes[idx].key_off);
}
int CJson::Node_GetKeyLen(int idx) const
{
if(idx < 0 || idx >= m_node_count) return(0);
return(m_nodes[idx].key_len);
}
int CJson::Node_GetValOff(int idx) const
{
if(idx < 0 || idx >= m_node_count) return(-1);
return(m_nodes[idx].val_off);
}
int CJson::Node_GetValLen(int idx) const
{
if(idx < 0 || idx >= m_node_count) return(0);
return(m_nodes[idx].val_len);
}
double CJson::Node_GetNumVal(int idx) const
{
if(idx < 0 || idx >= m_node_count) return(0.0);
return(m_nodes[idx].num_val);
}
uchar CJson::Node_GetType(int idx) const
{
if(idx < 0 || idx >= m_node_count) return(255);
return(m_nodes[idx].type);
}
uchar CJson::Node_GetFlags(int idx) const
{
if(idx < 0 || idx >= m_node_count) return(0);
return(m_nodes[idx].flags);
}
ushort CJson::Node_GetChildCnt(int idx) const
{
if(idx < 0 || idx >= m_node_count) return(0);
return(m_nodes[idx].child_cnt);
}
bool CJson::Node_HasEscapes(int idx) const
{
if(idx < 0 || idx >= m_node_count) return(false);
return((m_nodes[idx].flags & 1) != 0);
}
//+------------------------------------------------------------------+
//| LIFE CYCLE |
//+------------------------------------------------------------------+
bool CJson::Parse()
{
m_error = JERR_OK;
m_error_pos = -1;
m_pos = 0;
m_stack_size = 0;
m_parsed = false;
if(ArraySize(Buffer) == 0)
{
m_error = JERR_BUFFER_EMPTY;
m_error_pos = 0;
return(false);
}
m_length = ArraySize(Buffer);
if(m_node_cap == 0)
{
ArrayResize(m_nodes, JSON_INIT_NODES);
m_node_cap = JSON_INIT_NODES;
}
m_node_count = 0;
if(m_stack_cap < JSON_MAX_DEPTH)
{
ArrayResize(m_stack, JSON_MAX_DEPTH);
m_stack_cap = JSON_MAX_DEPTH;
}
m_stack_size = 0;
// Saltar BOM UTF-8 si esta presente
if(m_length >= 3 && Buffer[0] == 0xEF && Buffer[1] == 0xBB && Buffer[2] == 0xBF)
m_pos = 3;
SkipWhitespace();
if(m_pos >= m_length)
{
m_error = JERR_UNEXPECTED_END;
m_error_pos = m_pos;
return(false);
}
int root = AddNode(-1, JT_NULL);
if(root < 0) return(false);
if(!ParseValue(root))
return(false);
SkipWhitespace();
if(m_pos < m_length)
{
m_error = JERR_TRAILING_DATA;
m_error_pos = m_pos;
return(false);
}
m_parsed = true;
return(true);
}
void CJson::Clear()
{
m_node_count = 0;
m_pos = 0;
m_length = 0;
m_error = JERR_OK;
m_error_pos = -1;
m_stack_size = 0;
m_parsed = false;
}
void CJson::Reset()
{
Clear();
ArrayFree(m_nodes);
ArrayFree(m_stack);
m_node_cap = 0;
m_stack_cap = 0;
}
//+------------------------------------------------------------------+
//| ERROR HANDLING |
//+------------------------------------------------------------------+
bool CJson::HasError() const
{
return(m_error != JERR_OK);
}
int CJson::GetError() const
{
return(m_error);
}
string CJson::GetErrorMessage() const
{
switch(m_error)
{
case JERR_OK: return("OK");
case JERR_UNEXPECTED_CHAR: return("Caracter inesperado en la entrada");
case JERR_UNEXPECTED_END: return("Fin inesperado del buffer");
case JERR_INVALID_NUMBER: return("Formato de numero invalido");
case JERR_INVALID_STRING: return("Formato de string invalido");
case JERR_INVALID_ESCAPE: return("Secuencia de escape invalida");
case JERR_INVALID_UNICODE: return("Secuencia \\uXXXX invalida");
case JERR_EXPECTED_COLON: return("Se esperaba ':'");
case JERR_EXPECTED_COMMA: return("Se esperaba ','");
case JERR_EXPECTED_KEY: return("Se esperaba clave string en objeto");
case JERR_EXPECTED_VALUE: return("Se esperaba un valor");
case JERR_EXPECTED_OBJ_END: return("Se esperaba '}'");
case JERR_EXPECTED_ARR_END: return("Se esperaba ']'");
case JERR_DEPTH_EXCEEDED: return("Profundidad maxima excedida");
case JERR_NODE_LIMIT: return("Limite de nodos alcanzado");
case JERR_TRAILING_DATA: return("Datos extra despues del valor raiz");
case JERR_NOT_PARSED: return("No se ha llamado a Parse()");
case JERR_INDEX_OOB: return("Indice fuera de rango");
case JERR_KEY_NOT_FOUND: return("Clave no encontrada");
case JERR_TYPE_MISMATCH: return("Tipo de nodo incorrecto para la operacion");
case JERR_BUFFER_EMPTY: return("Buffer vacio o nulo");
}
return("Error desconocido");
}
int CJson::GetErrorPosition() const
{
return(m_error_pos);
}
int CJson::GetLine() const
{
if(m_error_pos < 0) return(1);
int line = 1;
for(int i = 0; i < m_error_pos && i < m_length; i++)
if(Buffer[i] == 0x0A) line++;
return(line);
}
int CJson::GetColumn() const
{
if(m_error_pos < 0) return(1);
int col = 1;
for(int i = m_error_pos - 1; i >= 0 && i < m_length; i--)
{
if(Buffer[i] == 0x0A) break;
col++;
}
return(col);
}
//+------------------------------------------------------------------+
//| ROOT ACCESS |
//+------------------------------------------------------------------+
CJsonValue CJson::Root()
{
if(!m_parsed || m_node_count == 0)
return(CJsonValue(NULL, -1));
return(CJsonValue(GetPointer(this), 0));
}
bool CJson::IsValid() const
{
return(m_parsed && m_node_count > 0);
}
CJsonValue CJson::operator[](string key)
{
return(Root()[key]);
}
CJsonValue CJson::operator[](int index)
{
return(Root()[index]);
}
bool CJson::Exists(string key)
{
return(Root().Exists(key));
}
int CJson::NodeCount() const
{
return(m_node_count);
}
//+------------------------------------------------------------------+
//| METODOS INTERNOS DE PARSEO |
//+------------------------------------------------------------------+
int CJson::AddNode(int parent, uchar type)
{
if(m_node_count >= m_node_cap)
{
int new_cap = m_node_cap * JSON_GROW_FACTOR;
if(new_cap < JSON_INIT_NODES) new_cap = JSON_INIT_NODES;
if(ArrayResize(m_nodes, new_cap) != new_cap)
{
m_error = JERR_NODE_LIMIT;
m_error_pos = m_pos;
return(-1);
}
m_node_cap = new_cap;
}
int idx = m_node_count;
m_node_count++;
m_nodes[idx].parent = parent;
m_nodes[idx].child = -1;
m_nodes[idx].next = -1;
m_nodes[idx].key_off = -1;
m_nodes[idx].key_len = 0;
m_nodes[idx].val_off = -1;
m_nodes[idx].val_len = 0;
m_nodes[idx].num_val = 0.0;
m_nodes[idx].type = type;
m_nodes[idx].flags = 0;
m_nodes[idx].child_cnt = 0;
// Enlazar con el padre
if(parent >= 0 && parent < m_node_count)
{
if(m_nodes[parent].child < 0)
m_nodes[parent].child = idx;
else
{
int last = m_nodes[parent].child;
while(m_nodes[last].next >= 0)
last = m_nodes[last].next;
m_nodes[last].next = idx;
}
m_nodes[parent].child_cnt++;
}
return(idx);
}
void CJson::SkipWhitespace()
{
while(m_pos < m_length)
{
if(Buffer[m_pos] > 32) // CAMBIADO antes usaba la tabla..
return;
m_pos++;
}
}
void CJson::SetError(int err)
{
m_error = err;
m_error_pos = m_pos;
}
//+------------------------------------------------------------------+
//| PARSEADOR PRINCIPAL |
//+------------------------------------------------------------------+
bool CJson::ParseValue(int parent)
{
SkipWhitespace();
if(m_pos >= m_length)
{
SetError(JERR_UNEXPECTED_END);
return(false);
}
uchar c = Buffer[m_pos];
uchar cls = s_char_class[c];
switch(cls)
{
case _JC_STRUCT:
{
if(c == '{') return(ParseObject(parent));
if(c == '[') return(ParseArray(parent));
SetError(JERR_UNEXPECTED_CHAR);
return(false);
}
case _JC_QUOTE:
return(ParseString(parent, false));
case _JC_DIGIT:
case _JC_MINUS:
return(ParseNumber(parent));
case _JC_LIT:
return(ParseLiteral(parent, c));
default:
SetError(JERR_UNEXPECTED_CHAR);
return(false);
}
}
bool CJson::ParseObject(int parent)
{
m_pos++;
int obj_node = AddNode(parent, (uchar)JT_OBJECT);
if(obj_node < 0) return(false);
SkipWhitespace();
if(m_pos < m_length && Buffer[m_pos] == '}')
{
m_pos++;
return(true);
}
while(true)
{
SkipWhitespace();
if(m_pos >= m_length)
{
SetError(JERR_UNEXPECTED_END);
return(false);
}
if(Buffer[m_pos] != '"')
{
if(Buffer[m_pos] == '}')
{
m_pos++;
return(true);
}
SetError(JERR_EXPECTED_KEY);
return(false);
}
if(!ParseString(obj_node, true))
return(false);
SkipWhitespace();
if(m_pos >= m_length)
{
SetError(JERR_UNEXPECTED_END);
return(false);
}
if(Buffer[m_pos] != ':')
{
SetError(JERR_EXPECTED_COLON);
return(false);
}
m_pos++;
SkipWhitespace();
if(!ParseValue(obj_node))
return(false);
SkipWhitespace();
if(m_pos >= m_length)
{
SetError(JERR_UNEXPECTED_END);
return(false);
}
if(Buffer[m_pos] == ',')
{
m_pos++;
continue;
}
else if(Buffer[m_pos] == '}')
{
m_pos++;
return(true);
}
else
{
SetError(JERR_EXPECTED_COMMA);
return(false);
}
}
SetError(JERR_UNEXPECTED_END);
return(false);
}
bool CJson::ParseArray(int parent)
{
m_pos++;
int arr_node = AddNode(parent, (uchar)JT_ARRAY);
if(arr_node < 0) return(false);
SkipWhitespace();
if(m_pos < m_length && Buffer[m_pos] == ']')
{
m_pos++;
return(true);
}
while(true)
{
SkipWhitespace();
if(!ParseValue(arr_node))
return(false);
SkipWhitespace();
if(m_pos >= m_length)
{
SetError(JERR_UNEXPECTED_END);
return(false);
}
if(Buffer[m_pos] == ',')
{
m_pos++;
continue;
}
else if(Buffer[m_pos] == ']')
{
m_pos++;
return(true);
}
else
{
SetError(JERR_EXPECTED_COMMA);
return(false);
}
}
SetError(JERR_UNEXPECTED_END);
return(false);
}
bool CJson::ParseString(int parent, bool is_key)
{
m_pos++; // consumir '"'
int content_start = m_pos;
bool has_escapes = false;
while(m_pos < m_length)
{
uchar c = Buffer[m_pos];
if(c == '"')
{
int content_len = m_pos - content_start;
int str_node = AddNode(parent, (uchar)JT_STRING);
if(str_node < 0) return(false);
if(is_key)
{
m_nodes[str_node].key_off = content_start;
m_nodes[str_node].key_len = content_len;
}
else
{
m_nodes[str_node].val_off = content_start;
m_nodes[str_node].val_len = content_len;
}
if(has_escapes)
m_nodes[str_node].flags |= 1;
m_pos++;
return(true);
}
if(c == '\\')
{
has_escapes = true;
m_pos+=2;
continue;
}
/* else if(c < 0x20) CODIGO CORREGIDO DADO QUE DABA ERROR:
2026.06.28 08:13:50.783 BenMqlLite (EURUSD,MN1) Secuencia de escape invalida
2026.06.28 08:13:50.783 BenMqlLite (EURUSD,MN1) 6031 | 28
{
SetError(JERR_INVALID_STRING);
return(false);
}
else*/
m_pos++;
}
SetError(JERR_UNEXPECTED_END);
return(false);
}
bool CJson::ParseNumber(int parent)
{
int start = m_pos;
bool is_float = false;
if(Buffer[m_pos] == '-')
m_pos++;
if(m_pos >= m_length)
{
SetError(JERR_UNEXPECTED_END);
return(false);
}
if(Buffer[m_pos] == '0')
m_pos++;
else if(Buffer[m_pos] >= '1' && Buffer[m_pos] <= '9')
{
m_pos++;
while(m_pos < m_length && Buffer[m_pos] >= '0' && Buffer[m_pos] <= '9')
m_pos++;
}
else
{
SetError(JERR_INVALID_NUMBER);
return(false);
}
if(m_pos < m_length && Buffer[m_pos] == '.')
{
is_float = true;
m_pos++;
if(m_pos >= m_length || Buffer[m_pos] < '0' || Buffer[m_pos] > '9')
{
SetError(JERR_INVALID_NUMBER);
return(false);
}
while(m_pos < m_length && Buffer[m_pos] >= '0' && Buffer[m_pos] <= '9')
m_pos++;
}
if(m_pos < m_length && (Buffer[m_pos] == 'e' || Buffer[m_pos] == 'E'))
{
is_float = true;
m_pos++;
if(m_pos < m_length && (Buffer[m_pos] == '+' || Buffer[m_pos] == '-'))
m_pos++;
if(m_pos >= m_length || Buffer[m_pos] < '0' || Buffer[m_pos] > '9')
{
SetError(JERR_INVALID_NUMBER);
return(false);
}
while(m_pos < m_length && Buffer[m_pos] >= '0' && Buffer[m_pos] <= '9')
m_pos++;
}
string num_str = CharArrayToString(Buffer, start, m_pos - start);
double value = StringToDouble(num_str);
uchar node_type = is_float ? (uchar)JT_DOUBLE : (uchar)JT_INT;
int num_node = AddNode(parent, node_type);
if(num_node < 0) return(false);
m_nodes[num_node].num_val = value;
m_nodes[num_node].val_off = start;
m_nodes[num_node].val_len = m_pos - start;
return(true);
}
bool CJson::ParseLiteral(int parent, uchar first)
{
if(first == 't')
{
if(m_pos + 4 > m_length) { SetError(JERR_UNEXPECTED_END); return(false); }
if(Buffer[m_pos+1] != 'r' || Buffer[m_pos+2] != 'u' || Buffer[m_pos+3] != 'e')
{ SetError(JERR_UNEXPECTED_CHAR); return(false); }
int node = AddNode(parent, (uchar)JT_BOOL);
if(node < 0) return(false);
m_nodes[node].num_val = 1.0;
m_nodes[node].val_off = m_pos;
m_nodes[node].val_len = 4;
m_pos += 4;
}
else if(first == 'f')
{
if(m_pos + 5 > m_length) { SetError(JERR_UNEXPECTED_END); return(false); }
if(Buffer[m_pos+1] != 'a' || Buffer[m_pos+2] != 'l' || Buffer[m_pos+3] != 's' || Buffer[m_pos+4] != 'e')
{ SetError(JERR_UNEXPECTED_CHAR); return(false); }
int node = AddNode(parent, (uchar)JT_BOOL);
if(node < 0) return(false);
m_nodes[node].num_val = 0.0;
m_nodes[node].val_off = m_pos;
m_nodes[node].val_len = 5;
m_pos += 5;
}
else if(first == 'n')
{
if(m_pos + 4 > m_length) { SetError(JERR_UNEXPECTED_END); return(false); }
if(Buffer[m_pos+1] != 'u' || Buffer[m_pos+2] != 'l' || Buffer[m_pos+3] != 'l')
{ SetError(JERR_UNEXPECTED_CHAR); return(false); }
int node = AddNode(parent, (uchar)JT_NULL);
if(node < 0) return(false);
m_nodes[node].val_off = m_pos;
m_nodes[node].val_len = 4;
m_pos += 4;
}
else
{
SetError(JERR_UNEXPECTED_CHAR);
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
//| EXTRACCION DE VALORES |
//+------------------------------------------------------------------+
string CJson::ExtractNodeString(int node_idx)
{
if(node_idx < 0 || node_idx >= m_node_count)
return("");
uchar t = m_nodes[node_idx].type;
int off = m_nodes[node_idx].val_off;
int len = m_nodes[node_idx].val_len;
if(t == JT_STRING)
{
if(off < 0 || len <= 0)
return("");
bool has_esc = (m_nodes[node_idx].flags & 1) != 0;
return(ExtractJsonString(off, len, has_esc));
}
else if(t == JT_INT || t == JT_DOUBLE)
{
if(off >= 0 && len > 0)
return(CharArrayToString(Buffer, off, len));
return(DoubleToString(m_nodes[node_idx].num_val, 8));
}
else if(t == JT_BOOL)
return(m_nodes[node_idx].num_val != 0.0 ? "true" : "false");
else if(t == JT_NULL)
return("null");
else if(t == JT_OBJECT)
return("[object]");
else if(t == JT_ARRAY)
return("[array]");
return("");
}
string CJson::ExtractJsonString(int offset, int length, bool has_escapes)
{
if(length <= 0) return("");
if(!has_escapes)
return(CharArrayToString(Buffer, offset, length));
uchar result[];
ArrayResize(result, length + 8);
int ri = 0;
int i = 0;
while(i < length)
{
uchar c = Buffer[offset + i];
if(c == '\\')
{
i++;
if(i >= length) break;
uchar esc = Buffer[offset + i];
switch(esc)
{
case '"': result[ri++] = 0x22; break;
case '\\': result[ri++] = 0x5C; break;
case '/': result[ri++] = 0x2F; break;
case 'b': result[ri++] = 0x08; break;
case 'f': result[ri++] = 0x0C; break;
case 'n': result[ri++] = 0x0A; break;
case 'r': result[ri++] = 0x0D; break;
case 't': result[ri++] = 0x09; break;
case 'u':
{
i++;
int hex_val = 0;
for(int j = 0; j < 4; j++)
{
uchar h = Buffer[offset + i];
hex_val = (hex_val << 4);
if(h >= '0' && h <= '9')
hex_val += (h - '0');
else if(h >= 'A' && h <= 'F')
hex_val += (h - 'A' + 10);
else if(h >= 'a' && h <= 'f')
hex_val += (h - 'a' + 10);
i++;
}
i--;
if(hex_val < 0x80)
result[ri++] = (uchar)hex_val;
else if(hex_val < 0x800)
{
result[ri++] = (uchar)(0xC0 | (hex_val >> 6));
result[ri++] = (uchar)(0x80 | (hex_val & 0x3F));
}
else
{
result[ri++] = (uchar)(0xE0 | (hex_val >> 12));
result[ri++] = (uchar)(0x80 | ((hex_val >> 6) & 0x3F));
result[ri++] = (uchar)(0x80 | (hex_val & 0x3F));
}
break;
}
default:
result[ri++] = esc;
break;
}
i++;
}
else
{
result[ri++] = c;
i++;
}
}
if(ri == 0) return("");
return(CharArrayToString(result, 0, ri));
}
//+------------------------------------------------------------------+
//| NAVEGACION |
//+------------------------------------------------------------------+
int CJson::FindChildByKey(int node_idx, string key)
{
if(node_idx < 0 || node_idx >= m_node_count) return(-1);
if(m_nodes[node_idx].type != JT_OBJECT) return(-1);
int child = m_nodes[node_idx].child;
while(child >= 0)
{
int koff = m_nodes[child].key_off;
int klen = m_nodes[child].key_len;
bool hes = (m_nodes[child].flags & 1) != 0;
if(koff >= 0 && klen > 0)
{
string child_key = ExtractJsonString(koff, klen, hes);
if(child_key == key)
return(child);
}
child = m_nodes[child].next;
}
return(-1);
}
int CJson::FindChildByIndex(int node_idx, int index)
{
if(node_idx < 0 || node_idx >= m_node_count) return(-1);
uchar t = m_nodes[node_idx].type;
if(t != JT_ARRAY && t != JT_OBJECT) return(-1);
if(index < 0 || (uint)index >= (uint)m_nodes[node_idx].child_cnt) return(-1);
int child = m_nodes[node_idx].child;
for(int i = 0; i < index && child >= 0; i++)
child = m_nodes[child].next;
return(child);
}
string CJson::GetChildKey(int node_idx, int index)
{
int child = FindChildByIndex(node_idx, index);
if(child < 0) return("");
int koff = m_nodes[child].key_off;
int klen = m_nodes[child].key_len;
bool hes = (m_nodes[child].flags & 1) != 0;
if(koff < 0 || klen <= 0) return("");
return(ExtractJsonString(koff, klen, hes));
}
//+------------------------------------------------------------------+