şundan çatallanmış 14134597/fast_json
1755 satır
EOL bulunamadı
56 KiB
MQL5
1755 satır
EOL bulunamadı
56 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| ClaudeJson.mqh |
|
|
//| Copyright 2026, Niquel Mendoza. |
|
|
//| https://www.mql5.com/ |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2026, Niquel Mendoza."
|
|
#property link "https://www.mql5.com/"
|
|
#property strict
|
|
|
|
namespace CLAUDEJSON
|
|
{
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
/*
|
|
# 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 |
|
|
//| Parser JSON de alto rendimiento en MQL5 puro (sin DLL) |
|
|
//| |
|
|
//| Caracteristicas principales (diseno orientado a velocidad): |
|
|
//| - Parser ITERATIVO con pila manual (sin recursion -> no se |
|
|
//| desborda la pila del sistema con documentos muy anidados). |
|
|
//| - ARENA de nodos: todos los nodos viven en un unico arreglo |
|
|
//| contiguo (cache-friendly). Crecimiento geometrico => muy |
|
|
//| pocas reasignaciones de memoria. |
|
|
//| - CERO COPIAS del texto: los strings y numeros se almacenan |
|
|
//| como (offset,longitud) dentro del propio Buffer. La |
|
|
//| decodificacion (escapes, \uXXXX, numeros) es PEREZOSA: solo |
|
|
//| se realiza cuando se consulta el valor. |
|
|
//| - Tablas de consulta (lookup tables) para clasificar bytes |
|
|
//| (espacios, fin-de-string, hex) en O(1) y con bucles internos |
|
|
//| muy ajustados. |
|
|
//| - Un solo recorrido (single pass) del documento. |
|
|
//| - Representacion en arbol persistente: se analiza UNA vez y se |
|
|
//| consulta tantas veces como se quiera sin volver a parsear. |
|
|
//| |
|
|
//| Compatibilidad: especificacion JSON completa (objetos, arrays, |
|
|
//| strings, numeros enteros/decimales/exponentes, true/false/null |
|
|
//| y todas las secuencias de escape incluyendo \uXXXX y pares |
|
|
//| sustitutos UTF-16). |
|
|
//| |
|
|
//| Uso minimo: |
|
|
//| #include "Json.mqh" |
|
|
//| CJson json; |
|
|
//| json.Parse("{\"symbol\":\"EURUSD\",\"price\":1.2345}"); |
|
|
//| if(json.HasError()) Print(json.GetErrorMessage()); |
|
|
//| string sym = json["symbol"].GetString(); |
|
|
//| double px = json["price"].ToDouble(); |
|
|
//| |
|
|
//| Uso sin copias (rellenando el Buffer directamente): |
|
|
//| CJson json; |
|
|
//| ArrayResize(json.Buffer, n); |
|
|
//| // ... rellenar json.Buffer[0..n-1] con tus bytes ... |
|
|
//| json.Parse(); |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef __JSON_MQH__
|
|
#define __JSON_MQH__
|
|
|
|
#define JSON_VERSION "1.00"
|
|
#define JSON_DEFAULT_MAX_DEPTH 256 // profundidad maxima de anidamiento
|
|
|
|
//--- bits del campo 'flags' de cada nodo
|
|
#define JFLAG_ESC 0x01 // el valor (string) contiene secuencias de escape
|
|
#define JFLAG_INT 0x02 // el numero es entero (sin '.', 'e' ni 'E')
|
|
#define JFLAG_KESC 0x04 // la clave del nodo contiene secuencias de escape
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Tipos de valor JSON |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_JSON_TYPE
|
|
{
|
|
JSON_UNDEFINED = 0, // nodo invalido / inexistente
|
|
JSON_OBJECT, // { ... }
|
|
JSON_ARRAY, // [ ... ]
|
|
JSON_STRING, // "..."
|
|
JSON_NUMBER, // 123 / -4.5 / 1e10
|
|
JSON_BOOL, // true / false
|
|
JSON_NULL // null
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Codigos de error |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_JSON_ERROR
|
|
{
|
|
JSON_OK = 0,
|
|
JSON_EMPTY, // buffer vacio
|
|
JSON_UNEXPECTED_CHARACTER, // caracter inesperado
|
|
JSON_UNEXPECTED_END, // fin de datos inesperado
|
|
JSON_INVALID_NUMBER, // numero mal formado
|
|
JSON_INVALID_STRING, // string con caracter de control sin escapar
|
|
JSON_INVALID_ESCAPE, // secuencia de escape invalida
|
|
JSON_INVALID_UNICODE, // escape \uXXXX invalido
|
|
JSON_EXPECTED_COLON, // se esperaba ':'
|
|
JSON_EXPECTED_COMMA, // se esperaba ',' o cierre
|
|
JSON_EXPECTED_KEY, // se esperaba una clave string
|
|
JSON_TRAILING_CHARACTERS, // datos extra tras el valor raiz
|
|
JSON_MAX_DEPTH // se supero la profundidad maxima
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Estados internos del automata de parseo |
|
|
//+------------------------------------------------------------------+
|
|
enum ENUM_JSON_STATE
|
|
{
|
|
JST_VALUE = 0, // se espera un valor (raiz / valor de miembro / tras ',')
|
|
JST_VALUE_OR_END, // dentro de array: valor o ']'
|
|
JST_KEY_OR_END, // dentro de objeto: clave o '}'
|
|
JST_KEY, // tras ',' en objeto: clave obligatoria
|
|
JST_COLON, // se espera ':'
|
|
JST_COMMA_OR_END, // se espera ',' o cierre del contenedor
|
|
JST_DONE // fin del valor raiz
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Nodo de la arena. POD compacto y contiguo (cache-friendly). |
|
|
//| Para STRING/NUMBER: (start,len) apuntan al texto crudo dentro |
|
|
//| del Buffer. Para OBJECT/ARRAY: lista enlazada de hijos. Cada |
|
|
//| miembro de objeto guarda su clave en (key,klen). |
|
|
//+------------------------------------------------------------------+
|
|
struct SJsonNode
|
|
{
|
|
char type; // ENUM_JSON_TYPE compactado
|
|
int start; // offset del texto del valor (string/number)
|
|
int len; // longitud del texto del valor; para BOOL: 1=true,0=false
|
|
int key; // offset de la clave (objeto) o -1
|
|
int klen; // longitud de la clave
|
|
int first; // indice del primer hijo (-1)
|
|
int last; // indice del ultimo hijo (-1) [uso interno: append O(1)]
|
|
int next; // indice del siguiente hermano (-1)
|
|
int count; // numero de hijos (objetos/arrays)
|
|
uchar flags; // JFLAG_*
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Tablas de consulta globales (se inicializan una sola vez). |
|
|
//+------------------------------------------------------------------+
|
|
uchar g_ws[256]; // 1 si el byte es espacio en blanco JSON
|
|
uchar g_strstop[256]; // 1 si el byte detiene el escaneo rapido de string
|
|
uchar g_hexval[256]; // valor hex 0..15, 255 si el byte no es hexadecimal
|
|
bool g_json_tables_ready = false;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Inicializa las tablas de consulta (idempotente). |
|
|
//+------------------------------------------------------------------+
|
|
void JsonInitTables(void)
|
|
{
|
|
if(g_json_tables_ready)
|
|
return;
|
|
for(int i=0;i<256;i++)
|
|
{
|
|
g_ws[i] = 0;
|
|
g_strstop[i] = 0;
|
|
g_hexval[i] = 255;
|
|
}
|
|
//--- espacios en blanco: espacio, tab, LF, CR
|
|
g_ws[0x20] = 1;
|
|
g_ws[0x09] = 1;
|
|
g_ws[0x0A] = 1;
|
|
g_ws[0x0D] = 1;
|
|
//--- bytes que detienen el escaneo rapido de string: controles, '"' y '\'
|
|
for(int i=0;i<0x20;i++)
|
|
g_strstop[i] = 1;
|
|
g_strstop[0x22] = 1; // "
|
|
g_strstop[0x5C] = 1; // backslash
|
|
//--- tabla de dígitos hexadecimales
|
|
for(int i='0';i<='9';i++)
|
|
g_hexval[i] = (uchar)(i-'0');
|
|
for(int i='a';i<='f';i++)
|
|
g_hexval[i] = (uchar)(10+i-'a');
|
|
for(int i='A';i<='F';i++)
|
|
g_hexval[i] = (uchar)(10+i-'A');
|
|
g_json_tables_ready = true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Codifica un punto de codigo Unicode a UTF-8 en out[pos..]. |
|
|
//| Devuelve la nueva posicion. (Hasta 4 bytes.) |
|
|
//+------------------------------------------------------------------+
|
|
int JsonUtf8Encode(const uint cp,uchar &out[],int pos)
|
|
{
|
|
if(cp<0x80)
|
|
out[pos++] = (uchar)cp;
|
|
else if(cp<0x800)
|
|
{
|
|
out[pos++] = (uchar)(0xC0 | (cp>>6));
|
|
out[pos++] = (uchar)(0x80 | (cp & 0x3F));
|
|
}
|
|
else if(cp<0x10000)
|
|
{
|
|
out[pos++] = (uchar)(0xE0 | (cp>>12));
|
|
out[pos++] = (uchar)(0x80 | ((cp>>6) & 0x3F));
|
|
out[pos++] = (uchar)(0x80 | (cp & 0x3F));
|
|
}
|
|
else
|
|
{
|
|
out[pos++] = (uchar)(0xF0 | (cp>>18));
|
|
out[pos++] = (uchar)(0x80 | ((cp>>12) & 0x3F));
|
|
out[pos++] = (uchar)(0x80 | ((cp>>6) & 0x3F));
|
|
out[pos++] = (uchar)(0x80 | (cp & 0x3F));
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
//--- Declaracion adelantada para que CJsonNode pueda referenciarla.
|
|
class CJson;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CJsonNode |
|
|
//| |
|
|
//| "Vista" ligera sobre un nodo del documento (puntero al documento |
|
|
//| + indice). Es muy barata de copiar, por lo que se devuelve por |
|
|
//| valor y permite encadenar accesos: json["a"]["b"][3].ToInt(). |
|
|
//| Si el nodo no existe, la vista queda invalida y los getters |
|
|
//| devuelven el valor por defecto (patron "null object", sin crash).|
|
|
//+------------------------------------------------------------------+
|
|
class CJsonNode
|
|
{
|
|
private:
|
|
CJson *m_doc; // documento propietario
|
|
int m_idx; // indice del nodo en la arena (-1 = invalido)
|
|
|
|
public:
|
|
CJsonNode(void) { m_doc=NULL; m_idx=-1; }
|
|
void Bind(CJson *doc,const int idx) { m_doc=doc; m_idx=idx; }
|
|
bool IsValid(void) const { return(m_doc!=NULL && m_idx>=0); }
|
|
int Index(void) const { return m_idx; }
|
|
|
|
//--- tipo
|
|
ENUM_JSON_TYPE Type(void);
|
|
bool IsObject(void);
|
|
bool IsArray(void);
|
|
bool IsString(void);
|
|
bool IsNumber(void);
|
|
bool IsBool(void);
|
|
bool IsNull(void);
|
|
bool IsInt(void);
|
|
|
|
//--- tamano / pertenencia
|
|
int Size(void);
|
|
int Count(void);
|
|
bool Exists(const string key);
|
|
|
|
//--- clave de este nodo (cuando es miembro de un objeto)
|
|
string Key(void);
|
|
|
|
//--- navegacion (operadores y equivalentes explicitos)
|
|
CJsonNode operator[](const string key);
|
|
CJsonNode operator[](const int index);
|
|
CJsonNode Get(const string key); // equivalente a operator[](string)
|
|
CJsonNode At(const int index); // equivalente a operator[](int)
|
|
CJsonNode Child(const int index); // alias de At()
|
|
string ChildKey(const int index);// clave del hijo i-esimo
|
|
|
|
//--- lectura de valores
|
|
string GetString(const string def="");
|
|
long ToLong(const long def=0);
|
|
int ToInt(const int def=0);
|
|
double ToDouble(const double def=0.0);
|
|
bool ToBool(const bool def=false);
|
|
|
|
//--- serializacion del subarbol
|
|
string ToString(void);
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CJson |
|
|
//| |
|
|
//| Documento JSON. Posee el Buffer de entrada y la arena de nodos. |
|
|
//+------------------------------------------------------------------+
|
|
class CJson
|
|
{
|
|
public:
|
|
//--- Buffer publico de bytes de entrada (UTF-8). Se puede rellenar
|
|
//--- directamente para evitar copias, o usar Parse(string)/SetText.
|
|
uchar Buffer[];
|
|
|
|
private:
|
|
//--- arena de nodos
|
|
SJsonNode m_nodes[];
|
|
int m_count; // nodos usados
|
|
int m_cap; // capacidad asignada
|
|
int m_root; // indice del nodo raiz (-1)
|
|
|
|
//--- estado de error
|
|
ENUM_JSON_ERROR m_error;
|
|
int m_errpos; // posicion (byte) del error
|
|
|
|
//--- estado de parseo
|
|
int m_pos; // posicion actual en Buffer
|
|
int m_len; // longitud logica del Buffer
|
|
int m_pk; // clave pendiente: offset
|
|
int m_pkl; // clave pendiente: longitud
|
|
bool m_pke; // clave pendiente: tiene escapes
|
|
|
|
//--- pila de contenedores (indices de nodo)
|
|
int m_stack[];
|
|
int m_sp; // puntero de pila (numero de elementos)
|
|
int m_maxdepth;
|
|
|
|
//========================= construccion ==========================
|
|
void ReserveNodes(const int need);
|
|
int NewNode(const char t);
|
|
void AppendChild(const int parent,const int child);
|
|
void Emit(const int idx);
|
|
void Push(const int idx);
|
|
|
|
//========================= lexer / parser ========================
|
|
bool Fail(const ENUM_JSON_ERROR e);
|
|
void SkipWs(void);
|
|
int Cur(void) { return(m_pos<m_len ? (int)Buffer[m_pos] : -1); }
|
|
bool ParseStringToken(int &oStart,int &oLen,bool &oEsc);
|
|
bool ParseNumberToken(int &oStart,int &oLen,bool &oInt);
|
|
|
|
//========================= decodificacion ========================
|
|
string RawRange(const int start,const int len);
|
|
string DecodeRange(const int start,const int len,const bool esc);
|
|
|
|
public:
|
|
CJson(void);
|
|
~CJson(void) {}
|
|
|
|
//========================= ciclo de vida =========================
|
|
bool Parse(void); // analiza el Buffer actual
|
|
bool Parse(const string text); // rellena el Buffer y analiza
|
|
void SetText(const string text); // rellena el Buffer (UTF-8)
|
|
void Reset(void); // reinicia contadores (conserva memoria)
|
|
void Clear(void); // libera toda la memoria interna
|
|
|
|
//========================= errores ===============================
|
|
bool HasError(void) const { return(m_error!=JSON_OK); }
|
|
ENUM_JSON_ERROR GetError(void) const { return m_error; }
|
|
int GetErrorPosition(void) const { return m_errpos; }
|
|
int GetLine(void);
|
|
int GetColumn(void);
|
|
string GetErrorMessage(void);
|
|
|
|
//========================= raiz / diagnostico ====================
|
|
CJsonNode Root(void);
|
|
int RootIndex(void) const { return m_root; }
|
|
int NodeCount(void) const { return m_count; }
|
|
int Capacity(void) const { return m_cap; }
|
|
int MaxDepth(void) const { return m_maxdepth; }
|
|
void SetMaxDepth(const int d) { m_maxdepth=(d>0?d:1); }
|
|
|
|
//--- atajos que operan sobre la raiz
|
|
ENUM_JSON_TYPE GetType(void) { return TypeAt(m_root); }
|
|
bool IsObject(void) { return(TypeAt(m_root)==JSON_OBJECT); }
|
|
bool IsArray(void) { return(TypeAt(m_root)==JSON_ARRAY); }
|
|
bool IsNull(void) { return(TypeAt(m_root)==JSON_NULL); }
|
|
int Size(void) { return CountAt(m_root); }
|
|
int Count(void) { return CountAt(m_root); }
|
|
bool Exists(const string key) { return(FindChildKey(m_root,key)>=0); }
|
|
string GetString(const string def="") { return StrAt(m_root,def); }
|
|
long ToLong(const long def=0) { return IntAt(m_root,def); }
|
|
int ToInt(const int def=0) { return (int)IntAt(m_root,(long)def); }
|
|
double ToDouble(const double def=0.0){ return DblAt(m_root,def); }
|
|
bool ToBool(const bool def=false){ return BoolAt(m_root,def); }
|
|
string Serialize(void);
|
|
|
|
//--- operadores de acceso desde la raiz
|
|
CJsonNode operator[](const string key);
|
|
CJsonNode operator[](const int index);
|
|
//--- equivalentes explicitos (utiles si algun build rechaza operator[])
|
|
CJsonNode Get(const string key);
|
|
CJsonNode At(const int index);
|
|
|
|
//========================= API por indice de nodo =================
|
|
// (Publica: la usa CJsonNode. Tambien util para iterar a bajo nivel.)
|
|
ENUM_JSON_TYPE TypeAt(const int idx);
|
|
int CountAt(const int idx);
|
|
bool IsIntAt(const int idx);
|
|
string KeyAt(const int idx);
|
|
int FindChildKey(const int idx,const string key);
|
|
int FindChildIndex(const int idx,int i);
|
|
string StrAt(const int idx,const string def);
|
|
long IntAt(const int idx,const long def);
|
|
double DblAt(const int idx,const double def);
|
|
bool BoolAt(const int idx,const bool def);
|
|
void SerializeNode(const int idx,string &out);
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CJson::CJson(void)
|
|
{
|
|
m_count = 0;
|
|
m_cap = 0;
|
|
m_root = -1;
|
|
m_error = JSON_OK;
|
|
m_errpos = 0;
|
|
m_pos = 0;
|
|
m_len = 0;
|
|
m_pk = 0;
|
|
m_pkl = 0;
|
|
m_pke = false;
|
|
m_sp = 0;
|
|
m_maxdepth = JSON_DEFAULT_MAX_DEPTH;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reinicia el estado conservando la memoria (para reutilizar). |
|
|
//+------------------------------------------------------------------+
|
|
void CJson::Reset(void)
|
|
{
|
|
m_count = 0;
|
|
m_root = -1;
|
|
m_error = JSON_OK;
|
|
m_errpos = 0;
|
|
m_pos = 0;
|
|
m_sp = 0;
|
|
m_pk = 0;
|
|
m_pkl = 0;
|
|
m_pke = false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Libera toda la memoria interna (la arena y la pila). |
|
|
//| No toca Buffer (es propiedad del usuario). |
|
|
//+------------------------------------------------------------------+
|
|
void CJson::Clear(void)
|
|
{
|
|
ArrayFree(m_nodes);
|
|
ArrayFree(m_stack);
|
|
m_cap = 0;
|
|
Reset();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Garantiza capacidad para 'need' nodos (crecimiento geometrico). |
|
|
//+------------------------------------------------------------------+
|
|
void CJson::ReserveNodes(const int need)
|
|
{
|
|
if(need<=m_cap)
|
|
return;
|
|
int nc = (m_cap<64 ? 64 : m_cap);
|
|
while(nc<need)
|
|
nc *= 2;
|
|
ArrayResize(m_nodes,nc);
|
|
m_cap = nc;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Crea un nodo nuevo del tipo dado y devuelve su indice. |
|
|
//+------------------------------------------------------------------+
|
|
int CJson::NewNode(const char t)
|
|
{
|
|
if(m_count>=m_cap)
|
|
ReserveNodes(m_count+1);
|
|
int idx = m_count++;
|
|
m_nodes[idx].type = t;
|
|
m_nodes[idx].start = 0;
|
|
m_nodes[idx].len = 0;
|
|
m_nodes[idx].key = -1;
|
|
m_nodes[idx].klen = 0;
|
|
m_nodes[idx].first = -1;
|
|
m_nodes[idx].last = -1;
|
|
m_nodes[idx].next = -1;
|
|
m_nodes[idx].count = 0;
|
|
m_nodes[idx].flags = 0;
|
|
return idx;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Anexa 'child' como ultimo hijo de 'parent' en O(1). |
|
|
//+------------------------------------------------------------------+
|
|
void CJson::AppendChild(const int parent,const int child)
|
|
{
|
|
m_nodes[child].next = -1;
|
|
if(m_nodes[parent].first==-1)
|
|
{
|
|
m_nodes[parent].first = child;
|
|
m_nodes[parent].last = child;
|
|
}
|
|
else
|
|
{
|
|
m_nodes[m_nodes[parent].last].next = child;
|
|
m_nodes[parent].last = child;
|
|
}
|
|
m_nodes[parent].count++;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Inserta un nodo-valor en el contenedor actual (o como raiz). |
|
|
//| Si el padre es un objeto, asocia la clave pendiente al nodo. |
|
|
//+------------------------------------------------------------------+
|
|
void CJson::Emit(const int idx)
|
|
{
|
|
if(m_sp==0)
|
|
{
|
|
m_root = idx;
|
|
return;
|
|
}
|
|
int top = m_stack[m_sp-1];
|
|
if(m_nodes[top].type==JSON_OBJECT)
|
|
{
|
|
m_nodes[idx].key = m_pk;
|
|
m_nodes[idx].klen = m_pkl;
|
|
if(m_pke)
|
|
m_nodes[idx].flags |= JFLAG_KESC;
|
|
}
|
|
AppendChild(top,idx);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Apila un contenedor (con crecimiento geometrico de la pila). |
|
|
//+------------------------------------------------------------------+
|
|
void CJson::Push(const int idx)
|
|
{
|
|
int cap = ArraySize(m_stack);
|
|
if(m_sp>=cap)
|
|
{
|
|
int nc = (cap<64 ? 64 : cap*2);
|
|
ArrayResize(m_stack,nc);
|
|
}
|
|
m_stack[m_sp++] = idx;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Registra el primer error (los siguientes se ignoran). |
|
|
//+------------------------------------------------------------------+
|
|
bool CJson::Fail(const ENUM_JSON_ERROR e)
|
|
{
|
|
if(m_error==JSON_OK)
|
|
{
|
|
m_error = e;
|
|
m_errpos = m_pos;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Salta espacios en blanco (bucle interno ajustado con tabla). |
|
|
//+------------------------------------------------------------------+
|
|
void CJson::SkipWs(void)
|
|
{
|
|
int p = m_pos;
|
|
int L = m_len;
|
|
while(p<L && g_ws[Buffer[p]]!=0)
|
|
p++;
|
|
m_pos = p;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Analiza un token string (m_pos apunta a la comilla de apertura). |
|
|
//| Devuelve (offset,longitud) del CONTENIDO (sin comillas) y si |
|
|
//| contiene escapes. No decodifica nada aqui (decodificacion lazy). |
|
|
//+------------------------------------------------------------------+
|
|
bool CJson::ParseStringToken(int &oStart,int &oLen,bool &oEsc)
|
|
{
|
|
m_pos++; // consume la comilla de apertura
|
|
int start = m_pos;
|
|
bool esc = false;
|
|
int p = m_pos;
|
|
int L = m_len;
|
|
for(;;)
|
|
{
|
|
//--- bucle interno rapido: avanza mientras no haya un byte de parada
|
|
while(p<L && g_strstop[Buffer[p]]==0)
|
|
p++;
|
|
if(p>=L)
|
|
{
|
|
m_pos = p;
|
|
return Fail(JSON_UNEXPECTED_END);
|
|
}
|
|
uchar c = Buffer[p];
|
|
if(c=='"')
|
|
{
|
|
oStart = start;
|
|
oLen = p-start;
|
|
oEsc = esc;
|
|
m_pos = p+1; // consume la comilla de cierre
|
|
return true;
|
|
}
|
|
if(c=='\\')
|
|
{
|
|
esc = true;
|
|
p++;
|
|
if(p>=L)
|
|
{
|
|
m_pos = p;
|
|
return Fail(JSON_UNEXPECTED_END);
|
|
}
|
|
uchar e = Buffer[p];
|
|
if(e=='"' || e=='\\' || e=='/' || e=='b' || e=='f' || e=='n' || e=='r' || e=='t')
|
|
p++;
|
|
else if(e=='u')
|
|
{
|
|
if(p+4>=L)
|
|
{
|
|
m_pos = p;
|
|
return Fail(JSON_UNEXPECTED_END);
|
|
}
|
|
if(g_hexval[Buffer[p+1]]==255 || g_hexval[Buffer[p+2]]==255 ||
|
|
g_hexval[Buffer[p+3]]==255 || g_hexval[Buffer[p+4]]==255)
|
|
{
|
|
m_pos = p;
|
|
return Fail(JSON_INVALID_UNICODE);
|
|
}
|
|
p += 5;
|
|
}
|
|
else
|
|
{
|
|
m_pos = p;
|
|
return Fail(JSON_INVALID_ESCAPE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- caracter de control < 0x20 sin escapar
|
|
m_pos = p;
|
|
return Fail(JSON_INVALID_STRING);
|
|
}
|
|
}
|
|
return false; // inalcanzable
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Analiza y valida un token numerico segun la gramatica JSON. |
|
|
//| No convierte a numero (conversion lazy en ToDouble/ToLong). |
|
|
//+------------------------------------------------------------------+
|
|
bool CJson::ParseNumberToken(int &oStart,int &oLen,bool &oInt)
|
|
{
|
|
int start = m_pos;
|
|
int p = m_pos;
|
|
int L = m_len;
|
|
bool isint = true;
|
|
|
|
if(p<L && Buffer[p]=='-')
|
|
p++;
|
|
if(p>=L)
|
|
{
|
|
m_pos = p;
|
|
return Fail(JSON_INVALID_NUMBER);
|
|
}
|
|
//--- parte entera
|
|
if(Buffer[p]=='0')
|
|
p++; // un solo 0 (no se permiten ceros a la izquierda)
|
|
else if(Buffer[p]>='1' && Buffer[p]<='9')
|
|
{
|
|
p++;
|
|
while(p<L && Buffer[p]>='0' && Buffer[p]<='9')
|
|
p++;
|
|
}
|
|
else
|
|
{
|
|
m_pos = p;
|
|
return Fail(JSON_INVALID_NUMBER);
|
|
}
|
|
//--- parte fraccionaria
|
|
if(p<L && Buffer[p]=='.')
|
|
{
|
|
isint = false;
|
|
p++;
|
|
if(p>=L || Buffer[p]<'0' || Buffer[p]>'9')
|
|
{
|
|
m_pos = p;
|
|
return Fail(JSON_INVALID_NUMBER);
|
|
}
|
|
while(p<L && Buffer[p]>='0' && Buffer[p]<='9')
|
|
p++;
|
|
}
|
|
//--- exponente
|
|
if(p<L && (Buffer[p]=='e' || Buffer[p]=='E'))
|
|
{
|
|
isint = false;
|
|
p++;
|
|
if(p<L && (Buffer[p]=='+' || Buffer[p]=='-'))
|
|
p++;
|
|
if(p>=L || Buffer[p]<'0' || Buffer[p]>'9')
|
|
{
|
|
m_pos = p;
|
|
return Fail(JSON_INVALID_NUMBER);
|
|
}
|
|
while(p<L && Buffer[p]>='0' && Buffer[p]<='9')
|
|
p++;
|
|
}
|
|
oStart = start;
|
|
oLen = p-start;
|
|
oInt = isint;
|
|
m_pos = p;
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Rellena el Buffer a partir de un string (codificacion UTF-8). |
|
|
//+------------------------------------------------------------------+
|
|
void CJson::SetText(const string text)
|
|
{
|
|
StringToCharArray(text,Buffer,0,WHOLE_ARRAY,CP_UTF8);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Atajo: rellena el Buffer y analiza. |
|
|
//+------------------------------------------------------------------+
|
|
bool CJson::Parse(const string text)
|
|
{
|
|
SetText(text);
|
|
return Parse();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Analiza el Buffer actual. Nucleo iterativo (sin recursion). |
|
|
//+------------------------------------------------------------------+
|
|
bool CJson::Parse(void)
|
|
{
|
|
JsonInitTables();
|
|
Reset();
|
|
m_len = ArraySize(Buffer);
|
|
if(m_len<=0)
|
|
return Fail(JSON_EMPTY);
|
|
|
|
//--- reserva heuristica: evita la mayoria de reasignaciones
|
|
ReserveNodes(m_len/8+16);
|
|
if(ArraySize(m_stack)<64)
|
|
ArrayResize(m_stack,64);
|
|
|
|
int state = JST_VALUE;
|
|
|
|
while(true)
|
|
{
|
|
if(state==JST_DONE)
|
|
break;
|
|
SkipWs();
|
|
int c = Cur();
|
|
|
|
//================= se espera un VALOR ======================
|
|
if(state==JST_VALUE || state==JST_VALUE_OR_END)
|
|
{
|
|
if(state==JST_VALUE_OR_END && c==']') // array vacio o cierre
|
|
{
|
|
m_pos++;
|
|
m_sp--;
|
|
state = (m_sp==0 ? JST_DONE : JST_COMMA_OR_END);
|
|
continue;
|
|
}
|
|
if(c==-1)
|
|
return Fail(JSON_UNEXPECTED_END);
|
|
|
|
if(c=='{')
|
|
{
|
|
m_pos++;
|
|
int idx = NewNode((char)JSON_OBJECT);
|
|
Emit(idx);
|
|
if(m_sp>=m_maxdepth)
|
|
return Fail(JSON_MAX_DEPTH);
|
|
Push(idx);
|
|
state = JST_KEY_OR_END;
|
|
continue;
|
|
}
|
|
if(c=='[')
|
|
{
|
|
m_pos++;
|
|
int idx = NewNode((char)JSON_ARRAY);
|
|
Emit(idx);
|
|
if(m_sp>=m_maxdepth)
|
|
return Fail(JSON_MAX_DEPTH);
|
|
Push(idx);
|
|
state = JST_VALUE_OR_END;
|
|
continue;
|
|
}
|
|
if(c=='"')
|
|
{
|
|
int st,ln; bool esc;
|
|
if(!ParseStringToken(st,ln,esc))
|
|
return false;
|
|
int idx = NewNode((char)JSON_STRING);
|
|
m_nodes[idx].start = st;
|
|
m_nodes[idx].len = ln;
|
|
if(esc)
|
|
m_nodes[idx].flags |= JFLAG_ESC;
|
|
Emit(idx);
|
|
state = (m_sp==0 ? JST_DONE : JST_COMMA_OR_END);
|
|
continue;
|
|
}
|
|
if(c=='t')
|
|
{
|
|
if(m_pos+4>m_len || Buffer[m_pos+1]!='r' || Buffer[m_pos+2]!='u' || Buffer[m_pos+3]!='e')
|
|
return Fail(JSON_UNEXPECTED_CHARACTER);
|
|
m_pos += 4;
|
|
int idx = NewNode((char)JSON_BOOL);
|
|
m_nodes[idx].len = 1; // true
|
|
Emit(idx);
|
|
state = (m_sp==0 ? JST_DONE : JST_COMMA_OR_END);
|
|
continue;
|
|
}
|
|
if(c=='f')
|
|
{
|
|
if(m_pos+5>m_len || Buffer[m_pos+1]!='a' || Buffer[m_pos+2]!='l' ||
|
|
Buffer[m_pos+3]!='s' || Buffer[m_pos+4]!='e')
|
|
return Fail(JSON_UNEXPECTED_CHARACTER);
|
|
m_pos += 5;
|
|
int idx = NewNode((char)JSON_BOOL);
|
|
m_nodes[idx].len = 0; // false
|
|
Emit(idx);
|
|
state = (m_sp==0 ? JST_DONE : JST_COMMA_OR_END);
|
|
continue;
|
|
}
|
|
if(c=='n')
|
|
{
|
|
if(m_pos+4>m_len || Buffer[m_pos+1]!='u' || Buffer[m_pos+2]!='l' || Buffer[m_pos+3]!='l')
|
|
return Fail(JSON_UNEXPECTED_CHARACTER);
|
|
m_pos += 4;
|
|
int idx = NewNode((char)JSON_NULL);
|
|
Emit(idx);
|
|
state = (m_sp==0 ? JST_DONE : JST_COMMA_OR_END);
|
|
continue;
|
|
}
|
|
if(c=='-' || (c>='0' && c<='9'))
|
|
{
|
|
int st,ln; bool isint;
|
|
if(!ParseNumberToken(st,ln,isint))
|
|
return false;
|
|
int idx = NewNode((char)JSON_NUMBER);
|
|
m_nodes[idx].start = st;
|
|
m_nodes[idx].len = ln;
|
|
if(isint)
|
|
m_nodes[idx].flags |= JFLAG_INT;
|
|
Emit(idx);
|
|
state = (m_sp==0 ? JST_DONE : JST_COMMA_OR_END);
|
|
continue;
|
|
}
|
|
return Fail(JSON_UNEXPECTED_CHARACTER);
|
|
}
|
|
//================= objeto: clave o cierre ===================
|
|
else if(state==JST_KEY_OR_END)
|
|
{
|
|
if(c=='}')
|
|
{
|
|
m_pos++;
|
|
m_sp--;
|
|
state = (m_sp==0 ? JST_DONE : JST_COMMA_OR_END);
|
|
continue;
|
|
}
|
|
if(c=='"')
|
|
{
|
|
int st,ln; bool esc;
|
|
if(!ParseStringToken(st,ln,esc))
|
|
return false;
|
|
m_pk = st; m_pkl = ln; m_pke = esc;
|
|
state = JST_COLON;
|
|
continue;
|
|
}
|
|
if(c==-1)
|
|
return Fail(JSON_UNEXPECTED_END);
|
|
return Fail(JSON_UNEXPECTED_CHARACTER);
|
|
}
|
|
//================= objeto: clave obligatoria (tras ',') =====
|
|
else if(state==JST_KEY)
|
|
{
|
|
if(c=='"')
|
|
{
|
|
int st,ln; bool esc;
|
|
if(!ParseStringToken(st,ln,esc))
|
|
return false;
|
|
m_pk = st; m_pkl = ln; m_pke = esc;
|
|
state = JST_COLON;
|
|
continue;
|
|
}
|
|
if(c==-1)
|
|
return Fail(JSON_UNEXPECTED_END);
|
|
return Fail(JSON_EXPECTED_KEY);
|
|
}
|
|
//================= se espera ':' ============================
|
|
else if(state==JST_COLON)
|
|
{
|
|
if(c==':')
|
|
{
|
|
m_pos++;
|
|
state = JST_VALUE;
|
|
continue;
|
|
}
|
|
if(c==-1)
|
|
return Fail(JSON_UNEXPECTED_END);
|
|
return Fail(JSON_EXPECTED_COLON);
|
|
}
|
|
//================= se espera ',' o cierre ===================
|
|
else if(state==JST_COMMA_OR_END)
|
|
{
|
|
int top = m_stack[m_sp-1];
|
|
int tt = m_nodes[top].type;
|
|
if(tt==JSON_OBJECT && c=='}')
|
|
{
|
|
m_pos++;
|
|
m_sp--;
|
|
state = (m_sp==0 ? JST_DONE : JST_COMMA_OR_END);
|
|
continue;
|
|
}
|
|
if(tt==JSON_ARRAY && c==']')
|
|
{
|
|
m_pos++;
|
|
m_sp--;
|
|
state = (m_sp==0 ? JST_DONE : JST_COMMA_OR_END);
|
|
continue;
|
|
}
|
|
if(c==',')
|
|
{
|
|
m_pos++;
|
|
state = (tt==JSON_OBJECT ? JST_KEY : JST_VALUE);
|
|
continue;
|
|
}
|
|
if(c==-1)
|
|
return Fail(JSON_UNEXPECTED_END);
|
|
return Fail(JSON_EXPECTED_COMMA);
|
|
}
|
|
}
|
|
|
|
//--- comprobacion de basura tras el valor raiz (se tolera un 0 final)
|
|
SkipWs();
|
|
if(m_pos<m_len && Buffer[m_pos]!=0)
|
|
return Fail(JSON_TRAILING_CHARACTERS);
|
|
return(m_error==JSON_OK);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Linea del error (1-based), contando saltos de linea. |
|
|
//+------------------------------------------------------------------+
|
|
int CJson::GetLine(void)
|
|
{
|
|
int line = 1;
|
|
int n = (m_errpos<m_len ? m_errpos : m_len);
|
|
for(int i=0;i<n;i++)
|
|
if(Buffer[i]==0x0A)
|
|
line++;
|
|
return line;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Columna del error (1-based, en bytes). |
|
|
//+------------------------------------------------------------------+
|
|
int CJson::GetColumn(void)
|
|
{
|
|
int col = 1;
|
|
int n = (m_errpos<m_len ? m_errpos : m_len);
|
|
for(int i=0;i<n;i++)
|
|
{
|
|
if(Buffer[i]==0x0A)
|
|
col = 1;
|
|
else
|
|
col++;
|
|
}
|
|
return col;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Mensaje de error legible. |
|
|
//+------------------------------------------------------------------+
|
|
string CJson::GetErrorMessage(void)
|
|
{
|
|
switch(m_error)
|
|
{
|
|
case JSON_OK: return "OK";
|
|
case JSON_EMPTY: return "Buffer vacio";
|
|
case JSON_UNEXPECTED_CHARACTER: return "Caracter inesperado";
|
|
case JSON_UNEXPECTED_END: return "Fin de datos inesperado";
|
|
case JSON_INVALID_NUMBER: return "Numero invalido";
|
|
case JSON_INVALID_STRING: return "String invalido (caracter de control sin escapar)";
|
|
case JSON_INVALID_ESCAPE: return "Secuencia de escape invalida";
|
|
case JSON_INVALID_UNICODE: return "Escape unicode \\uXXXX invalido";
|
|
case JSON_EXPECTED_COLON: return "Se esperaba ':'";
|
|
case JSON_EXPECTED_COMMA: return "Se esperaba ',' o cierre";
|
|
case JSON_EXPECTED_KEY: return "Se esperaba una clave string";
|
|
case JSON_TRAILING_CHARACTERS: return "Caracteres sobrantes tras el valor raiz";
|
|
case JSON_MAX_DEPTH: return "Profundidad maxima de anidamiento superada";
|
|
}
|
|
return "Error desconocido";
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Devuelve la vista del nodo raiz. |
|
|
//+------------------------------------------------------------------+
|
|
CJsonNode CJson::Root(void)
|
|
{
|
|
CJsonNode n;
|
|
n.Bind(GetPointer(this),m_root);
|
|
return n;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| operator[] desde la raiz: acceso por clave. |
|
|
//+------------------------------------------------------------------+
|
|
CJsonNode CJson::operator[](const string key)
|
|
{
|
|
CJsonNode n;
|
|
n.Bind(GetPointer(this),FindChildKey(m_root,key));
|
|
return n;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| operator[] desde la raiz: acceso por indice. |
|
|
//+------------------------------------------------------------------+
|
|
CJsonNode CJson::operator[](const int index)
|
|
{
|
|
CJsonNode n;
|
|
n.Bind(GetPointer(this),FindChildIndex(m_root,index));
|
|
return n;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Equivalentes explicitos de operator[] desde la raiz. |
|
|
//+------------------------------------------------------------------+
|
|
CJsonNode CJson::Get(const string key)
|
|
{
|
|
CJsonNode n;
|
|
n.Bind(GetPointer(this),FindChildKey(m_root,key));
|
|
return n;
|
|
}
|
|
|
|
CJsonNode CJson::At(const int index)
|
|
{
|
|
CJsonNode n;
|
|
n.Bind(GetPointer(this),FindChildIndex(m_root,index));
|
|
return n;
|
|
}
|
|
|
|
//==================== API por indice de nodo ========================
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Tipo del nodo. |
|
|
//+------------------------------------------------------------------+
|
|
ENUM_JSON_TYPE CJson::TypeAt(const int idx)
|
|
{
|
|
if(idx<0 || idx>=m_count)
|
|
return JSON_UNDEFINED;
|
|
return (ENUM_JSON_TYPE)m_nodes[idx].type;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Numero de hijos (objetos/arrays); 0 para escalares. |
|
|
//+------------------------------------------------------------------+
|
|
int CJson::CountAt(const int idx)
|
|
{
|
|
if(idx<0 || idx>=m_count)
|
|
return 0;
|
|
return m_nodes[idx].count;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Indica si el nodo es un numero entero (sin '.', 'e' ni 'E'). |
|
|
//+------------------------------------------------------------------+
|
|
bool CJson::IsIntAt(const int idx)
|
|
{
|
|
if(idx<0 || idx>=m_count || m_nodes[idx].type!=JSON_NUMBER)
|
|
return false;
|
|
return((m_nodes[idx].flags & JFLAG_INT)!=0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Devuelve la clave decodificada de un nodo (si es miembro). |
|
|
//+------------------------------------------------------------------+
|
|
string CJson::KeyAt(const int idx)
|
|
{
|
|
if(idx<0 || idx>=m_count || m_nodes[idx].key<0)
|
|
return "";
|
|
return DecodeRange(m_nodes[idx].key,m_nodes[idx].klen,(m_nodes[idx].flags & JFLAG_KESC)!=0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Busca un miembro por clave dentro de un objeto. Devuelve el |
|
|
//| indice del nodo o -1. Comparacion directa de bytes (rapida); |
|
|
//| solo decodifica si la clave almacenada tiene escapes. |
|
|
//+------------------------------------------------------------------+
|
|
int CJson::FindChildKey(const int idx,const string key)
|
|
{
|
|
if(idx<0 || idx>=m_count || m_nodes[idx].type!=JSON_OBJECT)
|
|
return -1;
|
|
uchar kb[];
|
|
int kl = StringToCharArray(key,kb,0,WHOLE_ARRAY,CP_UTF8) - 1; // sin terminador
|
|
if(kl<0)
|
|
kl = 0;
|
|
int ch = m_nodes[idx].first;
|
|
while(ch!=-1)
|
|
{
|
|
if((m_nodes[ch].flags & JFLAG_KESC)==0)
|
|
{
|
|
if(m_nodes[ch].klen==kl)
|
|
{
|
|
int ks = m_nodes[ch].key;
|
|
bool eq = true;
|
|
for(int j=0;j<kl;j++)
|
|
if(Buffer[ks+j]!=kb[j])
|
|
{
|
|
eq = false;
|
|
break;
|
|
}
|
|
if(eq)
|
|
return ch;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(DecodeRange(m_nodes[ch].key,m_nodes[ch].klen,true)==key)
|
|
return ch;
|
|
}
|
|
ch = m_nodes[ch].next;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Devuelve el hijo i-esimo de un array u objeto, o -1. |
|
|
//+------------------------------------------------------------------+
|
|
int CJson::FindChildIndex(const int idx,int i)
|
|
{
|
|
if(idx<0 || idx>=m_count || i<0)
|
|
return -1;
|
|
int t = m_nodes[idx].type;
|
|
if(t!=JSON_ARRAY && t!=JSON_OBJECT)
|
|
return -1;
|
|
int ch = m_nodes[idx].first;
|
|
while(ch!=-1 && i>0)
|
|
{
|
|
ch = m_nodes[ch].next;
|
|
i--;
|
|
}
|
|
return(i==0 ? ch : -1);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Lee un valor como string. Decodifica strings; los numeros se |
|
|
//| devuelven en su texto crudo; bool como "true"/"false". |
|
|
//+------------------------------------------------------------------+
|
|
string CJson::StrAt(const int idx,const string def)
|
|
{
|
|
if(idx<0 || idx>=m_count)
|
|
return def;
|
|
int t = m_nodes[idx].type;
|
|
if(t==JSON_STRING)
|
|
return DecodeRange(m_nodes[idx].start,m_nodes[idx].len,(m_nodes[idx].flags & JFLAG_ESC)!=0);
|
|
if(t==JSON_NUMBER)
|
|
return RawRange(m_nodes[idx].start,m_nodes[idx].len);
|
|
if(t==JSON_BOOL)
|
|
return(m_nodes[idx].len==1 ? "true" : "false");
|
|
return def;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Lee un valor como entero (long). Parseo manual rapido/exacto |
|
|
//| para enteros; para decimales convierte via DblAt. |
|
|
//+------------------------------------------------------------------+
|
|
long CJson::IntAt(const int idx,const long def)
|
|
{
|
|
if(idx<0 || idx>=m_count)
|
|
return def;
|
|
int t = m_nodes[idx].type;
|
|
if(t==JSON_NUMBER)
|
|
{
|
|
if((m_nodes[idx].flags & JFLAG_INT)!=0)
|
|
{
|
|
int i = m_nodes[idx].start;
|
|
int e = i + m_nodes[idx].len;
|
|
bool neg = false;
|
|
if(i<e && Buffer[i]=='-')
|
|
{
|
|
neg = true;
|
|
i++;
|
|
}
|
|
long v = 0;
|
|
for(;i<e;i++)
|
|
v = v*10 + (long)(Buffer[i]-'0');
|
|
return(neg ? -v : v);
|
|
}
|
|
return (long)DblAt(idx,(double)def);
|
|
}
|
|
if(t==JSON_BOOL)
|
|
return(m_nodes[idx].len==1 ? 1 : 0);
|
|
return def;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Lee un valor como double. Parseo manual: mantisa * 10^exp. |
|
|
//| Muy rapido; precision adecuada para datos tipicos (precios,...). |
|
|
//+------------------------------------------------------------------+
|
|
double CJson::DblAt(const int idx,const double def)
|
|
{
|
|
if(idx<0 || idx>=m_count)
|
|
return def;
|
|
int t = m_nodes[idx].type;
|
|
if(t==JSON_BOOL)
|
|
return(m_nodes[idx].len==1 ? 1.0 : 0.0);
|
|
if(t!=JSON_NUMBER)
|
|
return def;
|
|
|
|
int i = m_nodes[idx].start;
|
|
int e = i + m_nodes[idx].len;
|
|
bool neg = false;
|
|
if(i<e && Buffer[i]=='-')
|
|
{
|
|
neg = true;
|
|
i++;
|
|
}
|
|
double mant = 0.0;
|
|
int fdig = 0;
|
|
while(i<e && Buffer[i]>='0' && Buffer[i]<='9')
|
|
{
|
|
mant = mant*10.0 + (double)(Buffer[i]-'0');
|
|
i++;
|
|
}
|
|
if(i<e && Buffer[i]=='.')
|
|
{
|
|
i++;
|
|
while(i<e && Buffer[i]>='0' && Buffer[i]<='9')
|
|
{
|
|
mant = mant*10.0 + (double)(Buffer[i]-'0');
|
|
fdig++;
|
|
i++;
|
|
}
|
|
}
|
|
int exp = 0;
|
|
bool eneg = false;
|
|
if(i<e && (Buffer[i]=='e' || Buffer[i]=='E'))
|
|
{
|
|
i++;
|
|
if(i<e && (Buffer[i]=='+' || Buffer[i]=='-'))
|
|
{
|
|
eneg = (Buffer[i]=='-');
|
|
i++;
|
|
}
|
|
while(i<e && Buffer[i]>='0' && Buffer[i]<='9')
|
|
{
|
|
exp = exp*10 + (int)(Buffer[i]-'0');
|
|
i++;
|
|
}
|
|
if(eneg)
|
|
exp = -exp;
|
|
}
|
|
int totexp = exp - fdig;
|
|
double val = mant;
|
|
if(totexp!=0)
|
|
val = mant * MathPow(10.0,(double)totexp);
|
|
return(neg ? -val : val);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Lee un valor como bool. |
|
|
//+------------------------------------------------------------------+
|
|
bool CJson::BoolAt(const int idx,const bool def)
|
|
{
|
|
if(idx<0 || idx>=m_count)
|
|
return def;
|
|
int t = m_nodes[idx].type;
|
|
if(t==JSON_BOOL)
|
|
return(m_nodes[idx].len==1);
|
|
if(t==JSON_NUMBER)
|
|
return(DblAt(idx,0.0)!=0.0);
|
|
if(t==JSON_NULL)
|
|
return false;
|
|
return def;
|
|
}
|
|
|
|
//==================== decodificacion / serializacion ================
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Devuelve el rango de bytes crudo como string (UTF-8), sin tocar |
|
|
//| los escapes (utilizado por la serializacion: ya es JSON valido). |
|
|
//+------------------------------------------------------------------+
|
|
string CJson::RawRange(const int start,const int len)
|
|
{
|
|
if(len<=0)
|
|
return "";
|
|
return CharArrayToString(Buffer,start,len,CP_UTF8);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Decodifica un rango de string resolviendo los escapes JSON. |
|
|
//| Si no hay escapes, conversion directa UTF-8 (caso comun, rapido).|
|
|
//+------------------------------------------------------------------+
|
|
string CJson::DecodeRange(const int start,const int len,const bool esc)
|
|
{
|
|
if(len<=0)
|
|
return "";
|
|
if(!esc)
|
|
return CharArrayToString(Buffer,start,len,CP_UTF8);
|
|
|
|
//--- la salida decodificada nunca es mayor que la entrada
|
|
uchar tmp[];
|
|
ArrayResize(tmp,len);
|
|
int o = 0;
|
|
int i = start;
|
|
int e = start + len;
|
|
while(i<e)
|
|
{
|
|
uchar c = Buffer[i];
|
|
if(c=='\\')
|
|
{
|
|
i++;
|
|
uchar x = Buffer[i];
|
|
switch(x)
|
|
{
|
|
case '"': tmp[o++] = '"'; i++; break;
|
|
case '\\': tmp[o++] = '\\'; i++; break;
|
|
case '/': tmp[o++] = '/'; i++; break;
|
|
case 'b': tmp[o++] = 0x08; i++; break;
|
|
case 'f': tmp[o++] = 0x0C; i++; break;
|
|
case 'n': tmp[o++] = 0x0A; i++; break;
|
|
case 'r': tmp[o++] = 0x0D; i++; break;
|
|
case 't': tmp[o++] = 0x09; i++; break;
|
|
case 'u':
|
|
{
|
|
uint cp = ((uint)g_hexval[Buffer[i+1]]<<12) |
|
|
((uint)g_hexval[Buffer[i+2]]<<8) |
|
|
((uint)g_hexval[Buffer[i+3]]<<4) |
|
|
((uint)g_hexval[Buffer[i+4]]);
|
|
i += 5;
|
|
//--- par sustituto UTF-16: combinar alto + bajo
|
|
if(cp>=0xD800 && cp<=0xDBFF && (i+6)<=e && Buffer[i]=='\\' && Buffer[i+1]=='u')
|
|
{
|
|
uint lo = ((uint)g_hexval[Buffer[i+2]]<<12) |
|
|
((uint)g_hexval[Buffer[i+3]]<<8) |
|
|
((uint)g_hexval[Buffer[i+4]]<<4) |
|
|
((uint)g_hexval[Buffer[i+5]]);
|
|
if(lo>=0xDC00 && lo<=0xDFFF)
|
|
{
|
|
cp = 0x10000 + ((cp-0xD800)<<10) + (lo-0xDC00);
|
|
i += 6;
|
|
}
|
|
}
|
|
o = JsonUtf8Encode(cp,tmp,o);
|
|
break;
|
|
}
|
|
default: i++; break; // no deberia ocurrir (validado al parsear)
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tmp[o++] = c;
|
|
i++;
|
|
}
|
|
}
|
|
return CharArrayToString(tmp,0,o,CP_UTF8);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Serializa el documento completo en JSON compacto. |
|
|
//+------------------------------------------------------------------+
|
|
string CJson::Serialize(void)
|
|
{
|
|
if(m_root<0)
|
|
return "";
|
|
string s = "";
|
|
SerializeNode(m_root,s);
|
|
return s;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Serializacion recursiva de un subarbol (profundidad acotada). |
|
|
//+------------------------------------------------------------------+
|
|
void CJson::SerializeNode(const int idx,string &out)
|
|
{
|
|
int t = m_nodes[idx].type;
|
|
if(t==JSON_OBJECT)
|
|
{
|
|
StringAdd(out,"{");
|
|
int ch = m_nodes[idx].first;
|
|
bool firstc = true;
|
|
while(ch!=-1)
|
|
{
|
|
if(!firstc)
|
|
StringAdd(out,",");
|
|
firstc = false;
|
|
StringAdd(out,"\"");
|
|
StringAdd(out,RawRange(m_nodes[ch].key,m_nodes[ch].klen));
|
|
StringAdd(out,"\":");
|
|
SerializeNode(ch,out);
|
|
ch = m_nodes[ch].next;
|
|
}
|
|
StringAdd(out,"}");
|
|
}
|
|
else if(t==JSON_ARRAY)
|
|
{
|
|
StringAdd(out,"[");
|
|
int ch = m_nodes[idx].first;
|
|
bool firstc = true;
|
|
while(ch!=-1)
|
|
{
|
|
if(!firstc)
|
|
StringAdd(out,",");
|
|
firstc = false;
|
|
SerializeNode(ch,out);
|
|
ch = m_nodes[ch].next;
|
|
}
|
|
StringAdd(out,"]");
|
|
}
|
|
else if(t==JSON_STRING)
|
|
{
|
|
StringAdd(out,"\"");
|
|
StringAdd(out,RawRange(m_nodes[idx].start,m_nodes[idx].len));
|
|
StringAdd(out,"\"");
|
|
}
|
|
else if(t==JSON_NUMBER)
|
|
StringAdd(out,RawRange(m_nodes[idx].start,m_nodes[idx].len));
|
|
else if(t==JSON_BOOL)
|
|
StringAdd(out,(m_nodes[idx].len==1 ? "true" : "false"));
|
|
else if(t==JSON_NULL)
|
|
StringAdd(out,"null");
|
|
}
|
|
|
|
//==================== Implementacion de CJsonNode ===================
|
|
|
|
ENUM_JSON_TYPE CJsonNode::Type(void) { return(IsValid() ? m_doc.TypeAt(m_idx) : JSON_UNDEFINED); }
|
|
bool CJsonNode::IsObject(void) { return(Type()==JSON_OBJECT); }
|
|
bool CJsonNode::IsArray(void) { return(Type()==JSON_ARRAY); }
|
|
bool CJsonNode::IsString(void) { return(Type()==JSON_STRING); }
|
|
bool CJsonNode::IsNumber(void) { return(Type()==JSON_NUMBER); }
|
|
bool CJsonNode::IsBool(void) { return(Type()==JSON_BOOL); }
|
|
bool CJsonNode::IsNull(void) { return(Type()==JSON_NULL); }
|
|
|
|
int CJsonNode::Size(void) { return(IsValid() ? m_doc.CountAt(m_idx) : 0); }
|
|
int CJsonNode::Count(void) { return Size(); }
|
|
string CJsonNode::Key(void) { return(IsValid() ? m_doc.KeyAt(m_idx) : ""); }
|
|
|
|
bool CJsonNode::Exists(const string key)
|
|
{
|
|
return(IsValid() && m_doc.FindChildKey(m_idx,key)>=0);
|
|
}
|
|
|
|
bool CJsonNode::IsInt(void)
|
|
{
|
|
return(IsValid() && m_doc.IsIntAt(m_idx));
|
|
}
|
|
|
|
CJsonNode CJsonNode::operator[](const string key)
|
|
{
|
|
CJsonNode n;
|
|
if(IsValid())
|
|
n.Bind(m_doc,m_doc.FindChildKey(m_idx,key));
|
|
return n;
|
|
}
|
|
|
|
CJsonNode CJsonNode::operator[](const int index)
|
|
{
|
|
CJsonNode n;
|
|
if(IsValid())
|
|
n.Bind(m_doc,m_doc.FindChildIndex(m_idx,index));
|
|
return n;
|
|
}
|
|
|
|
CJsonNode CJsonNode::Get(const string key)
|
|
{
|
|
CJsonNode n;
|
|
if(IsValid())
|
|
n.Bind(m_doc,m_doc.FindChildKey(m_idx,key));
|
|
return n;
|
|
}
|
|
|
|
CJsonNode CJsonNode::At(const int index)
|
|
{
|
|
CJsonNode n;
|
|
if(IsValid())
|
|
n.Bind(m_doc,m_doc.FindChildIndex(m_idx,index));
|
|
return n;
|
|
}
|
|
|
|
CJsonNode CJsonNode::Child(const int index)
|
|
{
|
|
CJsonNode n;
|
|
if(IsValid())
|
|
n.Bind(m_doc,m_doc.FindChildIndex(m_idx,index));
|
|
return n;
|
|
}
|
|
|
|
string CJsonNode::ChildKey(const int index)
|
|
{
|
|
if(!IsValid())
|
|
return "";
|
|
int c = m_doc.FindChildIndex(m_idx,index);
|
|
return m_doc.KeyAt(c);
|
|
}
|
|
|
|
string CJsonNode::GetString(const string def) { return(IsValid() ? m_doc.StrAt(m_idx,def) : def); }
|
|
long CJsonNode::ToLong(const long def) { return(IsValid() ? m_doc.IntAt(m_idx,def) : def); }
|
|
int CJsonNode::ToInt(const int def) { return(IsValid() ? (int)m_doc.IntAt(m_idx,(long)def) : def); }
|
|
double CJsonNode::ToDouble(const double def) { return(IsValid() ? m_doc.DblAt(m_idx,def) : def); }
|
|
bool CJsonNode::ToBool(const bool def) { return(IsValid() ? m_doc.BoolAt(m_idx,def) : def); }
|
|
|
|
string CJsonNode::ToString(void)
|
|
{
|
|
if(!IsValid())
|
|
return "";
|
|
string s = "";
|
|
m_doc.SerializeNode(m_idx,s);
|
|
return s;
|
|
}
|
|
|
|
#endif // __JSON_MQH__
|
|
//+------------------------------------------------------------------+
|
|
} |