Beobachten
1
0
Fork
Du hast bereits einen Fork von JsonParserByLeo erstellt
0
Fast JSON parser from the TSN ecosystem
  • MQL5 90.6%
  • MQL4 5.6%
  • C++ 2.3%
  • C 0.8%
  • CMake 0.3%
  • Andere 0.4%
Datei suchen
Repository-Dateien (neuester Commit zuerst)
Dateiname Letzte Commit-Nachricht Letztes Commit-Datum
2026-09-05 09:36:51 -05:00
BenOther Dejar de rastrear carpetas generadas (.vs, out, target) 2026-08-26 21:41:08 -05:00
Src mejoiras leeves en numbers (ya no se usa mask si no directo + -) una instruccion menso de xor y acceoso a memoria par aunion en dbl 2026-09-05 09:36:51 -05:00
Test mejoiras leeves en numbers (ya no se usa mask si no directo + -) una instruccion menso de xor y acceoso a memoria par aunion en dbl 2026-09-05 09:36:51 -05:00
Wf Correcion de includes 2026-09-03 11:22:55 -05:00
.gitignore Merge commit '1b480921d7' 2026-08-26 21:31:57 -05:00
CHANGELOG.md 2026-08-26 22:30:01 -05:00
dependencies.json 2026-07-19 07:41:52 -05:00
JsonParserByLeo.mqproj Generated by MQL5 Wizard 2026-06-02 15:28:54 -05:00
LICENSE Añadir LICENSE 2026-06-03 02:26:21 +00:00
README.md 2026-08-28 17:44:15 -05:00

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 switch over a token enum, no helper function calls during parsing, strictly O(n)
  • Two parsing engines: a pure-MQL5 engine (Parse(), no external dependencies) and an optional SIMD engine that runs through a companion C++ DLL (ParseSimd(), AVX2, see SIMD via DLL below) — same tape output either way, pick whichever fits your deployment
  • JIT perfect-hash key lookup: object access starts as linear probing (StrEquals); once a node passes TSN_JSON_JIT_MIN_REF_TO_HASING accesses, 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: CJsonNode is a lightweight struct (pointer + two ints), copying is free
  • Iterator support: CJsonIteratorObj and CJsonIteratorArray for clean traversal
  • Mutable DOM API: CDomNodeBase builds a real, editable node tree from the tape (ToDom) — add/remove keys, change a value's type in place, slice arrays Python-style (PySlicing), serialize back to JSON (CJsonDomSerializer), and load/dump native MQL5 structs directly (Load<T>() / GetAs<T>())
  • SAX streaming parser: CJsonParserStream + ITsnJsonParserStream feed the tape incrementally in fixed-size chunks (e.g. 64 bytes at a time) and fire OnStartObj/OnKey/OnInteger/... callbacks as it goes — no need to hold the whole document, or the whole tape, in memory at once
  • JSON Builder: CJsonBuilder/CJsonBuilderStr for constructing valid JSON (or an escaped JSON string literal) directly, without an intermediate DOM
  • JSON Pointer (RFC 6901): Query("/a/0/b~1c")-style lookups on both CJsonNode (tape) and CDomNodeBase (DOM) via CJsonPointerResolver
  • JSONPath-like query VM: a small register-based virtual machine (CSLDomQueryVm) compiles an assembly-like query language (INS_ACCESO_KEY, INS_COMPARE_*, INS_ITER_ASSING, custom INS_CALL-able functions, ...) to filter/traverse a DOM tree — think $.libros[?(@.valor > 100)] compiled down to a tiny bytecode loop
  • Three input formats, one API: AssingFile auto-detects by extension — .json (plain text, needs Parse()), .jsonasm (assembly-like text dump of the tape, needs ParseAssembly()), .jsontc (precompiled binary tape, ready to use immediately, no parsing step)
  • Compiled tape cache (.jsontc): SaveCompiled persists the tape, the source JSON (or a reference to its file), and every perfect-hash table already built — so a reload via AssingFile skips parsing entirely and keeps any JIT-hashed nodes already O(1)
  • 104 unit tests in Wf/ covering node access, the builder, DOM mutation, .jsonasm/.jsontc round-trips and malformed-input handling — runnable as a standalone script inside MetaTrader

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);

SIMD via DLL

For MQL5, Parse() is fast but limited to what a script/EA can do in pure MQL5. ParseSimd() offloads phase 1 (finding every structural character: {, }, [, ], :, ,, string bounds) to a small companion C++ DLL that uses AVX2 (SWAR bit-tricks for backslash/quote detection, pclmulqdq for the in-string prefix-xor, popcnt/tzcnt to walk set bits) — phase 2 (building the tape) still runs the same flat state machine. Same long[] tape as Parse(); only the structural scan changes.

#define JSONPARSERBYLEO_DLL
#define JSONPARSERBYLEO_DLL_AVX2
#include "Src\\JsonNode.mqh"

TSN::CJsonParser parser;
parser.Assing(my_json_string);
parser.CorrectPadding();       // pads to a 64-byte boundary when JSONPARSERBYLEO_DLL is defined
if(parser.ParseSimd())
 {
  TSN::CJsonNode root = parser.GetRoot();
 }

Building the DLL: source is in Src/DLL/ (Parser.cpp + CMake project), requires MSVC + a AVX2-capable CPU. Build it (CMakeLists.txt targets x64-windows-static, C++23, /arch:AVX2) and drop the resulting JsonParserByLeo-Avx2.dll into your terminal's MQL5/Libraries folder. Only needed if you want the SIMD path — the pure-MQL5 Parse() has no external dependency.


Performance

JsonParserByLeo is a fast JSON library. You can see the results of my "mini-investigation" of JSON libraries where I tested and benchmarked the parsing and node access capabilities of the most popular MQL5 libraries. I also included parsing only for libraries from other languages.


Repository Structure

JsonParserByLeo/
├── BenOther/    # Reference benchmarks in other languages (C++/simdjson, Python, Rust)
├── Src/         # Full MQL5 source (Parser, Node, DOM, SAX, Builder, Pointer, JsonPath VM)
│   └── DLL/     # Optional companion C++ DLL: AVX2 structural scan (Parser.cpp + CMake project)
├── Test/        # Manual tests and benchmarks (Test/Ben/), run as MQL5 scripts
└── Wf/          # 104 automated unit tests (Wf/Test.mq5), run as a standalone script

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