//+------------------------------------------------------------------+ //| DomTest.mqh | //| Copyright 2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property strict #ifndef JSONPARSERBYLEO_WF_INC_DOMTEST_MQH #define JSONPARSERBYLEO_WF_INC_DOMTEST_MQH //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "Def.mqh" #resource "dom_test.json" as const string g_dom_test_json //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ namespace TSN { //+------------------------------------------------------------------+ //| 1. ToDom() sobre objeto raiz valido retorna true y crea el root | //+------------------------------------------------------------------+ class CTestDom_ToDomBasicSucceeds : public ICiTest { public: inline string Name() const override final { return "Dom_ToDom_Basic_Succeeds"; } inline int Importance() const override final { return 95; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; bool ok = parser.GetRoot().ToDom(root, &manager); if(!ok || root == NULL) { msg = "ToDom() fallo o root quedo NULL"; return false; } delete root; return true; } }; //+------------------------------------------------------------------+ //| 2. ToDom() preserva valores escalares (string/double/bool/int) | //+------------------------------------------------------------------+ class CTestDom_ToDomPreservesScalars : public ICiTest { public: inline string Name() const override final { return "Dom_ToDom_PreservesScalars"; } inline int Importance() const override final { return 95; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); string symbol = root["symbol"].ToString("__MISSING__"); double bid = root["bid"].ToDouble(-1.0); bool active = root["active"].ToBool(false); long neg = root["neg"].ToInt(0); bool fail = (symbol != "EURUSD" || MathAbs(bid - 1.2345) > 0.0001 || !active || neg != -42); delete root; if(fail) { msg = StringFormat("valores no preservados tras ToDom: symbol=%s bid=%.4f active=%s neg=%d", symbol, bid, active ? "true" : "false", neg); return false; } return true; } }; //+------------------------------------------------------------------+ //| 3. ToDom() preserva anidamiento (meta.nested.deep) | //+------------------------------------------------------------------+ class CTestDom_ToDomPreservesNesting : public ICiTest { public: inline string Name() const override final { return "Dom_ToDom_PreservesNesting"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); //parser.PrintCintaTypes(0,WHOLE_ARRAY); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; //TSN::CJsonDomSerializer ser; if(!parser.GetRoot().ToDom(root, &manager)) { msg = "Fallo al pasar a dom"; return false; } //ser.Serialize(root); //Print(CharArrayToString(ser.m_buf, 0, ser.m_buf_s)); string deep = root["meta"]["nested"]["deep"].ToString("__MISSING__"); delete root; if(deep != "ok") { msg = StringFormat("esperado 'ok', obtenido '%s'", deep); return false; } return true; } }; //+------------------------------------------------------------------+ //| 4. ToDom() preserva arrays anidados: root["ae"].At(1).At(1) | //+------------------------------------------------------------------+ class CTestDom_ToDomPreservesNestedArray : public ICiTest { public: inline string Name() const override final { return "Dom_ToDom_PreservesNestedArray"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); long val = root["ae"].At(1).At(1).ToInt(-1); delete root; if(val != 20) { msg = StringFormat("esperado 20, obtenido %d", val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 5. ToDom() sobre un valor escalar (no obj/arr) falla (false) | //+------------------------------------------------------------------+ class CTestDom_ToDomOnScalarFails : public ICiTest { public: inline string Name() const override final { return "Dom_ToDom_OnScalarNode_Fails"; } inline int Importance() const override final { return 75; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* leaf = NULL; TSN::CDomNodeManager manager; // "symbol" es un STRING escalar, no OBJ/ARR -> debe fallar bool ok = parser.GetRoot()["symbol"].ToDom(leaf, &manager); if(ok) { msg = "ToDom() sobre nodo escalar devolvio true (esperado false)"; if(leaf != NULL) delete leaf; return false; } return true; } }; //+------------------------------------------------------------------+ //| 6. GetKeys()/Size() del DOM tras ToDom() coinciden con el origina| //+------------------------------------------------------------------+ class CTestDom_ToDomKeysAndSizeMatch : public ICiTest { public: inline string Name() const override final { return "Dom_ToDom_KeysAndSize_Match"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); string keys[]; int count = root.GetKeys(keys); int size = root.Size(); delete root; if(count != size || count <= 0) { msg = StringFormat("GetKeys count=%d, Size()=%d (deberian coincidir y ser > 0)", count, size); return false; } return true; } }; //+------------------------------------------------------------------+ //| 7. root["nueva_key"] = double crea la clave y cambia Size() | //+------------------------------------------------------------------+ class CTestDom_MutateAddNewKey : public ICiTest { public: inline string Name() const override final { return "Dom_Mutate_AddNewKey_Double"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); const int before = root.Size(); root["sumando"] = 20.0; const int after = root.Size(); double val = root["sumando"].ToDouble(-1.0); delete root; if(after != before + 1 || MathAbs(val - 20.0) > 0.0001) { msg = StringFormat("esperado size+1 y val=20.0, obtenido before=%d after=%d val=%.4f", before, after, val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 8. operator= cambia el tipo dinamicamente (double -> bool) | //+------------------------------------------------------------------+ class CTestDom_MutateChangeTypeDoubleToBool : public ICiTest { public: inline string Name() const override final { return "Dom_Mutate_ChangeType_DoubleToBool"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); root["sumando"] = 20.0; root["sumando"] = true; // cambia de FLT a BOL bool val = root["sumando"].ToBool(false); delete root; if(!val) { msg = "tras cambiar a bool, ToBool(false) devolvio false (esperado true)"; return false; } return true; } }; //+------------------------------------------------------------------+ //| 9. Cambiar un OBJ/ARR a escalar libera el contenedor previo | //| (no debe crashear ni dejar referencias colgantes accesibles) | //+------------------------------------------------------------------+ class CTestDom_MutateObjectToScalarFreesContainer : public ICiTest { public: inline string Name() const override final { return "Dom_Mutate_ObjectToScalar_FreesContainer"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); // "meta" es un objeto anidado; lo convertimos a string // operator[] devuelve el puntero real -> operator=(string&) libera el // OBJ previo internamente (ver CDomNodeBase::operator=(const string&)) bool assign_ok = (root["meta"].As("reemplazado")); string val = root["meta"].ToString("__MISSING__"); delete root; if(!assign_ok || val != "reemplazado") { msg = StringFormat("esperado 'reemplazado', obtenido '%s' (assign_ok=%s)", val, assign_ok ? "true" : "false"); return false; } return true; } }; //+------------------------------------------------------------------+ //| 10. Remove(key) elimina la clave y reduce Size() | //+------------------------------------------------------------------+ class CTestDom_RemoveKeyReducesSize : public ICiTest { public: inline string Name() const override final { return "Dom_Remove_Key_ReducesSize"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); const int before = root.Size(); bool removed = root.Remove("symbol"); const int after = root.Size(); bool still_has = root.HasKey("symbol"); delete root; if(!removed || after != before - 1 || still_has) { msg = StringFormat("removed=%s before=%d after=%d still_has=%s", removed ? "true" : "false", before, after, still_has ? "true" : "false"); return false; } return true; } }; //+------------------------------------------------------------------+ //| 11. Remove(key) sobre clave inexistente devuelve false | //+------------------------------------------------------------------+ class CTestDom_RemoveMissingKeyReturnsFalse : public ICiTest { public: inline string Name() const override final { return "Dom_Remove_MissingKey_ReturnsFalse"; } inline int Importance() const override final { return 70; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); bool removed = root.Remove("no_existe_esta_clave"); delete root; if(removed) { msg = "Remove() sobre clave inexistente devolvio true"; return false; } return true; } }; //+------------------------------------------------------------------+ //| 12. Remove(index) sobre array elimina el elemento y reduce Size | //+------------------------------------------------------------------+ class CTestDom_RemoveIndexReducesArraySize : public ICiTest { public: inline string Name() const override final { return "Dom_Remove_Index_ReducesArraySize"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); CDomNodeBase* precios = root["precios"]; // [10,20,30,40,50] const int before = precios.Size(); bool removed = precios.RemoveItem(0); const int after = precios.Size(); delete root; if(!removed || after != before - 1) { msg = StringFormat("removed=%s before=%d after=%d", removed ? "true" : "false", before, after); return false; } return true; } }; //+------------------------------------------------------------------+ //| 13. AddItem() en array standalone incrementa Size() | //+------------------------------------------------------------------+ class CTestDom_AddItemIncreasesSize : public ICiTest { public: inline string Name() const override final { return "Dom_AddItem_IncreasesSize"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CDomNodeManager manager; TSN::CDomNodeBase* arr = new TSN::CDomNodeBase(&manager); arr.NewArr(4); arr.AddItem(new TSN::CDomNodeBase(&manager, TSN_SBL_BASE_TYPE_INT, 1)); arr.AddItem(new TSN::CDomNodeBase(&manager, TSN_SBL_BASE_TYPE_INT, 2)); const int size = arr.Size(); long first = arr.At(0).ToInt(-1); long second = arr.At(1).ToInt(-1); delete arr; if(size != 2 || first != 1 || second != 2) { msg = StringFormat("esperado size=2 [1,2], obtenido size=%d [%d,%d]", size, first, second); return false; } return true; } }; //+------------------------------------------------------------------+ //| 14. InRange() valida correctamente indices de array | //+------------------------------------------------------------------+ class CTestDom_InRangeValidatesIndices : public ICiTest { public: inline string Name() const override final { return "Dom_InRange_ValidatesIndices"; } inline int Importance() const override final { return 75; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); CDomNodeBase* precios = root["precios"]; // 5 elementos bool in_range_ok = precios.InRange(4); bool in_range_bad = precios.InRange(99); delete root; if(!in_range_ok || in_range_bad) { msg = StringFormat("InRange(4)=%s InRange(99)=%s", in_range_ok ? "true" : "false", in_range_bad ? "true" : "false"); return false; } return true; } }; //+------------------------------------------------------------------+ //| 15. AtSafe() fuera de rango devuelve NULL sin crashear | //+------------------------------------------------------------------+ class CTestDom_AtSafeOutOfRangeReturnsNull : public ICiTest { public: inline string Name() const override final { return "Dom_AtSafe_OutOfRange_ReturnsNull"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); CDomNodeBase* precios = root["precios"]; CDomNodeBase* out = precios.AtSafe(999); delete root; if(out != NULL) { msg = "AtSafe(999) fuera de rango no devolvio NULL"; return false; } return true; } }; //+------------------------------------------------------------------+ //| 16. Clear() sobre objeto elimina todas sus claves (Size() -> 0) | //+------------------------------------------------------------------+ class CTestDom_ClearObjectEmptiesIt : public ICiTest { public: inline string Name() const override final { return "Dom_Clear_Object_EmptiesIt"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); CDomNodeBase* meta = root["meta"]; meta.Clear(); const int size_after = meta.Size(); delete root; if(size_after != 0) { msg = StringFormat("esperado Size()=0 tras Clear(), obtenido %d", size_after); return false; } return true; } }; //+------------------------------------------------------------------+ //| 17. NewObj + Set + operator[] construyen un objeto correctamente | //+------------------------------------------------------------------+ class CTestDom_StandaloneNewObjSetGet : public ICiTest { public: inline string Name() const override final { return "Dom_Standalone_NewObj_SetGet"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CDomNodeManager manager; TSN::CDomNodeBase* root = new TSN::CDomNodeBase(&manager); root.NewObj(8); root["a"] = (long)1; root["b"].As("hola"); root["c"] = true; long a = root["a"].ToInt(-1); string b = root["b"].ToString("__MISSING__"); bool c = root["c"].ToBool(false); int size = root.Size(); delete root; if(a != 1 || b != "hola" || !c || size != 3) { msg = StringFormat("a=%d b=%s c=%s size=%d", a, b, c ? "true" : "false", size); return false; } return true; } }; //+------------------------------------------------------------------+ //| 18. NewArr + AddItem construyen un array homogeneo correctamente | //+------------------------------------------------------------------+ class CTestDom_StandaloneNewArrAddItem : public ICiTest { public: inline string Name() const override final { return "Dom_Standalone_NewArr_AddItem"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CDomNodeManager manager; TSN::CDomNodeBase* arr = new TSN::CDomNodeBase(&manager); arr.NewArr(8); for(int i = 0; i < 5; i++) arr.AddItem(new TSN::CDomNodeBase(&manager, TSN_SBL_BASE_TYPE_INT, (long)(i * 10))); int size = arr.Size(); long third = arr.At(2).ToInt(-1); delete arr; if(size != 5 || third != 20) { msg = StringFormat("esperado size=5 third=20, obtenido size=%d third=%d", size, third); return false; } return true; } }; //+------------------------------------------------------------------+ //| 19. ToArray extrae un array standalone a un array MQL5 | //+------------------------------------------------------------------+ class CTestDom_StandaloneToArray : public ICiTest { public: inline string Name() const override final { return "Dom_Standalone_ToArray"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CDomNodeManager manager; TSN::CDomNodeBase* arr = new TSN::CDomNodeBase(&manager); arr.NewArr(4); arr.AddItem(new TSN::CDomNodeBase(&manager, TSN_SBL_BASE_TYPE_INT, (long)7)); arr.AddItem(new TSN::CDomNodeBase(&manager, TSN_SBL_BASE_TYPE_INT, (long)8)); arr.AddItem(new TSN::CDomNodeBase(&manager, TSN_SBL_BASE_TYPE_INT, (long)9)); long out[]; int count = arr.ToArray(out); delete arr; if(count != 3 || out[0] != 7 || out[2] != 9) { msg = StringFormat("esperado count=3 [7,..,9], obtenido count=%d", count); return false; } return true; } }; //+------------------------------------------------------------------+ //| 20. Patron multi-manager: Set() con puntero de OTRO manager no lo | //| copia (comparten el puntero), y cada manager libera solo lo | //| que es suyo — replica exacto del patron del README/Dom.mq5. | //+------------------------------------------------------------------+ class CTestDom_MultiManagerSetSharesPointer : public ICiTest { public: inline string Name() const override final { return "Dom_MultiManager_Set_SharesPointer_NoDoubleFree"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CDomNodeManager manager1; TSN::CDomNodeBase* root = new TSN::CDomNodeBase(&manager1); root.NewObj(8); root["a"] = (long)1; //--- Segundo manager y arbol independiente TSN::CDomNodeManager manager2; TSN::CDomNodeBase* ptr = new TSN::CDomNodeBase(&manager2); ptr.NewObj(4); ptr["valores"] = 20.0; //--- Colgamos ptr (de manager2) dentro de root (de manager1) sin copia profunda bool set_ok = root.Set("nueva_key", ptr); ptr["coma"] = true; // seguimos pudiendo mutar ptr normalmente double valores = root["nueva_key"]["valores"].ToDouble(-1.0); bool coma = root["nueva_key"]["coma"].ToBool(false); //--- Cada manager libera SOLO lo suyo: como root.m_ctx == manager1 y // ptr.m_ctx == manager2, 'delete root' NO debe liberar 'ptr' (evita // doble free); hay que liberarlo explicitamente aparte. delete root; delete ptr; if(!set_ok || MathAbs(valores - 20.0) > 0.0001 || !coma) { msg = StringFormat("set_ok=%s valores=%.4f coma=%s", set_ok ? "true" : "false", valores, coma ? "true" : "false"); return false; } return true; } }; //+------------------------------------------------------------------+ //| 21. Serialize() de un objeto standalone simple produce JSON valid| //+------------------------------------------------------------------+ class CTestDomSerializer_SimpleObject : public ICiTest { public: inline string Name() const override final { return "DomSerializer_SimpleObject"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CDomNodeManager manager; TSN::CDomNodeBase* root = new TSN::CDomNodeBase(&manager); root.NewObj(4); root["id"] = (long)1; root["active"] = true; TSN::CJsonDomSerializer serializer; bool ok = serializer.Serialize(root); string out = CharArrayToString(serializer.m_buf, 0, serializer.m_buf_s); delete root; if(!ok || out != "{\"id\":1,\"active\":true}") { msg = StringFormat("ok=%s obtenido '%s'", ok ? "true" : "false", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 22. Serialize() escapa strings correctamente (comillas/newline) | //+------------------------------------------------------------------+ class CTestDomSerializer_EscapesStrings : public ICiTest { public: inline string Name() const override final { return "DomSerializer_EscapesStrings"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CDomNodeManager manager; TSN::CDomNodeBase* root = new TSN::CDomNodeBase(&manager); root.NewObj(4); root["k"].As("line1\nline2 \"quoted\""); TSN::CJsonDomSerializer serializer; serializer.Serialize(root); string out = CharArrayToString(serializer.m_buf, 0, serializer.m_buf_s); delete root; if(out != "{\"k\":\"line1\\nline2 \\\"quoted\\\"\"}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 23. Serialize() de array anidado produce estructura correcta | //+------------------------------------------------------------------+ class CTestDomSerializer_NestedArray : public ICiTest { public: inline string Name() const override final { return "DomSerializer_NestedArray"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CDomNodeManager manager; TSN::CDomNodeBase* root = new TSN::CDomNodeBase(&manager); root.NewArr(4); root.AddItem(new TSN::CDomNodeBase(&manager, TSN_SBL_BASE_TYPE_INT, (long)1)); root.AddItem(new TSN::CDomNodeBase(&manager, TSN_SBL_BASE_TYPE_INT, (long)2)); TSN::CJsonDomSerializer serializer; serializer.Serialize(root); string out = CharArrayToString(serializer.m_buf, 0, serializer.m_buf_s); delete root; if(out != "[1,2]") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 24. Serialize() de null produce el literal "null" | //+------------------------------------------------------------------+ class CTestDomSerializer_NullValue : public ICiTest { public: inline string Name() const override final { return "DomSerializer_NullValue"; } inline int Importance() const override final { return 75; } bool Run(string &msg) override final { TSN::CDomNodeManager manager; TSN::CDomNodeBase* root = new TSN::CDomNodeBase(&manager); root.NewObj(4); root["k"]; // operator[] crea el nodo hijo como NULL (TSN_SBL_BASE_DOM_NULL) por defecto TSN::CJsonDomSerializer serializer; serializer.Serialize(root); string out = CharArrayToString(serializer.m_buf, 0, serializer.m_buf_s); delete root; if(out != "{\"k\":null}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 25. Round-trip completo: Parse -> ToDom -> mutar -> Serialize -> | //| Reparsear el resultado y verificar los valores mutados | //+------------------------------------------------------------------+ class CTestDomSerializer_FullRoundTripWithMutation : public ICiTest { public: inline string Name() const override final { return "DomSerializer_FullRoundTrip_WithMutation"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonParser parser; parser.Assing(g_dom_test_json); parser.CorrectPadding(); parser.Parse(); TSN::CDomNodeBase* root; TSN::CDomNodeManager manager; parser.GetRoot().ToDom(root, &manager); //--- Mutamos: agregamos una clave nueva root["sumando"] = 20.0; //--- Serializamos de vuelta a texto TSN::CJsonDomSerializer serializer; serializer.Serialize(root); string out = CharArrayToString(serializer.m_buf, 0, serializer.m_buf_s); delete root; //--- Reparseamos el resultado con un parser nuevo, independiente TSN::CJsonParser p2; p2.Assing(out); p2.CorrectPadding(); if(!p2.Parse()) { msg = StringFormat("el JSON serializado no es reparseable: '%s'", out); return false; } double sumando = p2.GetRoot()["sumando"].ToDouble(-1.0); string symbol = p2.GetRoot()["symbol"].ToString("__MISSING__"); if(MathAbs(sumando - 20.0) > 0.0001 || symbol != "EURUSD") { msg = StringFormat("tras round-trip completo: sumando=%.4f symbol=%s", sumando, symbol); return false; } return true; } }; } //+------------------------------------------------------------------+ #endif // JSONPARSERBYLEO_WF_INC_DOMTEST_MQH