JsonParserByLeo/Test/Other/ClaudeJson.mqh
2069 lines
69 KiB
MQL5
//+------------------------------------------------------------------+
Nique_372
2026-06-21 10:55:27 -05:00
//| 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.
*/
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
// Desarollo documentado 6:27 PM (Inicio)
/*
Bueno le di el codigo a fable MAX y su puso a pensar, ahora toca espera 10-20 minutos o mas quizas
lop deje aqui.
Buieno va asi:
--------
Architected high-performance JSON parser with iterative algorithm and optimized node access
Architected high-performance JSON parser with iterative algorithm and optimized node access
Architecting high-performance JSON parser implementation.
Nique_372
2026-06-21 10:55:27 -05:00
Architecting node struct with class pointers and operator overloads.
Nique_372
2026-06-21 10:55:27 -05:00
Architecting accessor methods and indexing operators.
Nique_372
2026-06-21 10:55:27 -05:00
Resolviendo detalles de punteros y estructura de datos.
Nique_372
2026-06-21 10:55:27 -05:00
Escribiendo lógica de escaneo de cadenas.
Elaborando métodos de reporte de errores y rastreo de posición.
Refinando manejo de errores y estado inicial del parser.
Optimizing buffer assignment with ArraySwap for efficiency.
Finalizando lógica de asignación de claves y nodos.
Architected high-performance JSON parser with iterative algorithm and optimized node access.
Crear el parser JSON de alto rendimiento en MQL5 puro (archivo único Json.mqh)
--------
Esta pensado...
---- 18:36
Bueno termino ya lo compile y NI UN SOLO error... vemos si funciona o NO
void OnStart()
{
//---
CJson json;
StringToCharArray(g_json_data, json.Buffer);
Print(json.Parse());
Print(json.GetErrorMessage());
Print(json.GetLine());
Print(json.GetColumn());
}
Tenemos errores
2026.08.12 18:40:28.449 BenAi (GBPUSD,H1) false
2026.08.12 18:40:28.449 BenAi (GBPUSD,H1) secuencia de escape invalida
2026.08.12 18:40:28.449 BenAi (GBPUSD,H1) 6031
2026.08.12 18:40:28.449 BenAi (GBPUSD,H1) 27
le dire por que pasa eso, le dire esto:
Y de paso ojo aqui hare esto para empezar lo dire que lo solucione
SI SIGUE el mismo error ahi le dire un consejo...
______
Oye lo estuve probadmno con el twiter.json (el mismo que usan otros parsers
para sus test ) y me da un error de "secuencia de escape invalida", y da false?¿
El texto qeu parece ser el caunsante de dicho error es este:
"description": "\ もうすぐ18歳 “Only One”になる /",
______
Bueno enviado veamos que sale, por cierto me da ganas de decirle quizas
por lo vsito casi todas las ias a las que le pregunte lo mismo de json
para los tokens terminan haciendo un struct en vez un long.. la verdad
no se por que sera eso,, creo qeu tendran sus motivos.. pero quizas
le podira decir no exacmante .. proq eyu yaseria trampa si no
inspirante en los mejroes parsers json del mundo.... como rapidjson yyson sonic-rs y simdjson... pero
aplica solo lo que se peuda en MQL5 sin DLL... algo asi
Ahi como se ve no incito a opciar y pegar si no a analizar y raonzar que cosas puedo aprovechar o aprender
de dichos parsers y aplicarlo a MQL5.. algo asi..
Bueno note que emepzao a ronzar:
Pensando en resolver error de secuencia de escape inválida en JSON.
Investigando si conversión de codificación causa el error.
Replicando lógica de escaneo en Python para diagnosticar.
Investigó múltiples causas de escape inválido y propuso pruebas empíricas.
Descargar el twitter.json de referencia para reproducir el fallo
Nique_372
2026-06-21 10:55:27 -05:00
Exploró alternativas para resolver bloqueo de dominio.
Intentar descargar twitter.json desde un mirror CDN
nativejson-benchmark twitter.json raw githubusercontent
8 resultados
nativejson-benchmark/data/twitter.json at master · miloyip/nativejson-benchmark
github.com
GitHub - miloyip/nativejson-benchmark: C/C++ JSON parser/generator benchmark · GitHub
github.com
GitHub - serde-rs/json-benchmark: nativejson-benchmark in Rust · GitHub
github.com
Add more JSON libaries · Issue #9 · miloyip/nativejson-benchmark
github.com
GitHub - armink/nativejson-benchmark: C/C++ JSON parser/generator benchmark
github.com
GitHub - Loki-Astari/JsonBenchmark: Json benchmark based on nativejson-benchmark · GitHub
github.com
json-benchmark/README.md at master · serde-rs/json-benchmark
github.com
GitHub - project-gemmi/benchmarking-json: data from a preliminary benchmark of mmJSON parsing
github.com
nativejson-benchmark/data/twitter.json at master · miloyip/nativejson-benchmark
github.com
Error al obtener https://github.com/miloyip/nativejson-benchmark/raw/refs/heads/master/data/twitter.json
Analizando cómo el usuario carga el archivo en MQL5.
Descifrando el mecanismo de corrupción de caracteres.
Identificó corrupción de codificación por conversión de caracteres fullwidth a ASCII.
Añadir el código de error JSON_FILE_ERROR al enum
Json.mqh
+4
-3
Añadir ParseFile() que carga bytes crudos sin conversión de codepage
Json.mqh
+31
-4
La verdad no se por qeu hara eso cree qeu el error esta en la cofiaiocn dudo que lo este
aunque depende de la implemtancion.. en mi cimplemacion este error no existe literalemtne
daod que no me deiao a verificar esacpes en hoytpath si no luego
No se si de dara cuenta.. pero buenmo..
Solucion medio estupida que dio bueno le dije que como tal no uso archivos.. si no directo
quizas me falto max contexto
----
Cambios en esta versión:
json.ParseFile("twitter.json"): nuevo método que abre el archivo con FILE_BIN, lee los bytes directamente a Buffer con FileReadArray (sin ninguna conversión de texto) y parsea. Si el archivo no se puede abrir, GetError() devuelve el nuevo JSON_FILE_ERROR. Pásale true como segundo parámetro si el archivo está en la carpeta común del terminal.
Tolerancia al BOM UTF-8: al leer bytes crudos, algunos archivos empiezan con EF BB BF; ahora Parse() lo salta automáticamente en lugar de fallar con "carácter inesperado" en la posición 0.
Prueba con json.ParseFile("twitter.json") y debería parsear el documento completo sin problemas. Si aun cargando los bytes en crudo te diera algún error, dime la posición exacta (GetErrorPosition()) y los bytes alrededor, y lo miramos — pero apostaría a que era la conversión de codepage.
Json
MQH
Mi respuesta: (YO NIQUE)
yo lo hago como: #resource "twitter.json" as const string g_json_data
void OnStart()
{
//---
CJson json;
StringToCharArray(g_json_data, json.Buffer);
Print(json.Parse());
Print(json.GetErrorMessage());
Print(json.GetLine());
Print(json.GetColumn());
} no uso eso com ofile es raw... ?¿
MODELO EMPIEZA A RAZONAR:
Deliberando entre soluciones de codificación de caracteres
¡Ahí está! El problema es exactamente esa línea:
cpp
StringToCharArray(g_json_data, json.Buffer); // ← sin codepage
El recurso como string está bien (MetaEditor lo carga como Unicode correcto), pero StringToCharArray
sin quinto parámetro usa CP_ACP (la página de códigos ANSI de tu Windows), no UTF-8. Al convertir Unicode→ANSI,
Windows aplica el mapeo "best-fit": como \ (U+FF3C) no existe en tu codepage, lo sustituye por su pariente ASCII más parecido...
que es \ (0x5C). Lo mismo pasa con “ ”→" y /→/. Resultado: en tu buffer aparece un \ seguido de espacio y el parser reporta,
correctamente, "secuencia de escape inválida" justo en esa descripción japonesa. El JSON llega ya corrompido al parser.
Tienes dos formas de arreglarlo:
Opción A — la realmente "raw" (recomendada): incrusta el recurso como bytes, no como string.
Así no hay ninguna conversión en ningún punto:
cpp
#resource "twitter.json" as uchar g_json_data[]
void OnStart()
{
CJson json;
json.Buffer = g_json_data; // copia de bytes tal cual, sin codepages
Print(json.Parse());
Print(json.GetErrorMessage());
}
Opción B — si prefieres mantener el recurso como string: especifica UTF-8 en la conversión, o directamente usa ParseText,
que ya lo hace por ti:
cpp
json.ParseText(g_json_data);
// equivale a: StringToCharArray(g_json_data, json.Buffer, 0, WHOLE_ARRAY, CP_UTF8);
Con cualquiera de las dos, twitter.json debería parsear completo (Parse() → true). La opción A es además la más rápida: el recurso u
char[] son los 617 KB del archivo byte a byte, y el parser trabaja directamente sobre ellos.
Como regla general para esta librería: Buffer debe contener siempre los bytes UTF-8 originales del documento.
Cualquier paso por string sin CP_UTF8 explícito (o lecturas con FILE_ANSI) puede corromper silenciosamente caracteres no latinos —
y twitter.json, con todo su japonés, es el detector perfecto de ese fallo.
----
Buenot iene sentido en parte.. lo qeu dice yo la verdad no lo veriiqeu pero si eso de verdad pasara
entonces por que mi swar no falla?¿ por uqe mi jsonparser no falla con esa linea..asi que no se si tomarlo como verdad
creo que no.. ademas es medio tonto lo qeu menciona como ejemplo (copia de arrays con = en vez de hacerlo con un fro o arrcopy)
Bueno hare algo lo pasare a UTF 8 y vemos que pasa
2026.08.12 18:50:36.488 BenAi (GBPUSD,H1) true
2026.08.12 18:50:36.488 BenAi (GBPUSD,H1) sin errores
2026.08.12 18:50:36.488 BenAi (GBPUSD,H1) 0
2026.08.12 18:50:36.488 BenAi (GBPUSD,H1) 0
Bueno funciono todo este tiempo error mio.. asi que no lo culpo auqnue UTF8 vs CPACP no se si uno icrementera bytes o no
si lo hace entonces claude tendra mas desventaja.. cosa uqe parece ser... que paso..
creo qeu su parsers no es tan fast.. como prce en ScanString hace coass que bueno son cuestionalbes
(Nota hubo un gap dado qeu tuve mis propioes errores dado que pensaba uqe nof ucnaiona pero
no llama a json.Parser asi que estuve en uso botando tokens.. pero ya volvemos... )
Pero probemos..
2026.08.12 19:14:52.600 BenAi (GBPUSD,H1) Time: 1318010
2026.08.12 19:14:58.592 BenAi (GBPUSD,H1) Time: 1308737
2026.08.12 19:15:02.202 BenAi (GBPUSD,H1) Time: 1309672
2026.08.12 19:15:04.751 BenAi (GBPUSD,H1) Time: 1309841
Bueno nada mal... ligeramente mejoe que la ultiam vez estos son los datos.. sin iteraciones
Ahora probemos haciendo una lib fast de verdad...
----
Le dije esto:
----
Bueno error mio todo este tiempo funciona perfecto disculpa..... Oyue crees que podmoas seguir mejorando?¿ este parsers..
es rapido me dio como 1308 ms para parsear 1000 veces twiter.json de 616.7kb algo de 450mb\s+ en mi laptop... la idea es
quizas mejoralro.. estuve investigando sobre parsers fast.... en otros lengujes y encontre vaiors interesntes de los cuales
quizas te podiras guiar? como simdjson?¿ o sonic.rs o yyjson?¿ la idea es tomar sus conceptos y usarlos en MQL5
quizas no como un copia-pega si no adaptarlos (Y SOLO LOS QUE SE PUEDEN.. sin usar una DLL para funciones del sistmea
o asm diurecto) se puede mejorar ese resutaldo o no?¿
----
Veamos que nos da con ese mensaje le di pistas... lo que yo intuo e sque zuais se peuda bajar
el codigo de esos parsers verlos y sacar ideas de eso... es una
o quizas no.. ya veremos coomo es que el interpresta este prompt..
como tal no se lo dare servido
La idea es que yo para evaluar estos modelos me pondre el rol de "usuario que no sabe mucho"...
****************
¡Ya decía yo! Me alegro de que funcione. Y ese número no está nada mal: ~450 MB/s en MQL5 puro, con bounds-checking de arrays en cada acceso, es territorio respetable (rustc_serialize ronda 120 MB/s en el mismo archivo, RapidJSON ~320-440 MB/s en C++ nativo).
Sobre tus referencias, siendo honesto con lo que se puede y no se puede trasladar a MQL5:
Lo que NO se puede adaptar: el corazón de simdjson y sonic-rs son intrínsecos SIMD (AVX2/NEON: clasificar 32-64 bytes por instrucción)
más trucos como PDEP/PCLMUL. Sin DLL ni asm, eso está fuera. Incluso el truco SWAR (simular SIMD con aritmética de 64 bits en ulong)
no compensa en MQL5: no hay forma de reinterpretar 8 bytes del buffer como un ulong sin pagar 8 cargas + shifts, que cuesta más de lo
que ahorra. Y el "stage 1" de índice estructural de simdjson solo gana porque es SIMD; hecho escalar es una segunda pasada que duplica
tráfico de memoria.
Lo que SÍ se puede adaptar (sobre todo de yyjson, que es el más "escalar" de los tres): disposición de ramas según frecuencia de bytes,
eliminación de accesos redundantes al array (cada Buffer[pos] paga bounds-check en MQL5, así que leer una vez y comparar en registro es oro),
adelgazar el nodo para menos tráfico de caché, mover contadores del nodo a registros, e inlining manual de las rutinas calientes vía macro
(MQL5 no tiene inline). Déjame implementar una v2 con esto y verificarla contra el oráculo antes de dártela:
****************
Bueno en parte le doy la razon no podemos usar SIMD directo en MQL5
Pero el tema del SWAR si pero depend ede como se haga...
Por que intenat replicar SIMDJSOn tal cual (2-fases)no vale la pena estar cargando un monton de chynks
Eso ya lo he probado yo a mano...
Y al final el overedae de la carga de 64 bytes en un ulonbg cragandolo aplicar swar luego comprimie y haceir ors, shifs
ya no compensa lo qeu te ahorras y clasifiacno estrucuta,....
Pero lo qeu si es que el swar se puede aplicar quiruriqiamten en zonas especiias, pro ejemplo solo en string
para dtecatar los cierres vertdares de "" strings... creo que es lo unico...
Y eso es lo que hice..
Ahora sobre usar un "branch precior" como menciona de "dispcinio de ramas" la verdad
no se como lo hara ciermante peude llevar "stats" de los numeros strgins que van aprencio y en base a eso
tener quizas un swith o un bucle donde el "case" con mas % de paricion se ponga...
La verdad harbai qeu probar eso (infiero qeu se reuiqer a eso o quiza salgo mas simple) pero creo
que hacer eso es mucho overhead.. por llevar contadores.... etc etc no se si valga la pena..
*****
1. Vía ultrarrápida en el bucle de strings (el punto caliente de twitter.json, que es ~70% strings):
cualquier byte >= 0x5D —minúsculas a-z y todo byte UTF-8 multibyte, o sea todo el japonés— avanza con una sola
comparación en vez de tres. Es la adaptación escalar de la clasificación de bytes de simdjson: no puedo clasificar 32 bytes de golpe,
pero sí elegir el umbral que despacha el 80-90% de los bytes al primer compare.
2. Claves y strings de valor en bucles separados: las strings de valor ya no pagan ni el if(isKey) ni el hash por byte;
solo las claves (minoría) lo calculan.
3. Una sola lectura de Buffer[pos] por posición: en MQL5 cada acceso a array paga bounds-check, así que los bucles numéricos
que leían dos veces por dígito ahora leen una, y el test de dígito pasa de dos comparaciones a una con el truco sin signo (uchar)
(d-'0') <= 9.
4. Contadores en registros (estilo yyjson): count y "último hermano" del contenedor actual viven en variables locales con una
pila por profundidad; el nodo padre ya no se toca en memoria por cada hijo nuevo, y count se escribe una única vez al cerrar }/].
Además el nodo perdió el campo last (48→44 bytes) y la inicialización por nodo bajó de 12 escrituras a 4.
5. Salto de espacios inline vía macro (JSON_WS): MQL5 no tiene inline, así que la macro elimina la llamada a función en la ruta
caliente — relevante sobre todo en JSON minificado, donde SkipWS se llamaba millones de veces para no saltar nada.
Y lo que descarté con motivo: SWAR con ulong (sin reinterpretar bytes como palabras, cargar 8 bytes cuesta más de lo que ahorra),
el índice estructural en dos pasadas de simdjson (solo gana porque es SIMD; escalar duplica el tráfico de memoria) y las tablas de clasificación de 256 entradas (cambiarían comparaciones en registro por otro acceso a array con su bounds-check — dudoso en MQL5).
La lógica v2 está verificada contra el oráculo igual que la v1 (4.000 documentos, cero regresiones, statuses=100). Mide con este patrón
para que el número sea limpio:
*****
1. Bueno es una "optimizaicon" espeicia para nuestro dataset no se que tan polemico sea eso.. pero bueno en otros tipos de json
sera peor en todo caso ya lo veremos
2. Bueno este si esta intenate en vez de digmoas 10 ifas.... en cada iteracion pagas solo un if fuera
para strigns largos peud euqiza ayudar auqneu igual un if el cosoto es peuqeulito pero com miles de iteraciones
y usos quizas en algo ayude..
3. bueno esto si me parece intenate justo lo qeu yo hago aunque vemor en vez de usar restas usamos xor (en mi caso)
4. No seria mejor una pila completa?¿ en vez de ciertas cosas?¿ bueno igual una optimiacion para su aruqicta
5. Bueno esto si era necesario... un call a funcion que se ejeucta un monton de veces peude llegar a costar..
asi qeu
me parece
una optimicion razonable
6. sobre el swar si tiene rzon en qeu sin reitpracion es medio jodido dao que itnes que armar tu el ulong a mano
lo cual quizas mete mas overhead pero en strgins grandes creo qeu se "apaga" y el costo beneifcifion baja..
y sabeidno como es twi.tesjson esto ayudam mas ....
Bueno luego de dar mi opinino sobre esot vemoas que tal..
*/
// JSON_ACTIVE_CLAUDE_WITH_CONSEJOS
Nique_372
2026-06-21 10:55:27 -05:00
// Codigo 2
//+------------------------------------------------------------------+
//| Json.mqh |
//| Parser JSON de alto rendimiento en MQL5 puro (v2) |
//| |
//| Archivo unico, autocontenido, sin DLL ni dependencias. |
//| Conforme a RFC 8259: objetos, arrays, strings, numeros |
//| (enteros/decimales/exponentes), true/false, null y todas las |
//| secuencias de escape, incluida \uXXXX con pares sustitutos. |
//| |
//| ARQUITECTURA (v2, tecnicas adaptadas de yyjson/simdjson en la |
//| medida en que MQL5 escalar lo permite): |
//| |
//| * Parser ITERATIVO de una sola pasada sobre bytes, sin |
//| recursion (la jerarquia vive en el enlace parent). |
//| * Pool de nodos CONTIGUO (arena) con crecimiento geometrico; |
//| Reset() reutiliza toda la memoria. |
//| * ZERO-COPY + decodificacion PEREZOSA: los nodos guardan |
//| offsets; strings y numeros se convierten solo al consultar. |
//| * Bucle de string con via ultrarapida: bytes >= 0x5D |
//| (minusculas y todo UTF-8 multibyte) salen con UNA sola |
//| comparacion; ideal para documentos con texto/CJK. |
//| * Bucles separados para claves (con hash FNV-1a) y strings |
//| de valor (sin hash): cero coste de hash en la ruta comun. |
//| * UNA sola lectura de Buffer[pos] por posicion (cada acceso |
//| paga bounds-check en MQL5) y test de digito con una unica |
//| comparacion sin signo: (uchar)(d-'0') <= 9. |
//| * Contadores de hijos y ultimo-hermano en REGISTROS con una |
//| pila por profundidad: el nodo padre no se toca por cada |
//| hijo nuevo; count se escribe una vez al cerrar. |
//| * Inicializacion minima de nodos (4 campos), el resto lo |
//| escribe cada rama solo si aplica. |
//| * Salto de espacios INLINE via macro en la ruta caliente |
//| (MQL5 no tiene 'inline'; la macro elimina la llamada). |
//| * Busqueda de claves: filtro hash+longitud antes de comparar |
//| bytes. Acceso secuencial a arrays memoizado (O(1) amort.). |
//| * Linea/columna de error calculadas bajo demanda. |
//| |
//| BENCHMARKING RECOMENDADO: |
//| json.Buffer = datos; |
//| json.Parse(); // calentamiento + reserva |
//| ulong t0 = GetMicrosecondCount(); |
//| for(int i = 0; i < N; i++) { json.Reset(); json.Parse(); } |
//| ulong us = GetMicrosecondCount() - t0; |
//| |
//| USO BASICO: |
//| #include "Json.mqh" |
//| CJson json; |
//| ArraySwap(json.Buffer, MiBuffer); // cero copias |
//| // o: json.Buffer = MiBuffer; // una copia |
//| // o: json.ParseText(texto); // desde string |
//| // o: json.ParseFile("datos.json"); // bytes crudos |
//| if(!json.Parse()) |
//| PrintFormat("%s pos=%d linea=%d col=%d", |
//| json.GetErrorMessage(), |
//| json.GetErrorPosition(), |
//| json.GetLine(), json.GetColumn()); |
//| double balance = json["account"]["balance"].ToDouble(); |
//| // iteracion rapida: |
//| JsonNode t = json["ticks"].First(); |
//| while(t.IsValid()) { /* t.ToDouble() */ t = t.Next(); } |
//| |
//+------------------------------------------------------------------+
#ifndef __JSON_MQH__
#define __JSON_MQH__
Nique_372
2026-06-21 10:55:27 -05:00
//+------------------------------------------------------------------+
//| Tipos de nodo JSON |
Nique_372
2026-06-21 10:55:27 -05:00
//+------------------------------------------------------------------+
enum ENUM_JSON_TYPE
Nique_372
2026-06-21 10:55:27 -05:00
{
JSON_UNDEFINED = 0, // nodo inexistente / no parseado
JSON_NULL = 1, // null
JSON_BOOL = 2, // true / false
JSON_NUMBER = 3, // numero
JSON_STRING = 4, // string
JSON_ARRAY = 5, // array []
JSON_OBJECT = 6 // objeto {}
};
Nique_372
2026-06-21 10:55:27 -05:00
//+------------------------------------------------------------------+
//| Codigos de error del parser |
Nique_372
2026-06-21 10:55:27 -05:00
//+------------------------------------------------------------------+
enum ENUM_JSON_ERROR
Nique_372
2026-06-21 10:55:27 -05:00
{
JSON_OK = 0, // sin errores
JSON_NOT_PARSED, // Parse() aun no se ha llamado
JSON_EMPTY_DOCUMENT, // documento vacio
JSON_UNEXPECTED_CHARACTER, // caracter inesperado
JSON_UNEXPECTED_END, // fin de datos inesperado
JSON_INVALID_NUMBER, // numero mal formado
JSON_INVALID_STRING, // string mal formada (control char)
JSON_INVALID_ESCAPE, // secuencia de escape invalida
JSON_INVALID_UNICODE, // \uXXXX con digitos no hexadecimales
JSON_EXPECTED_COLON, // se esperaba ':'
JSON_EXPECTED_COMMA, // se esperaba ',' o cierre
JSON_EXPECTED_KEY, // se esperaba una clave (string)
JSON_TRAILING_CHARACTERS, // datos sobrantes tras el valor raiz
JSON_MAX_DEPTH_EXCEEDED, // anidamiento maximo superado
JSON_FILE_ERROR // no se pudo abrir/leer el archivo
};
//--- Flags internos por nodo (bitmask en JsonTok::flags)
#define JSON_F_KEY_ESC ((uchar)1) // la clave contiene escapes
#define JSON_F_VAL_ESC ((uchar)2) // el valor string contiene escapes
#define JSON_F_INT ((uchar)4) // numero sin '.' ni exponente
#define JSON_F_TRUE ((uchar)8) // literal booleano == true
Nique_372
2026-06-21 10:55:27 -05:00
//+------------------------------------------------------------------+
//| JsonTok: nodo interno. POD contiguo, sin strings, solo offsets. |
//| v2: sin campo 'last' (vive en una pila por profundidad durante |
//| el parseo) -> nodo mas pequeno, menos trafico de cache. |
//| NOTA: en nodos escalares, child/count NO se inicializan; todos |
//| los lectores (NodeFirst/NodeSize/ChildAt) comprueban el tipo. |
Nique_372
2026-06-21 10:55:27 -05:00
//+------------------------------------------------------------------+
struct JsonTok
Nique_372
2026-06-21 10:55:27 -05:00
{
int start; // offset del valor en el buffer
int len; // longitud del valor (span crudo)
int kstart; // offset de la clave (miembros de objeto)
int klen; // longitud de la clave (bytes crudos)
uint khash; // hash FNV-1a de la clave
int parent; // indice del padre (-1 = raiz)
int next; // siguiente hermano (-1 = ultimo)
int child; // primer hijo (solo contenedores)
int count; // numero de hijos (solo contenedores)
uchar type; // ENUM_JSON_TYPE
uchar flags; // JSON_F_*
};
class CJson; // declaracion adelantada
Nique_372
2026-06-21 10:55:27 -05:00
//+------------------------------------------------------------------+
//| JsonNode: vista ligera sobre un nodo. Se copia por valor; |
//| navegacion segura (un nodo invalido devuelve nodos invalidos). |
//| Valida solo mientras viva el CJson y hasta Parse/Reset/Clear. |
Nique_372
2026-06-21 10:55:27 -05:00
//+------------------------------------------------------------------+
struct JsonNode
Nique_372
2026-06-21 10:55:27 -05:00
{
CJson *m_doc;
int m_idx;
JsonNode(void) { m_doc = NULL; m_idx = -1; }
bool IsValid(void) const { return(m_doc != NULL && m_idx >= 0); }
ENUM_JSON_TYPE GetType(void) const;
bool IsObject(void) const { return(GetType() == JSON_OBJECT); }
bool IsArray(void) const { return(GetType() == JSON_ARRAY); }
bool IsString(void) const { return(GetType() == JSON_STRING); }
bool IsNumber(void) const { return(GetType() == JSON_NUMBER); }
bool IsBool(void) const { return(GetType() == JSON_BOOL); }
bool IsNull(void) const { return(GetType() == JSON_NULL); }
int Size(void) const;
int Count(void) const { return(Size()); }
JsonNode operator[](const string key);
JsonNode operator[](const int i);
JsonNode Get(const string key);
JsonNode At(const int i);
bool Exists(const string key);
JsonNode First(void); // primer hijo
JsonNode Next(void); // siguiente hermano
string Key(void); // clave de este miembro
string ToString(const string def = "");
double ToDouble(const double def = 0.0);
long ToInteger(const long def = 0);
bool ToBool(const bool def = false);
};
Nique_372
2026-06-21 10:55:27 -05:00
//+------------------------------------------------------------------+
//| CJson: parser JSON de alto rendimiento |
Nique_372
2026-06-21 10:55:27 -05:00
//+------------------------------------------------------------------+
class CJson
Nique_372
2026-06-21 10:55:27 -05:00
{
public:
//--- Buffer publico de entrada (bytes UTF-8 del documento).
/// ArraySwap(json.Buffer, MiBuffer); // cero copias
/// json.Buffer = MiBuffer; // una copia
uchar Buffer[];
private:
JsonTok m_nodes[]; // pool de nodos (arena contigua)
int m_ncount; // nodos usados
int m_ncap; // capacidad del pool
int m_end; // longitud valida del buffer
int m_maxdepth; // limite de anidamiento
ENUM_JSON_ERROR m_error;
int m_errpos;
uchar m_tmp[]; // buffer reutilizable de decodificacion
uchar m_keybuf[]; // clave de busqueda en UTF-8
Nique_372
2026-06-21 10:55:27 -05:00
int m_stk_last[]; // pila: ultimo hermano por profundidad
int m_stk_cnt[]; // pila: numero de hijos por profundidad
//--- memoizacion de acceso secuencial a arrays
int m_memo_parent;
int m_memo_index;
int m_memo_node;
//--- tablas estaticas compartidas
static bool s_tables;
static uchar s_hex[256]; // valor hexadecimal (0xFF = invalido)
static double s_pow10[309]; // potencias de 10
public:
CJson(void)
Nique_372
2026-06-21 10:55:27 -05:00
{
m_ncount = 0;
m_ncap = 0;
m_end = 0;
m_maxdepth = 512;
m_error = JSON_NOT_PARSED;
m_errpos = 0;
m_memo_parent = -1;
m_memo_index = 0;
m_memo_node = -1;
InitTables();
Nique_372
2026-06-21 10:55:27 -05:00
}
//+---------------------------------------------------------------+
//| API principal |
//+---------------------------------------------------------------+
bool Parse(void) { return(Parse(ArraySize(Buffer))); }
bool Parse(const int length);
//--- Conveniencia: convierte una string (UTF-8) y parsea
bool ParseText(const string text)
Nique_372
2026-06-21 10:55:27 -05:00
{
int n = StringToCharArray(text, Buffer, 0, WHOLE_ARRAY, CP_UTF8);
if(n > 0)
n--; // descartar el terminador '\0'
return(Parse(n));
Nique_372
2026-06-21 10:55:27 -05:00
}
//--- Conveniencia: carga un archivo como BYTES CRUDOS (FILE_BIN)
//--- y lo parsea. Via recomendada: sin conversiones de codepage.
bool ParseFile(const string filename, const bool common = false)
Nique_372
2026-06-21 10:55:27 -05:00
{
const int h = FileOpen(filename,
FILE_READ | FILE_BIN | FILE_SHARE_READ | FILE_SHARE_WRITE |
(common ? FILE_COMMON : 0));
if(h == INVALID_HANDLE)
Nique_372
2026-06-21 10:55:27 -05:00
{
m_ncount = 0;
m_error = JSON_FILE_ERROR;
m_errpos = 0;
return(false);
Nique_372
2026-06-21 10:55:27 -05:00
}
const int size = (int)FileSize(h);
ArrayResize(Buffer, size);
const int read = (size > 0 ? (int)FileReadArray(h, Buffer, 0, size) : 0);
FileClose(h);
return(Parse(read));
Nique_372
2026-06-21 10:55:27 -05:00
}
//--- Reset: descarta el documento, CONSERVA toda la memoria
void Reset(void)
Nique_372
2026-06-21 10:55:27 -05:00
{
m_ncount = 0;
m_end = 0;
m_error = JSON_NOT_PARSED;
m_errpos = 0;
m_memo_parent = -1;
m_memo_node = -1;
Nique_372
2026-06-21 10:55:27 -05:00
}
//--- Clear: libera toda la memoria
void Clear(void)
{
Reset();
ArrayFree(m_nodes);
ArrayFree(Buffer);
ArrayFree(m_tmp);
ArrayFree(m_keybuf);
ArrayFree(m_stk_last);
ArrayFree(m_stk_cnt);
m_ncap = 0;
}
Nique_372
2026-06-21 10:55:27 -05:00
//--- Pre-reserva del pool de nodos (evita ArrayResize durante
//--- el parseo si conoces el tamano tipico de tus documentos)
void Reserve(const int nodes) { Grow(nodes); }
Nique_372
2026-06-21 10:55:27 -05:00
void SetMaxDepth(const int depth) { m_maxdepth = (depth > 0 ? depth : 1); }
int GetMaxDepth(void) const { return(m_maxdepth); }
Nique_372
2026-06-21 10:55:27 -05:00
//+---------------------------------------------------------------+
//| Errores |
//+---------------------------------------------------------------+
ENUM_JSON_ERROR GetError(void) const { return(m_error); }
bool HasError(void) const { return(m_error != JSON_OK); }
int GetErrorPosition(void) const { return(m_errpos); }
string GetErrorMessage(void) const
{
switch(m_error)
{
case JSON_OK: return("sin errores");
case JSON_NOT_PARSED: return("Parse() no se ha ejecutado");
case JSON_EMPTY_DOCUMENT: return("documento 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 invalida");
case JSON_INVALID_ESCAPE: return("secuencia de escape invalida");
case JSON_INVALID_UNICODE: return("secuencia \\uXXXX invalida");
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 entre comillas");
case JSON_TRAILING_CHARACTERS: return("datos sobrantes tras el valor raiz");
case JSON_MAX_DEPTH_EXCEEDED: return("anidamiento maximo superado");
case JSON_FILE_ERROR: return("no se pudo abrir/leer el archivo");
}
return("error desconocido");
}
//--- Linea/columna del error (1-based; columna en bytes UTF-8).
//--- Se calculan bajo demanda: cero coste durante el parseo.
int GetLine(void) const
{
if(m_error == JSON_OK || m_error == JSON_NOT_PARSED)
return(0);
int line = 1;
const int stop = (m_errpos < m_end ? m_errpos : m_end);
for(int i = 0; i < stop; i++)
if(Buffer[i] == 10)
line++;
return(line);
}
int GetColumn(void) const
{
if(m_error == JSON_OK || m_error == JSON_NOT_PARSED)
return(0);
int col = 1;
const int stop = (m_errpos < m_end ? m_errpos : m_end);
for(int i = 0; i < stop; i++)
{
if(Buffer[i] == 10)
col = 1;
else
col++;
}
return(col);
}
//+---------------------------------------------------------------+
//| Acceso al documento |
//+---------------------------------------------------------------+
int RootIndex(void) const { return(m_error == JSON_OK && m_ncount > 0 ? 0 : -1); }
JsonNode Root(void)
{
JsonNode n;
n.m_doc = GetPointer(this);
n.m_idx = RootIndex();
return(n);
}
JsonNode operator[](const string key)
{
JsonNode n;
n.m_doc = GetPointer(this);
n.m_idx = FindMember(RootIndex(), key);
return(n);
}
JsonNode operator[](const int i)
{
JsonNode n;
n.m_doc = GetPointer(this);
n.m_idx = ChildAt(RootIndex(), i);
return(n);
}
ENUM_JSON_TYPE GetType(void) const { return(NodeType(RootIndex())); }
bool IsObject(void) const { return(GetType() == JSON_OBJECT); }
bool IsArray(void) const { return(GetType() == JSON_ARRAY); }
bool Exists(const string key) { return(FindMember(RootIndex(), key) >= 0); }
int Size(void) const { return(NodeSize(RootIndex())); }
int Count(void) const { return(Size()); }
int NodesUsed(void) const { return(m_ncount); }
//+---------------------------------------------------------------+
//| API interna por indice de nodo (usada por JsonNode). |
//| Publica por necesidad (MQL5 no tiene 'friend'). |
//+---------------------------------------------------------------+
ENUM_JSON_TYPE NodeType(const int i) const
{
if(i < 0 || i >= m_ncount)
return(JSON_UNDEFINED);
return((ENUM_JSON_TYPE)m_nodes[i].type);
}
//--- v2: child/count solo son validos en contenedores
int NodeSize(const int i) const
{
if(i < 0 || i >= m_ncount)
return(0);
const uchar t = m_nodes[i].type;
if(t != (uchar)JSON_ARRAY && t != (uchar)JSON_OBJECT)
return(0);
return(m_nodes[i].count);
}
int NodeFirst(const int i) const
{
if(i < 0 || i >= m_ncount)
return(-1);
const uchar t = m_nodes[i].type;
if(t != (uchar)JSON_ARRAY && t != (uchar)JSON_OBJECT)
return(-1);
return(m_nodes[i].child);
}
int NodeNext(const int i) const
{
if(i < 0 || i >= m_ncount)
return(-1);
return(m_nodes[i].next);
}
string NodeKey(const int i)
{
if(i < 0 || i >= m_ncount || m_nodes[i].klen <= 0)
return("");
return(DecodeSpan(m_nodes[i].kstart, m_nodes[i].klen,
(m_nodes[i].flags & JSON_F_KEY_ESC) != 0));
}
string NodeText(const int i, const string def)
{
if(i < 0 || i >= m_ncount)
return(def);
switch(m_nodes[i].type)
{
case JSON_STRING:
return(DecodeSpan(m_nodes[i].start, m_nodes[i].len,
(m_nodes[i].flags & JSON_F_VAL_ESC) != 0));
case JSON_NUMBER:
return(CharArrayToString(Buffer, m_nodes[i].start, m_nodes[i].len, CP_UTF8));
case JSON_BOOL:
return((m_nodes[i].flags & JSON_F_TRUE) != 0 ? "true" : "false");
case JSON_NULL:
return("null");
}
return(def);
}
double NodeDouble(const int i, const double def) const
{
if(i < 0 || i >= m_ncount)
return(def);
const uchar t = m_nodes[i].type;
if(t == JSON_NUMBER)
return(SpanToDouble(m_nodes[i].start, m_nodes[i].start + m_nodes[i].len));
if(t == JSON_BOOL)
return((m_nodes[i].flags & JSON_F_TRUE) != 0 ? 1.0 : 0.0);
if(t == JSON_STRING && m_nodes[i].len > 0)
{
const uchar c = Buffer[m_nodes[i].start];
if(c == '-' || (uchar)(c - '0') <= 9)
return(SpanToDouble(m_nodes[i].start, m_nodes[i].start + m_nodes[i].len));
}
return(def);
}
long NodeLong(const int i, const long def) const
{
if(i < 0 || i >= m_ncount)
return(def);
const uchar t = m_nodes[i].type;
if(t == JSON_NUMBER)
{
if((m_nodes[i].flags & JSON_F_INT) != 0)
return(SpanToLong(m_nodes[i].start, m_nodes[i].start + m_nodes[i].len));
return((long)SpanToDouble(m_nodes[i].start, m_nodes[i].start + m_nodes[i].len));
}
if(t == JSON_BOOL)
return((m_nodes[i].flags & JSON_F_TRUE) != 0 ? 1 : 0);
if(t == JSON_STRING && m_nodes[i].len > 0)
{
const uchar c = Buffer[m_nodes[i].start];
if(c == '-' || (uchar)(c - '0') <= 9)
return((long)SpanToDouble(m_nodes[i].start, m_nodes[i].start + m_nodes[i].len));
}
return(def);
}
bool NodeBool(const int i, const bool def)
{
if(i < 0 || i >= m_ncount)
return(def);
const uchar t = m_nodes[i].type;
if(t == JSON_BOOL)
return((m_nodes[i].flags & JSON_F_TRUE) != 0);
if(t == JSON_NUMBER)
return(SpanToDouble(m_nodes[i].start, m_nodes[i].start + m_nodes[i].len) != 0.0);
if(t == JSON_STRING)
{
const string s = NodeText(i, "");
if(s == "true")
return(true);
if(s == "false")
return(false);
}
return(def);
}
//--- Busqueda de miembro: filtro hash + longitud -> bytes.
int FindMember(const int obj, const string key)
{
if(obj < 0 || obj >= m_ncount || m_nodes[obj].type != (uchar)JSON_OBJECT)
return(-1);
int klen = StringToCharArray(key, m_keybuf, 0, WHOLE_ARRAY, CP_UTF8);
if(klen > 0)
klen--;
uint h = 0x811C9DC5;
for(int i = 0; i < klen; i++)
h = (h ^ m_keybuf[i]) * 16777619;
int n = m_nodes[obj].child;
while(n >= 0)
{
if((m_nodes[n].flags & JSON_F_KEY_ESC) == 0)
{
if(m_nodes[n].khash == h && m_nodes[n].klen == klen)
{
const int ks = m_nodes[n].kstart;
int j = 0;
while(j < klen && Buffer[ks + j] == m_keybuf[j])
j++;
if(j == klen)
return(n);
}
}
else
{
if(DecodeSpan(m_nodes[n].kstart, m_nodes[n].klen, true) == key)
return(n);
}
n = m_nodes[n].next;
}
return(-1);
}
//--- i-esimo hijo con memoizacion (acceso secuencial O(1) amort.)
int ChildAt(const int cont, const int i)
{
if(cont < 0 || cont >= m_ncount || i < 0)
return(-1);
const uchar t = m_nodes[cont].type;
if(t != (uchar)JSON_ARRAY && t != (uchar)JSON_OBJECT)
return(-1);
if(i >= m_nodes[cont].count)
return(-1);
int n, k;
if(m_memo_parent == cont && m_memo_node >= 0 && m_memo_index <= i)
{
n = m_memo_node;
k = m_memo_index;
}
else
{
n = m_nodes[cont].child;
k = 0;
}
while(k < i)
{
n = m_nodes[n].next;
k++;
}
m_memo_parent = cont;
m_memo_index = i;
m_memo_node = n;
return(n);
}
private:
//+---------------------------------------------------------------+
//| Nucleo del parser |
//+---------------------------------------------------------------+
bool Fail(const ENUM_JSON_ERROR err, const int pos)
{
m_error = err;
m_errpos = pos;
return(false);
}
void Grow(const int need)
{
int cap = (need < 16 ? 16 : need);
if(cap <= m_ncap)
return;
ArrayResize(m_nodes, cap);
m_ncap = cap;
}
//--- v2: asignacion pura; la inicializacion minima y el enlace
//--- de hermanos se hacen inline en Parse (menos escrituras).
int NewNode(void)
{
if(m_ncount >= m_ncap)
Grow(m_ncap * 2);
return(m_ncount++);
}
int SkipWS(int pos) const
{
const int end = m_end;
while(pos < end)
{
const uchar c = Buffer[pos];
if(c > 32)
return(pos);
if(c != 32 && c != 9 && c != 10 && c != 13)
return(pos);
pos++;
}
return(pos);
}
//--- Escaneo de STRING DE VALOR (sin hash). Bucle v2:
//--- * c >= 0x5D -> via ultrarapida, 1 comparacion
//--- (minusculas a-z, ']','^','_','`','{','|','}','~' y TODO
//--- byte UTF-8 multibyte >= 0x80: texto CJK, acentos...)
bool ScanStr(int &pos, int &start, int &len, uchar &escaped)
{
const int end = m_end;
pos++;
start = pos;
escaped = 0;
while(pos < end)
{
const uchar c = Buffer[pos];
if(c >= 0x5D)
{
pos++;
continue;
}
if(c == '"')
{
len = pos - start;
pos++;
return(true);
}
if(c == '\\')
{
escaped = 1;
pos++;
if(pos >= end)
return(Fail(JSON_UNEXPECTED_END, pos));
pos++;
continue;
}
if(c >= 0x20)
{
pos++;
continue;
}
return(Fail(JSON_INVALID_STRING, pos));
}
return(Fail(JSON_UNEXPECTED_END, pos));
}
//--- Escaneo de CLAVE (identico + hash FNV-1a). Bucle separado
//--- para que las strings de valor no paguen el hash.
bool ScanKey(int &pos, int &start, int &len, uchar &escaped, uint &hash)
{
const int end = m_end;
pos++;
start = pos;
escaped = 0;
uint h = 0x811C9DC5;
while(pos < end)
{
const uchar c = Buffer[pos];
if(c >= 0x5D)
{
h = (h ^ c) * 16777619;
pos++;
continue;
}
if(c == '"')
{
len = pos - start;
pos++;
hash = h;
return(true);
}
if(c == '\\')
{
escaped = 1;
pos++;
if(pos >= end)
return(Fail(JSON_UNEXPECTED_END, pos));
const uchar e = Buffer[pos];
if(e == 'u')
{
if(pos + 4 >= end)
return(Fail(JSON_UNEXPECTED_END, pos));
if(s_hex[Buffer[pos + 1]] == 0xFF || s_hex[Buffer[pos + 2]] == 0xFF ||
s_hex[Buffer[pos + 3]] == 0xFF || s_hex[Buffer[pos + 4]] == 0xFF)
return(Fail(JSON_INVALID_UNICODE, pos - 1));
pos += 5;
continue;
}
if(e == '"' || e == '\\' || e == '/' || e == 'b' ||
e == 'f' || e == 'n' || e == 'r' || e == 't')
{
pos++;
continue;
}
return(Fail(JSON_INVALID_ESCAPE, pos - 1));
}
if(c >= 0x20)
{
h = (h ^ c) * 16777619;
pos++;
continue;
}
return(Fail(JSON_INVALID_STRING, pos));
}
return(Fail(JSON_UNEXPECTED_END, pos));
}
//--- Escaneo/validacion de numero. v2: UNA lectura de Buffer[pos]
//--- por posicion y test de digito con una unica comparacion.
bool ScanNumber(int &pos, const int node)
{
const int end = m_end;
const int start = pos;
uchar isint = 1;
if(Buffer[pos] == '-')
{
pos++;
if(pos >= end)
return(Fail(JSON_INVALID_NUMBER, pos));
}
const uchar c = Buffer[pos];
if(c == '0')
pos++;
else
if((uchar)(c - '1') <= 8)
{
pos++;
while(pos < end)
{
const uchar d = Buffer[pos];
if((uchar)(d - '0') > 9)
break;
pos++;
}
}
else
return(Fail(JSON_INVALID_NUMBER, pos));
if(pos < end && Buffer[pos] == '.')
{
isint = 0;
pos++;
if(pos >= end || (uchar)(Buffer[pos] - '0') > 9)
return(Fail(JSON_INVALID_NUMBER, pos));
pos++;
while(pos < end)
{
const uchar d = Buffer[pos];
if((uchar)(d - '0') > 9)
break;
pos++;
}
}
if(pos < end && (Buffer[pos] == 'e' || Buffer[pos] == 'E'))
{
isint = 0;
pos++;
if(pos < end && (Buffer[pos] == '+' || Buffer[pos] == '-'))
pos++;
if(pos >= end || (uchar)(Buffer[pos] - '0') > 9)
return(Fail(JSON_INVALID_NUMBER, pos));
pos++;
while(pos < end)
{
const uchar d = Buffer[pos];
if((uchar)(d - '0') > 9)
break;
pos++;
}
}
m_nodes[node].type = (uchar)JSON_NUMBER;
m_nodes[node].start = start;
m_nodes[node].len = pos - start;
if(isint != 0)
m_nodes[node].flags |= JSON_F_INT;
return(true);
}
//--- Conversion rapida bytes -> double (18 digitos significativos)
double SpanToDouble(int p, const int end) const
{
bool neg = false;
if(p < end && Buffer[p] == '-')
{
neg = true;
p++;
}
long mant = 0;
int ndig = 0;
int dexp = 0;
while(p < end)
{
const uchar c = Buffer[p];
if((uchar)(c - '0') > 9)
break;
if(ndig < 18)
{
mant = mant * 10 + (c - '0');
if(mant != 0)
ndig++;
}
else
dexp++;
p++;
}
if(p < end && Buffer[p] == '.')
{
p++;
while(p < end)
Nique_372
2026-06-21 10:55:27 -05:00
{
const uchar c = Buffer[p];
if((uchar)(c - '0') > 9)
break;
if(ndig < 18)
{
mant = mant * 10 + (c - '0');
if(mant != 0)
ndig++;
dexp--;
}
p++;
}
}
if(p < end && (Buffer[p] == 'e' || Buffer[p] == 'E'))
{
p++;
bool eneg = false;
if(p < end && (Buffer[p] == '+' || Buffer[p] == '-'))
{
eneg = (Buffer[p] == '-');
p++;
}
int ex = 0;
while(p < end)
{
const uchar c = Buffer[p];
if((uchar)(c - '0') > 9)
break;
if(ex < 100000)
ex = ex * 10 + (c - '0');
p++;
}
dexp += (eneg ? -ex : ex);
}
double r = (double)mant;
if(dexp > 0)
{
while(dexp > 308)
{
r *= s_pow10[308];
dexp -= 308;
}
r *= s_pow10[dexp];
}
else
if(dexp < 0)
{
int e2 = -dexp;
while(e2 > 308)
{
r /= s_pow10[308];
e2 -= 308;
}
r /= s_pow10[e2];
}
return(neg ? -r : r);
}
long SpanToLong(int p, const int end) const
{
bool neg = false;
if(p < end && Buffer[p] == '-')
{
neg = true;
p++;
}
long v = 0;
while(p < end)
{
const uchar c = Buffer[p];
if((uchar)(c - '0') > 9)
break;
v = v * 10 + (c - '0');
p++;
}
return(neg ? -v : v);
}
//--- Decodificacion perezosa de spans. Via rapida sin escapes;
//--- via con escapes usa buffer reutilizable y genera UTF-8 real
//--- para \uXXXX, incluidos pares sustitutos (> U+FFFF).
string DecodeSpan(const int start, const int len, const bool escaped)
{
if(len <= 0)
return("");
if(!escaped)
return(CharArrayToString(Buffer, start, len, CP_UTF8));
if(ArraySize(m_tmp) < len)
ArrayResize(m_tmp, len, 256);
int o = 0;
int p = start;
const int end = start + len;
while(p < end)
{
const uchar c = Buffer[p];
if(c != '\\')
{
m_tmp[o++] = c;
p++;
continue;
}
p++;
const uchar e = Buffer[p++];
switch(e)
{
case '"': m_tmp[o++] = (uchar)'"'; break;
case '\\': m_tmp[o++] = (uchar)'\\'; break;
case '/': m_tmp[o++] = (uchar)'/'; break;
case 'b': m_tmp[o++] = 8; break;
case 'f': m_tmp[o++] = 12; break;
case 'n': m_tmp[o++] = 10; break;
case 'r': m_tmp[o++] = 13; break;
case 't': m_tmp[o++] = 9; break;
Nique_372
2026-06-21 10:55:27 -05:00
case 'u':
{
uint cp = ((uint)s_hex[Buffer[p]] << 12) |
((uint)s_hex[Buffer[p + 1]] << 8) |
((uint)s_hex[Buffer[p + 2]] << 4) |
(uint)s_hex[Buffer[p + 3]];
p += 4;
if(cp >= 0xD800 && cp <= 0xDBFF && p + 6 <= end &&
Buffer[p] == '\\' && Buffer[p + 1] == 'u')
Nique_372
2026-06-21 10:55:27 -05:00
{
const uint lo = ((uint)s_hex[Buffer[p + 2]] << 12) |
((uint)s_hex[Buffer[p + 3]] << 8) |
((uint)s_hex[Buffer[p + 4]] << 4) |
(uint)s_hex[Buffer[p + 5]];
if(lo >= 0xDC00 && lo <= 0xDFFF)
Nique_372
2026-06-21 10:55:27 -05:00
{
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
p += 6;
Nique_372
2026-06-21 10:55:27 -05:00
}
}
if(cp < 0x80)
m_tmp[o++] = (uchar)cp;
else
if(cp < 0x800)
{
m_tmp[o++] = (uchar)(0xC0 | (cp >> 6));
m_tmp[o++] = (uchar)(0x80 | (cp & 0x3F));
}
else
if(cp < 0x10000)
{
m_tmp[o++] = (uchar)(0xE0 | (cp >> 12));
m_tmp[o++] = (uchar)(0x80 | ((cp >> 6) & 0x3F));
m_tmp[o++] = (uchar)(0x80 | (cp & 0x3F));
}
else
{
m_tmp[o++] = (uchar)(0xF0 | (cp >> 18));
m_tmp[o++] = (uchar)(0x80 | ((cp >> 12) & 0x3F));
m_tmp[o++] = (uchar)(0x80 | ((cp >> 6) & 0x3F));
m_tmp[o++] = (uchar)(0x80 | (cp & 0x3F));
}
Nique_372
2026-06-21 10:55:27 -05:00
break;
}
default:
m_tmp[o++] = e;
break;
Nique_372
2026-06-21 10:55:27 -05:00
}
}
return(CharArrayToString(m_tmp, 0, o, CP_UTF8));
}
static void InitTables(void)
{
if(s_tables)
return;
for(int i = 0; i < 256; i++)
s_hex[i] = 0xFF;
for(int i = '0'; i <= '9'; i++)
s_hex[i] = (uchar)(i - '0');
for(int i = 'a'; i <= 'f'; i++)
s_hex[i] = (uchar)(i - 'a' + 10);
for(int i = 'A'; i <= 'F'; i++)
s_hex[i] = (uchar)(i - 'A' + 10);
s_pow10[0] = 1.0;
for(int i = 1; i <= 308; i++)
s_pow10[i] = s_pow10[i - 1] * 10.0;
s_tables = true;
}
};
//--- Salto de espacios INLINE para la ruta caliente de Parse.
//--- Evita la llamada a SkipWS: la mayoria de las veces el primer
//--- byte ya no es espacio y sale con una comparacion.
#define JSON_WS(p) while(p < end) { const uchar w_ = Buffer[p]; if(w_ > 32) break; if(w_ != 32 && w_ != 9 && w_ != 10 && w_ != 13) break; p++; }
//+------------------------------------------------------------------+
//| CJson::Parse - nucleo iterativo de una sola pasada (v2). |
//| Jerarquia via enlaces parent (sin recursion). 'ultimo hermano' y |
//| 'numero de hijos' del contenedor actual viven en registros; se |
//| salvan/restauran en una pila por profundidad al abrir/cerrar. |
//+------------------------------------------------------------------+
bool CJson::Parse(const int length)
{
m_ncount = 0;
m_error = JSON_OK;
m_errpos = 0;
m_memo_parent = -1;
m_memo_node = -1;
m_end = length;
const int bs = ArraySize(Buffer);
if(m_end > bs)
m_end = bs;
//--- recortar NULs finales (StringToCharArray suele anadir '\0')
while(m_end > 0 && Buffer[m_end - 1] == 0)
m_end--;
//--- pre-reserva del pool (Reserve() permite ajustarla a mano)
if(m_ncap < m_end / 8 + 16)
Grow(m_end / 8 + 16);
//--- pilas por profundidad (se conservan entre Reset)
if(ArraySize(m_stk_last) < m_maxdepth + 2)
{
ArrayResize(m_stk_last, m_maxdepth + 2);
ArrayResize(m_stk_cnt, m_maxdepth + 2);
}
const int end = m_end;
int pos = 0;
//--- saltar el BOM UTF-8 (EF BB BF) si el documento lo trae
if(end >= 3 && Buffer[0] == 0xEF && Buffer[1] == 0xBB && Buffer[2] == 0xBF)
pos = 3;
JSON_WS(pos);
if(pos >= end)
return(Fail(JSON_EMPTY_DOCUMENT, pos));
int parent = -1; // contenedor actual (-1 = nivel raiz)
int depth = 0;
int lastChild = -1; // ultimo hijo del contenedor actual (registro)
int childCount = 0; // hijos del contenedor actual (registro)
bool haskey = false;
int kstart = 0, klen = 0;
uint khash = 0;
uchar kesc = 0;
while(true)
{
//================================================================
// FASE 1: parsear un valor en 'pos'
//================================================================
if(pos >= end)
return(Fail(JSON_UNEXPECTED_END, pos));
uchar c = Buffer[pos];
const int node = NewNode();
//--- v2: inicializacion minima + enlace de hermanos inline.
//--- child/count solo se escriben en las ramas de contenedor.
m_nodes[node].parent = parent;
m_nodes[node].next = -1;
m_nodes[node].klen = 0;
m_nodes[node].flags = 0;
if(parent >= 0)
{
if(lastChild < 0)
m_nodes[parent].child = node;
else
m_nodes[lastChild].next = node;
lastChild = node;
childCount++;
}
if(haskey)
{
m_nodes[node].kstart = kstart;
m_nodes[node].klen = klen;
m_nodes[node].khash = khash;
if(kesc != 0)
m_nodes[node].flags |= JSON_F_KEY_ESC;
haskey = false;
}
//--- dispatcher ordenado por frecuencia tipica
if(c == '"')
{
int s = 0, l = 0;
uchar esc = 0;
if(!ScanStr(pos, s, l, esc))
return(false);
m_nodes[node].type = (uchar)JSON_STRING;
m_nodes[node].start = s;
m_nodes[node].len = l;
if(esc != 0)
m_nodes[node].flags |= JSON_F_VAL_ESC;
}
else
if((uchar)(c - '0') <= 9 || c == '-')
{
if(!ScanNumber(pos, node))
return(false);
}
else
if(c == '{')
{
m_nodes[node].type = (uchar)JSON_OBJECT;
m_nodes[node].start = pos;
m_nodes[node].child = -1;
m_nodes[node].count = 0;
pos++;
JSON_WS(pos);
if(pos >= end)
return(Fail(JSON_UNEXPECTED_END, pos));
if(Buffer[pos] == '}')
{
pos++; // objeto vacio
m_nodes[node].len = pos - m_nodes[node].start;
}
else
{
depth++;
if(depth > m_maxdepth)
return(Fail(JSON_MAX_DEPTH_EXCEEDED, pos));
m_stk_last[depth] = lastChild; // "push" O(1)
m_stk_cnt[depth] = childCount;
lastChild = -1;
childCount = 0;
parent = node;
if(Buffer[pos] != '"')
return(Fail(JSON_EXPECTED_KEY, pos));
if(!ScanKey(pos, kstart, klen, kesc, khash))
return(false);
JSON_WS(pos);
if(pos >= end || Buffer[pos] != ':')
return(Fail(JSON_EXPECTED_COLON, pos));
pos++;
JSON_WS(pos);
haskey = true;
continue; // parsear el valor
}
}
else
if(c == '[')
{
m_nodes[node].type = (uchar)JSON_ARRAY;
m_nodes[node].start = pos;
m_nodes[node].child = -1;
m_nodes[node].count = 0;
pos++;
JSON_WS(pos);
if(pos >= end)
return(Fail(JSON_UNEXPECTED_END, pos));
if(Buffer[pos] == ']')
{
pos++; // array vacio
m_nodes[node].len = pos - m_nodes[node].start;
}
else
{
depth++;
if(depth > m_maxdepth)
return(Fail(JSON_MAX_DEPTH_EXCEEDED, pos));
m_stk_last[depth] = lastChild;
m_stk_cnt[depth] = childCount;
lastChild = -1;
childCount = 0;
parent = node;
continue; // parsear 1er elemento
}
}
else
if(c == 't')
{
if(pos + 4 <= end && Buffer[pos + 1] == 'r' &&
Buffer[pos + 2] == 'u' && Buffer[pos + 3] == 'e')
{
m_nodes[node].type = (uchar)JSON_BOOL;
m_nodes[node].flags |= JSON_F_TRUE;
m_nodes[node].start = pos;
m_nodes[node].len = 4;
pos += 4;
}
else
return(Fail(JSON_UNEXPECTED_CHARACTER, pos));
}
else
if(c == 'f')
{
if(pos + 5 <= end && Buffer[pos + 1] == 'a' && Buffer[pos + 2] == 'l' &&
Buffer[pos + 3] == 's' && Buffer[pos + 4] == 'e')
{
m_nodes[node].type = (uchar)JSON_BOOL;
m_nodes[node].start = pos;
m_nodes[node].len = 5;
pos += 5;
}
else
return(Fail(JSON_UNEXPECTED_CHARACTER, pos));
}
Nique_372
2026-06-21 10:55:27 -05:00
else
if(c == 'n')
{
if(pos + 4 <= end && Buffer[pos + 1] == 'u' &&
Buffer[pos + 2] == 'l' && Buffer[pos + 3] == 'l')
{
m_nodes[node].type = (uchar)JSON_NULL;
m_nodes[node].start = pos;
m_nodes[node].len = 4;
pos += 4;
}
else
return(Fail(JSON_UNEXPECTED_CHARACTER, pos));
}
else
return(Fail(JSON_UNEXPECTED_CHARACTER, pos));
//================================================================
// FASE 2: valor completado -> resolver comas y cierres
//================================================================
bool needValue = false;
while(!needValue)
Nique_372
2026-06-21 10:55:27 -05:00
{
if(parent < 0)
{
JSON_WS(pos);
if(pos < end)
return(Fail(JSON_TRAILING_CHARACTERS, pos));
return(true);
}
JSON_WS(pos);
if(pos >= end)
return(Fail(JSON_UNEXPECTED_END, pos));
c = Buffer[pos];
if(m_nodes[parent].type == (uchar)JSON_OBJECT)
{
if(c == ',')
{
pos++;
JSON_WS(pos);
if(pos >= end || Buffer[pos] != '"')
return(Fail(JSON_EXPECTED_KEY, pos));
if(!ScanKey(pos, kstart, klen, kesc, khash))
return(false);
JSON_WS(pos);
if(pos >= end || Buffer[pos] != ':')
return(Fail(JSON_EXPECTED_COLON, pos));
pos++;
JSON_WS(pos);
haskey = true;
needValue = true;
}
else
if(c == '}')
{
pos++;
m_nodes[parent].len = pos - m_nodes[parent].start;
m_nodes[parent].count = childCount; // count al cerrar
parent = m_nodes[parent].parent; // "pop" O(1)
lastChild = m_stk_last[depth];
childCount = m_stk_cnt[depth];
depth--;
}
else
return(Fail(JSON_EXPECTED_COMMA, pos));
}
else // JSON_ARRAY
{
if(c == ',')
{
pos++;
JSON_WS(pos);
needValue = true;
}
else
if(c == ']')
{
pos++;
m_nodes[parent].len = pos - m_nodes[parent].start;
m_nodes[parent].count = childCount;
parent = m_nodes[parent].parent;
lastChild = m_stk_last[depth];
childCount = m_stk_cnt[depth];
depth--;
}
else
return(Fail(JSON_EXPECTED_COMMA, pos));
}
Nique_372
2026-06-21 10:55:27 -05:00
}
}
return(false); // inalcanzable
Nique_372
2026-06-21 10:55:27 -05:00
}
#undef JSON_WS
Nique_372
2026-06-21 10:55:27 -05:00
//+------------------------------------------------------------------+
//| Implementacion de JsonNode (requiere CJson completo) |
Nique_372
2026-06-21 10:55:27 -05:00
//+------------------------------------------------------------------+
ENUM_JSON_TYPE JsonNode::GetType(void) const
Nique_372
2026-06-21 10:55:27 -05:00
{
if(m_doc == NULL || m_idx < 0)
return(JSON_UNDEFINED);
return(m_doc.NodeType(m_idx));
Nique_372
2026-06-21 10:55:27 -05:00
}
int JsonNode::Size(void) const
Nique_372
2026-06-21 10:55:27 -05:00
{
if(m_doc == NULL)
return(0);
return(m_doc.NodeSize(m_idx));
Nique_372
2026-06-21 10:55:27 -05:00
}
JsonNode JsonNode::operator[](const string key)
{
JsonNode n;
n.m_doc = m_doc;
n.m_idx = (m_doc != NULL ? m_doc.FindMember(m_idx, key) : -1);
return(n);
}
Nique_372
2026-06-21 10:55:27 -05:00
JsonNode JsonNode::operator[](const int i)
{
JsonNode n;
n.m_doc = m_doc;
n.m_idx = (m_doc != NULL ? m_doc.ChildAt(m_idx, i) : -1);
return(n);
}
Nique_372
2026-06-21 10:55:27 -05:00
JsonNode JsonNode::Get(const string key)
Nique_372
2026-06-21 10:55:27 -05:00
{
JsonNode n;
n.m_doc = m_doc;
n.m_idx = (m_doc != NULL ? m_doc.FindMember(m_idx, key) : -1);
return(n);
Nique_372
2026-06-21 10:55:27 -05:00
}
JsonNode JsonNode::At(const int i)
Nique_372
2026-06-21 10:55:27 -05:00
{
JsonNode n;
n.m_doc = m_doc;
n.m_idx = (m_doc != NULL ? m_doc.ChildAt(m_idx, i) : -1);
return(n);
Nique_372
2026-06-21 10:55:27 -05:00
}
bool JsonNode::Exists(const string key)
Nique_372
2026-06-21 10:55:27 -05:00
{
return(m_doc != NULL && m_doc.FindMember(m_idx, key) >= 0);
Nique_372
2026-06-21 10:55:27 -05:00
}
JsonNode JsonNode::First(void)
Nique_372
2026-06-21 10:55:27 -05:00
{
JsonNode n;
n.m_doc = m_doc;
n.m_idx = (m_doc != NULL ? m_doc.NodeFirst(m_idx) : -1);
return(n);
Nique_372
2026-06-21 10:55:27 -05:00
}
JsonNode JsonNode::Next(void)
Nique_372
2026-06-21 10:55:27 -05:00
{
JsonNode n;
n.m_doc = m_doc;
n.m_idx = (m_doc != NULL ? m_doc.NodeNext(m_idx) : -1);
return(n);
Nique_372
2026-06-21 10:55:27 -05:00
}
string JsonNode::Key(void)
Nique_372
2026-06-21 10:55:27 -05:00
{
if(m_doc == NULL)
return("");
return(m_doc.NodeKey(m_idx));
Nique_372
2026-06-21 10:55:27 -05:00
}
string JsonNode::ToString(const string def)
Nique_372
2026-06-21 10:55:27 -05:00
{
if(m_doc == NULL)
return(def);
return(m_doc.NodeText(m_idx, def));
Nique_372
2026-06-21 10:55:27 -05:00
}
double JsonNode::ToDouble(const double def)
Nique_372
2026-06-21 10:55:27 -05:00
{
if(m_doc == NULL)
return(def);
return(m_doc.NodeDouble(m_idx, def));
Nique_372
2026-06-21 10:55:27 -05:00
}
long JsonNode::ToInteger(const long def)
{
if(m_doc == NULL)
return(def);
return(m_doc.NodeLong(m_idx, def));
}
Nique_372
2026-06-21 10:55:27 -05:00
bool JsonNode::ToBool(const bool def)
Nique_372
2026-06-21 10:55:27 -05:00
{
if(m_doc == NULL)
return(def);
return(m_doc.NodeBool(m_idx, def));
Nique_372
2026-06-21 10:55:27 -05:00
}
//+------------------------------------------------------------------+
//| Definicion de miembros estaticos |
//+------------------------------------------------------------------+
bool CJson::s_tables = false;
uchar CJson::s_hex[256];
double CJson::s_pow10[309];
Nique_372
2026-06-21 10:55:27 -05:00
#endif // __JSON_MQH__
Nique_372
2026-08-28 11:16:09 -05:00
//+------------------------------------------------------------------+