# 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.
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,
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..