//+------------------------------------------------------------------+ //| ClaudeJson.mqh | //| Copyright 2026, Niquel Mendoza. | //| https://www.mql5.com/ | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Niquel Mendoza." #property link "https://www.mql5.com/" #property strict //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ /* # 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"].GetDouble(); | //| | //| 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__ #property strict #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].GetInt(). | //| 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 GetLong(const long def=0); int GetInt(const int def=0); double GetDouble(const double def=0.0); bool GetBool(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_pos0?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 GetLong(const long def=0) { return IntAt(m_root,def); } int GetInt(const int def=0) { return (int)IntAt(m_root,(long)def); } double GetDouble(const double def=0.0){ return DblAt(m_root,def); } bool GetBool(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=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) { 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 GetDouble/GetLong). | //+------------------------------------------------------------------+ 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) { 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='0' && Buffer[p]<='9') p++; } else { m_pos = p; return Fail(JSON_INVALID_NUMBER); } //--- parte fraccionaria if(p=L || Buffer[p]<'0' || Buffer[p]>'9') { m_pos = p; return Fail(JSON_INVALID_NUMBER); } while(p='0' && Buffer[p]<='9') p++; } //--- exponente if(p=L || Buffer[p]<'0' || Buffer[p]>'9') { m_pos = p; return Fail(JSON_INVALID_NUMBER); } while(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_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=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=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='0' && Buffer[i]<='9') { mant = mant*10.0 + (double)(Buffer[i]-'0'); i++; } if(i='0' && Buffer[i]<='9') { mant = mant*10.0 + (double)(Buffer[i]-'0'); fdig++; i++; } } int exp = 0; bool eneg = false; if(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=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::GetLong(const long def) { return(IsValid() ? m_doc.IntAt(m_idx,def) : def); } int CJsonNode::GetInt(const int def) { return(IsValid() ? (int)m_doc.IntAt(m_idx,(long)def) : def); } double CJsonNode::GetDouble(const double def) { return(IsValid() ? m_doc.DblAt(m_idx,def) : def); } bool CJsonNode::GetBool(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__ //+------------------------------------------------------------------+