Fast JSON parser from the TSN ecosystem
  • MQL5 90%
  • MQL4 6%
  • C++ 2.7%
  • C 0.5%
  • CMake 0.3%
  • Other 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Nique_372 a1b411abe2
2026-08-09 09:17:06 -05:00
BenOther 2026-08-09 09:17:06 -05:00
Src Correciones de bugs 2026-08-04 09:29:06 -05:00
Test 2026-08-08 18:57:56 -05:00
Wf Correciones al nuevo uso de los iteradores 2026-07-26 17:16:21 -05:00
.gitignore 2026-08-08 17:59:32 -05:00
CHANGELOG.md 2026-08-04 09:07:11 -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 Actualizar README.md 2026-08-09 00:24:36 +00: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)
  • 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
  • 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)

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