- MQL5 90%
- MQL4 6%
- C++ 2.7%
- C 0.5%
- CMake 0.3%
- Other 0.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| BenOther | ||
| Src | ||
| Test | ||
| Wf | ||
| .gitignore | ||
| CHANGELOG.md | ||
| dependencies.json | ||
| JsonParserByLeo.mqproj | ||
| LICENSE | ||
| README.md | ||
A high-performance, memory-free JSON parser for MQL5, based on a flat tape architecture.
Single-pass iterative state machine (as is, without extra loops, "pure" o(n)): no recursion, no dynamic memory fragmentation, no overhead from function calls.
Main Features
- Tape-based zero-alloc model: the entire JSON is parsed into a single contiguous
long[]array - Single flat loop: one
switchover a token enum, no helper function calls during parsing, strictly O(n) - JIT perfect-hash key lookup: object access starts as linear probing (
StrEquals); once a node passesTSN_JSON_JIT_MIN_REF_TO_HASINGaccesses, a minimal perfect hash table is built for it on the fly (k = n, no slack), promoting that node's lookups to O(1). No FNV hashing is computed upfront during parsing. - Handle-based navigation:
CJsonNodeis a lightweight struct (pointer + two ints), copying is free - Iterator support:
CJsonIteratorObjandCJsonIteratorArrayfor clean traversal - Three input formats, one API:
AssingFileauto-detects by extension —.json(plain text, needsParse()),.jsonasm(assembly-like text dump of the tape, needsParseAssembly()),.jsontc(precompiled binary tape, ready to use immediately, no parsing step) - Compiled tape cache (
.jsontc):SaveCompiledpersists the tape, the source JSON (or a reference to its file), and every perfect-hash table already built — so a reload viaAssingFileskips parsing entirely and keeps any JIT-hashed nodes already O(1)
Parse and navigate
#include "Src\\JsonNode.mqh"
TSN::CJsonParser parser;
parser.Assing("{\"symbol\":\"EURUSD\",\"bid\":1.2345,\"ask\":1.2347,\"active\":true}");
parser.CorrectPadding();
parser.Parse();
TSN::CJsonNode root = parser.GetRoot();
string symbol = root["symbol"].ToString();
double bid = root["bid"].ToDouble(0.0);
bool active = root["active"].ToBool(false);
Build json as json string or valid json with CJsonBuilder
TSN::CJsonBuilderStr build;
build.PutChar('"');
build.Obj();
build.Key("Valor").ValS("└");
build.Key("asas").ValS("\r\n\v\t aaaaa");
uchar data[4] = {'h', 'o', 'l', 'a'};
build.KeyWV("valora").ValU(data);
build.EndObj();
build.PutChar('"');
Print(build.Build());
/*
"{\"Valor\":\"\\u2514\",\"asas\":\"\\r\\n\\v\\t aaaaa\",\"valora\":\"hola\"}"
*/
Iterate an object
TSN::CJsonIteratorObj it = root.BeginObj();
while(it.IsValid())
{
PrintFormat("%s : %s", it.Key(), it.Val().ToString());
it.Next();
}
Iterate an array
TSN::CJsonNode arr = root["prices"];
TSN::CJsonIteratorArray it = arr.BeginArr();
while(it.IsValid())
{
Print(it.Val().ToDouble(0.0));
it.Next();
}
Parse from file
TSN::CJsonParser parser;
parser.AssingFile("data.json", false);
parser.CorrectPadding();
parser.Parse();
DOM-API (Full mutable)
void OnStart()
{
//---
string json = "{\"ae\":[[10,10],[20,20]],\"comida\":\"\\ud83d\\ude00\",\"trapo\":10.05,\"a\":true,\"invalid\":\"\\xFF\",\"precios\":[10,20,30,40,50],\"va\":1"
",\"interpolado\":\"${{valor}}\"}";
//Print(json);
//---
TSN::CJsonParser parser;
parser.Assing(json);
parser.CorrectPadding();
parser.Parse();
parser.PrintCintaTypes(0, WHOLE_ARRAY);
//---
TSN::CDomNodeManager manager;
TSN::CJsonDomSerializer serializer;
TSN::CDomNodeBase* root = new TSN::CDomNodeBase(&manager);
parser.GetRoot().ToDom(root, &manager);
//---
string kes[];
root.GetKeys(kes);
ArrayPrint(kes);
//---
Print(root.Size());
Print(root["trapo"].ToDouble(0.0));
Print(root["ae"].At(1).At(1).ToInt(0));
Print(root["comida"].ToString());
// Agregar y cambiar tipo
root["sumando"] = 20.0;
Print(root.Size());
Print(root["sumando"].ToDouble(0.0));
root["sumando"] = true;
Print(root["sumando"].ToBool(false));
//--- Estadisticas
Print("Numero de arrays creados: ", manager.m_node_size);
Print("Numero de objetos creados: ", manager.m_objs_size);
Print("Numero de strgins creados: ", manager.m_string_stack_size);
//--- Otro json
TSN::CDomNodeManager manager2;
TSN::CDomNodeBase* ptr = new TSN::CDomNodeBase(&manager2); // vacio
ptr.NewObj(128);
ptr["valores"] = 20.0;
//---
root.Set("nueva_key", ptr); // No copia profunda si no PUNTERO..
ptr["coma"] = true;
TSN::CDomNodeBase* out[];
const int t = root["precios"].PySlicing(out, 0, -1, -1, -1);
for(int i = 0; i < t; i++)
Print("valor: ", out[i].ToInt(0)); // al revez 50,40,30,20,10
//---
Print(serializer.Serialize(root));
Print(CharArrayToString(serializer.m_buf, 0, serializer.m_buf_s));
//---
delete root;
delete ptr;
}
/*
2026.07.20 09:19:07.290 Dom (EURUSD,H1)
{"a":true,"invalid":"\\xFF","precios":[10,20,30,40,50],"va":1,"comida":"\ud83d\ude00","interpolado":"${{valor}}",
"sumando":true,"nueva_key":{"valores":20.00000000,"coma":true},"ae":[[10,10],[20,20]],"trapo":10.05000000}
*/
DOM API From and ToJson
struct Libro
{
string name;
string titulo;
double valor;
//---
Libro() {}
Libro(TSN::CDomNodeBase& obj)
{
name = obj["name"].ToString();
titulo = obj["titulo"].ToString();
valor = obj["valor"].ToDouble(0.00);
}
static void ToTsnDomNode(TSN::CDomNodeBase& obj, const Libro& other)
{
obj.NewObj(16);
obj["name"] = other.name;
obj["titulo"] = other.titulo;
obj["valor"] = other.valor;
}
};
void OnStart()
{
//---
TSN::CDomNodeManager manager;
Libro l;
l.name = "hola";
l.titulo = "titulo";
l.valor = 1.0;
// Load
TSN::CJsonDomSerializer ser;
TSN::CDomNodeBase node(&manager);
node.Load<Libro>(l);
Print(ser.Serialize(&node));
Print(CharArrayToString(ser.m_buf, 0, ser.m_buf_s)); // {"name":"hola","titulo":"titulo","valor":1.00000000}
//---
TSN::CJsonBuilder builder;
builder.Obj();
builder.KeyWV("name").ValSWV("hola");
builder.KeyWV("titulo").ValSWV("como");
builder.KeyWV("valor").Val(10.0490);
builder.EndObj();
//---
TSN::CJsonParser parser;
ArrayCopy(parser.m_raw, builder.m_buf, 0, 0, builder.m_pos);
parser.CalcLen();
parser.CorrectPadding();
parser.Parse();
// Dom building
TSN::CDomNodeBase node2(&manager); // le pasamos su manager
parser.GetRoot().ToDom(&node2, &manager);
//---
Libro l2 = node2.GetAs<Libro>();
Print(l2.name); // DomSer (EURUSD,H1) hola
Print(l2.titulo); // DomSer (EURUSD,H1) como
Print(l2.valor); // DomSer (EURUSD,H1) 10.049
}
Json Pointer (DOM and CJsonNode API)
//---
TSN::CJsonParser parser;
parser.Assing(g_json);
parser.CorrectPadding();
parser.Parse();
TSN::CJsonNode root = parser.GetRoot();
//---
Print("--- CJsonNode ---");
Print(root.Query("/config~1general/riesgo~0maximo").ToDouble(0.0));
Print(root.Query("/raro~1~0mix~0~11").ToString(""));
Print(root.Query("/ordenes/2/tags/2").ToString(""));
//---
TSN::CDomNodeManager manager;
TSN::CDomNodeBase node(&manager);
root.ToDom(&node, &manager);
//---
CJsonPointerResolver resolver;
resolver.Base(&node);
Print("--- CJsonPointerResolver ---");
Print(resolver.Query("/config~1general/riesgo~0maximo").ToDouble(0.0));
Print(resolver.Query("/raro~1~0mix~0~11").ToString(""));
Print(resolver.Query("/ordenes/2/tags/2").ToString(""));
/*
Pointer (EURUSD,M1) --- CJsonNode ---
Pointer (EURUSD,M1) 2.5
Pointer (EURUSD,M1) combinacion rara
Pointer (EURUSD,M1) riesgo/alto
Pointer (EURUSD,M1) --- CJsonPointerResolver ---
Pointer (EURUSD,M1) 2.5
Pointer (EURUSD,M1) combinacion rara
Pointer (EURUSD,M1) riesgo/alto
*/
Stream-With SAX Parse
class CTestStream : public TSN::ITsnJsonParserStream
{
public:
CTestStream(void) {}
~CTestStream(void) {}
void OnStartDocument() override final { Print("Inicio de documento"); }
void OnEndDocument() override final { Print("Fin de documento"); }
void OnStartObj() override final { Print("Inicio de objeto: ", m_ctx.CurrentStack()); }
void OnEndObJ() override final { Print("Fin de objeto: ", m_ctx.CurrentStack()); }
void OnStartArr() override final { Print("Inicio de array: ", m_ctx.CurrentStack()); }
void OnEndArr() override final { Print("Fin de array: ", m_ctx.CurrentStack()); }
void OnInteger(long v) override final { Print("Numero: ", v); }
void OnReal(double v) override final { Print("Real: ", v); }
void OnString(int s, int len) override final { Print("Texto: ", m_ctx.Unescape(s, s + len - 1)); }
void OnBool(bool v) override final { Print("Boleano: ", v); }
void OnNull() override final { Print("Null"); }
void OnKey(int s, int len) override final { Print("Clave: ", m_ctx.Unescape(s, s + len - 1)); }
};
//+------------------------------------------------------------------+
void OnStart()
{
//---
TSN::CJsonParserStream stream;
stream.SetCaller(new CTestStream(), true);
//---
string json = "{\"ae\":[[10,10],[20,20]],\"comida\":\"\\ud83d\\ude00\",\"trapo\":10.05,\"a\":true,\"invalid\":\"\\xFF\",\"precios\":[10,20,30,40,50],\"va\":1"
",\"interpolado\":\"${{valor}}\"}";
uchar raw[];
const int l = StringToCharArray(json, raw);
int p = 0;
// Simular pasadas de 64 bytes
while(p < l) // tien que ser menor, en un caso real quizas ahsta on end document
{
//--- le damos datos
// Tambien antes de dar los datos odemos llamar a CheckLimpieza
// Para que mueva pos a posicion 0 (reutlziacion) en caso se pueda si no seguir acomulando
// Ahora en este caso vamos sumando dado que m_len neceista crecer.... luego quizas se le peude reinicar..
stream.m_len += ArrayCopy(stream.m_raw, raw, stream.m_pos, p, 64); //
// intermante va parseando aumentado m_pos
// Otro si no da el documento para otro el bulce se dentecia aunqeu array copy ya menaja eso intemante
// si nos pamaos normaliza asi que no hay problema pero eso un aviso..
stream.ParseSreamSax();
// Aqui por ejemplo se peuden hacer mas cosas.. tu lo llamas cuando quieres
p += 64; // ya le dimos 64
}
}
Json Patch full Fast parse and VM (register-based)
void JsonPathSize(TSN::CSLDomQueryVm* state)
{
TSN::CDomNodeBase* ptr = state.FReadNode(0);
state.FPushInteger(ptr.Size());
}
void OnStart()
{
//---
const string json = "{\n"
"\"libros\":{\n"
"\"libro1\": {\"valor\":542,\"sumando\":[],\"comando\":30},\n"
"\"libro2\": {\"valor\":19,\"sumando\":[30],\"comando\":2},\n"
"\"libro3\": {\"valor\":452,\"sumando\":[20,50,65,89,419],\"comando\":7}\n"
" }\n"
"}";
//---
TSN::CJsonParser parser;
parser.Assing(json);
parser.CorrectPadding();
parser.Parse();
//---
TSN::CDomNodeManager manager;
TSN::CDomNodeBase node(&manager);
parser.GetRoot().ToDom(&node, &manager);
//---
TSN::CSLDomQueryVm vm;
vm.SetFunction("size", JsonPathSize);
// $.libros[?(@.comando * 20 < @.valor && @.sumando.size()].sumando[*]
const string asm =
"%reg 0 = 20 \n"
"%reg 1 = 1\n"
"INS_ACCESO_KEY_F K\"libros\"\n"
"INS_INICIAR_ITERACION 1 ; Solo se permiten objetos en esta iteracion\n"
"@Iteracion: ; Jump aqui\n"
" INS_ITER_ASSING 'Final' ; En caso falle salta a Final y terminamos .. \n"
" INS_ACCESO_KEY 'Iteracion' K\"comando\" ; Accedemos a comando \n"
" INS_SAVE 256 'Iteracion' ; Lo guardamos en el registro 256 si falla reset\n"
" INS_M_MUL 256 0 256 'Iteracion' ; Mulñtiplicamos el registro 256 por el 0 y lo guardamos en 256\n"
" INS_ACCESO_KEY 'Iteracion' K\"valor\" ; Accede a valor\n"
" INS_SAVE 257 'Iteracion'; Guardamos su valor actual\n"
" INS_COMPARE_MENOR 256 257 1 'Iteracion' ; Comparamos registros\n"
" INS_ACCESO_KEY 'Iteracion' K\"sumando\" ; Accedemos a sumando \n"
" INS_SAVE_NO_RESET 256 'Iteracion'; guadcamos en sumand\n"
// Nota que aqui el resultado se pondra apartir del 258 reg dado qeu el 257 se pone auto
// el numero de parametros escritos...
" INS_CALL 256 1 'size'; LLamamos a la funcino size y sus parametros emppizan en 256 y tiene 1 parametro a leer\n"
// Nota que esta verirficacion quizas no haga falta.. aunque si esperamos exactamentwe N parameotrs
// o digamos 1 quizas si si no se nos desalinea... nuestra lecutra.. auqnue quizas nos podemos saltar a cierta
// direccion daod que sabemos que tenemos n parmaeotrs en el reg 257
// A futuro si veo que se requiere separar o casos mas complejos como iterar sobre los parametros obtenidos
// ahi si si usariosm offsets... ahi quizas lo agrego no es complejo es cosa de cambiar layort y nueva sintaxis de asm
// pero ya eso creo que tiraraimso ya a una vm casi de un lenguaje solo faltura stack frames y ya por que en teoria
// ahora mismo podemos tener varaibles, bucles, con los ifs, etc...
" INS_COMPARE_EQ 257 1 1 'Iteracion' ; Verificamos que el numero de retornanos es 1\n"
" INS_COMPARE_IS 258 'Iteracion' 1 ; Usamos is para comprar su valor\n"
" ; Si llegamos hasta aqui pasamos todo entonces copy all\n"
" INS_COPY_ALL 'Iteracion' \n"
" INS_JUMP 'Iteracion'; Ahora nos dirigimos devuelva a iteracion \n"
"@Final: \n"
" INS_SALIDA ; salimos"
;
//---
vm.Assing(asm);
vm.CorrectPadding();
vm.CompileAsm();
vm.LockValues();
vm.BaseNode(&node);
//---
vm.Summary();
//---
TSN::CDomNodeBase* elements[];
vm.Run(elements);
//---
const int t = vm.m_el_s;
for(int i = 0; i < t; i++)
{
Print(elements[i].ToInt(0));
}
//---
/* Output:
JPath (EURUSD,M1) 20
JPath (EURUSD,M1) 50
JPath (EURUSD,M1) 65
JPath (EURUSD,M1) 89
JPath (EURUSD,M1) 419
*/
}
Compile and reload a .jsontc tape cache
SaveCompiled writes the parsed tape (plus the source JSON and every perfect-hash table already built) to a binary .jsontc file. Reloading it via AssingFile skips parsing entirely:
//--- Compile once
TSN::CJsonParser parser;
parser.Assing(my_json_string);
parser.CorrectPadding();
parser.Parse();
parser.GetRoot()["symbol"]; // (optional) warm up perfect-hash tables before saving
parser.SaveCompiled("data.jsontc", false);
//--- Reload later — no Parse()/ParseAssembly() needed, tape and hash tables are restored as-is
TSN::CJsonParser parser2;
parser2.AssingFile("data.jsontc", false);
TSN::CJsonNode root = parser2.GetRoot();
There's also an overload that stores a reference to an external JSON file instead of embedding it:
parser.SaveCompiled("data.jsontc", false, "data.json", false);
Performance
Benchmark: twitter.json (616.7 KB), 1000 iterations. Same file, Iterations, and Machine (Laptop)
Note: Here we measure how quickly the JSON is parsed.
All benchmarks are located in: Test\Ben\
| Parser | Language | Time (ms, total / 1000 iter) |
|---|---|---|
| simdjson::dom::parser (reused parser) | C++ | 380-384 |
| JsonParserByLeo (with-SIMD via DLL) | MQL5\C++ DLL | 376-384 |
| sonic-rs (Tiped Struct) | Rust | 460-461 |
| simdjson.Parser (reused parser) | Python | 475 |
| JsonParserByLeo ASM (One copy of array - JSONASM File R460kb~) | MQL5 | 651-653 Stable |
| JsonParserByLeo (One copy of array) | MQL5 | 662 - 667 |
| serde_json (Tiped Struct) | Rust | 788-796 |
| simd-json (Tiped-Struct) | Rust | 915-972 |
| fast_json v3.4 (One copy of array) | MQL5 | 1229 - 1300 |
| YamlParserByLeo (One copy of array) | MQL5 | 1294-1295 |
| Claude Opus 4.8 (Effort=Max) (Best Ai now) (Extended reasoning activated) Generating code (Fast json lib) (One copy of array) | MQL5 | 1312-1313 Stable |
| GLM 5.2 (MAX - Depth Tink) Generating code (Fast json lib) (One copy of array) | MQL5 | 1333-1336 |
| orjson | Python | 1863 |
| ding9736/MQL5-JsonLib (One copy of string) (no dom building only tape parsing) | MQL5 | 2145-2163 |
| ryml (Pure parse time) | C++ | 2846.55 |
| MQL5 Lite (goose) Metaeditor AI Model | MQL5 | 3242-3250 |
| simdjson.Parser + as_dict | Python | 4309 |
| simdjson.loads | Python | 4955 |
| ujson | Python | 5322 |
| json (stdlib) | Python | 5860 |
| JAson (One copy of array) | MQL5 | ~(19070-19220) |
| ToyJson3 (One copy of string) Only tokenization | MQL5 | ~(24600) Stable |
| CJsonNode (One copy of array) | MQL5 | ~(76720-80470) |
Performance in acceses
Benchmark: test.json, 1000 iterations. Same file, Iterations, and Machine (Laptop)
Note: Here we measure how fast access to a node:
root["key..."].
All benchmarks are located in: Test\All\
| Parser | Language | Time (microseconds, total / 1000 iter) |
|---|---|---|
| JsonParserByLeo | MQL5 | 111-112 |
| fast_json v3.4 | MQL5 | 223 Stable |
| Claude Opus 4.8 (Effort=Max) (Best Ai now) (Extended reasoning activated) Generating code (Fast json lib) (One copy of array) | MQL5 | 595-605 |
Machine
- OS Name: Microsoft Windows 10 Pro
- Version: 10.0.19045 Build 19045
- OS Manufacturer: Microsoft Corporation
- System Manufacturer: LENOVO
- System Model: 81DE
- System Type: x64-based PC
- System SKU: LENOVO_MT_81DE_BU_idea_FM_ideapad 330-15IKB
- Processor: Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz, 1800 MHz, 4 Cores, 8 Logical Processors
- RAM Type (Form Factor): SODIMM
- RAM Speed: 2133 MHz
- Installed Physical Memory (RAM): 8.00 GB
- Total Physical Memory: 7.91 GB
- Available Physical Memory: 2.87 GB
- Total Virtual Memory: 15.2 GB
- Available Virtual Memory: 9.13 GB
- Page File Space: 7.25 GB
- Storage: 13 GB Intel MEMPEI1J016GAL SSD, 224 GB HP SSD S650 240GB SSD
- Graphics Card: AMD Radeon(TM) 530 (2 GB), Intel(R) UHD Graphics 620 (128 MB)
Performance notes
- MQL5 run: MetaTrader 5 x64 build 5836-6070
- Python/C++ runs: Same machine (py = 3.10.9) and C++ (CXX23~ with optimizations)
Repository Structure
JsonParserByLeo/
├── BenOther/ # Benchmarks for C++, Py, Rust
├── Src/ # Full code (Defines, Node, Parser)
├── Test/ # Test and Benchmarks (vs)
└── Wf/ # 100+ Unit test
License
Read Full License
By downloading or using this repository, you accept the license terms.
Requirements
See dependencies.json for the full list.
Installation
cd "C:\Users\YOUR_USER\AppData\Roaming\MetaQuotes\Terminal\YOUR_ID\MQL5\Shared Projects"
tsndep install "https://forge.mql5.io/nique_372/JsonParserByLeo.git"
Requires the tsndep package, available on PyPI. It automatically downloads and installs all declared dependencies.
Quick Start
1. Include the library:
#include "..\\JsonParserByLeo\\Src\\JsonNode.mqh"
2. Parse and access:
TSN::CJsonParser parser;
parser.Assing(my_json_string);
parser.CorrectPadding();
if(parser.Parse())
{
TSN::CJsonNode root = parser.GetRoot();
double price = root["price"].ToDouble(0.0);
}
3. Re-parse without re-copying (benchmark pattern):
parser.Assing(raw_string); // copy once
parser.CorrectPadding();
for(int i = 0; i < 1000; i++)
parser.Parse(); // re-parse in-place
Roadmap
- Add suport for JSONPath [In progres 100% ASM ready] [Added 24/7/26]
- Add suport for (deserialize\serialize native structs\objs) [Added 23/7/26]
- Add Optimizer and Compiler for JsonPath queryes
Contact
- Platform: MQL5 Community
- Profile: https://www.mql5.com/es/users/nique_372
- Articles: https://www.mql5.com/es/users/nique_372/publications