//+------------------------------------------------------------------+ //| NodeTest.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_NODETEST_MQH #define JSONPARSERBYLEO_WF_INC_NODETEST_MQH //+------------------------------------------------------------------+ //| Todos estos tests comparten UN SOLO parser ya parseado | //| (fixture_test.json), inyectado por puntero. Ningun test | //| vuelve a parsear -> cero costo repetido de Assing/Parse. | //+------------------------------------------------------------------+ #include "Def.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ namespace TSN { //+------------------------------------------------------------------+ //| 1. operator[] sobre clave existente top-level (string) | //+------------------------------------------------------------------+ class CTestNode_BracketExistingKey : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_BracketExistingKey(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_Bracket_ExistingKey"; } inline int Importance() const override final { return 95; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); string val = root["symbol"].ToString("__MISSING__"); if(val != "EURUSD") { msg = StringFormat("esperado 'EURUSD', obtenido '%s'", val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 2. operator[] sobre clave inexistente -> nodo invalido | //+------------------------------------------------------------------+ class CTestNode_BracketMissingKey : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_BracketMissingKey(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_Bracket_MissingKey"; } inline int Importance() const override final { return 95; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode node = root["no_existe"]; string val = node.ToString("__DEFAULT__"); if(val != "__DEFAULT__") { msg = StringFormat("esperado default '__DEFAULT__', obtenido '%s'", val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 3. Acceso encadenado profundo (5 niveles) | //+------------------------------------------------------------------+ class CTestNode_BracketChainedDeep : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_BracketChainedDeep(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_Bracket_ChainedDeep5Levels"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); string val = root["deeply_nested"]["l1"]["l2"]["l3"]["l4"]["l5"].ToString("__MISSING__"); if(val != "reached_bottom") { msg = StringFormat("esperado 'reached_bottom', obtenido '%s'", val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 4. At() sobre indice valido de array de strings | //+------------------------------------------------------------------+ class CTestNode_AtValidIndex : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_AtValidIndex(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_At_ValidIndex"; } inline int Importance() const override final { return 95; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode tags = root["tags"]; string first = tags.At(0).ToString("__MISSING__"); string second = tags.At(1).ToString("__MISSING__"); if(first != "fx" || second != "major") { msg = StringFormat("esperado 'fx'/'major', obtenido '%s'/'%s'", first, second); return false; } return true; } }; //+------------------------------------------------------------------+ //| 5. At() fuera de rango -> no debe crashear, InRangeSafe false | //+------------------------------------------------------------------+ class CTestNode_AtOutOfRange : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_AtOutOfRange(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_At_OutOfRange"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode tags = root["tags"]; // 2 elementos, indices validos 0-1 const int index = 50; if(tags.InRangeSafe(index)) { string out = tags.At(index).ToString(); msg = StringFormat("acceso fuera de rango devolvio dato inesperado: %s", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 6. Array vacio -> At(0) invalido, sin crash | //+------------------------------------------------------------------+ class CTestNode_AtOnEmptyArray : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_AtOnEmptyArray(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_At_EmptyArray"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode empty = root["empty_arr"]; if(empty.InRangeSafe(0)) { msg = "InRangeSafe(0) en array vacio devolvio true"; return false; } string val = empty.At(0).ToString("__DEFAULT__"); if(val != "__DEFAULT__") { msg = StringFormat("At(0) en array vacio no devolvio default, obtenido '%s'", val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 7. HasKey sobre clave existente | //+------------------------------------------------------------------+ class CTestNode_HasKeyTrue : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_HasKeyTrue(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_HasKey_True"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); if(!root.HasKey("symbol")) { msg = "HasKey('symbol') devolvio false, esperado true"; return false; } return true; } }; //+------------------------------------------------------------------+ //| 8. HasKey sobre clave inexistente | //+------------------------------------------------------------------+ class CTestNode_HasKeyFalse : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_HasKeyFalse(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_HasKey_False"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); if(root.HasKey("no_existe")) { msg = "HasKey('no_existe') devolvio true, esperado false"; return false; } return true; } }; //+------------------------------------------------------------------+ //| 9. HasKey sobre objeto vacio {} | //+------------------------------------------------------------------+ class CTestNode_HasKeyEmptyObj : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_HasKeyEmptyObj(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_HasKey_EmptyObj"; } inline int Importance() const override final { return 75; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode empty = root["empty_obj"]; if(empty.HasKey("cualquiera")) { msg = "HasKey sobre objeto vacio devolvio true"; return false; } return true; } }; //+------------------------------------------------------------------+ //| 10. HasKey en objeto grande -> fuerza el path de JIT perfect-hash| //+------------------------------------------------------------------+ class CTestNode_HasKeyForcesHashing : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_HasKeyForcesHashing(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_HasKey_BigObj_ForcesHashing"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode big = root["big_obj_for_hash"]; // Accedemos varias veces para superar TSN_JSON_JIT_MIN_REF_TO_HASING // y forzar la promocion a perfect-hash; debe seguir siendo correcto. bool ok = true; for(int i = 0; i < 10 && ok; i++) ok = big.HasKey("k12"); if(!ok) { msg = "HasKey('k12') fallo tras forzar hashing"; return false; } if(big.HasKey("k99")) { msg = "HasKey('k99') (inexistente) devolvio true tras hashing"; return false; } return true; } }; //+------------------------------------------------------------------+ //| 11. operator[] en objeto grande tras hashing -> valor correcto | //+------------------------------------------------------------------+ class CTestNode_BracketAfterHashing : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_BracketAfterHashing(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_Bracket_BigObj_AfterHashing"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode big = root["big_obj_for_hash"]; long val = 0; for(int i = 0; i < 10; i++) val = big["k20"].ToInt(-1); if(val != 20) { msg = StringFormat("esperado 20 para 'k20', obtenido %d", val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 12. GetKeys sobre objeto top-level | //+------------------------------------------------------------------+ class CTestNode_GetKeysTopLevel : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_GetKeysTopLevel(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_GetKeys_TopLevel"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); string keys[]; int count = root.GetKeys(keys); bool found = false; for(int i = 0; i < count; i++) if(keys[i] == "symbol") { found = true; break; } if(!found) { msg = StringFormat("clave 'symbol' no aparece en GetKeys() (count=%d)", count); return false; } return true; } }; //+------------------------------------------------------------------+ //| 13. GetKeys sobre objeto vacio -> 0 | //+------------------------------------------------------------------+ class CTestNode_GetKeysEmptyObj : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_GetKeysEmptyObj(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_GetKeys_EmptyObj"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode empty = root["empty_obj"]; string keys[]; int count = empty.GetKeys(keys); if(count != 0) { msg = StringFormat("esperado 0 claves en objeto vacio, obtenido %d", count); return false; } return true; } }; //+------------------------------------------------------------------+ //| 14. GetKeys sobre objeto de 1 sola clave | //+------------------------------------------------------------------+ class CTestNode_GetKeysSingleKeyObj : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_GetKeysSingleKeyObj(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_GetKeys_SingleKeyObj"; } inline int Importance() const override final { return 70; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode single = root["single_key_obj"]; string keys[]; int count = single.GetKeys(keys); if(count != 1 || keys[0] != "only") { msg = StringFormat("esperado 1 clave 'only', obtenido count=%d keys[0]='%s'", count, count > 0 ? keys[0] : ""); return false; } return true; } }; //+------------------------------------------------------------------+ //| 15. KeyToPosition sobre clave existente | //+------------------------------------------------------------------+ class CTestNode_KeyToPositionValid : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_KeyToPositionValid(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_KeyToPosition_Valid"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); // "symbol" es la primera clave del fixture -> posicion 0 int pos = root.KeyToPosition("symbol"); if(pos != 0) { msg = StringFormat("esperado posicion 0 para 'symbol', obtenido %d", pos); return false; } return true; } }; //+------------------------------------------------------------------+ //| 16. KeyToPosition sobre clave inexistente -> -1 | //+------------------------------------------------------------------+ class CTestNode_KeyToPositionInvalid : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_KeyToPositionInvalid(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_KeyToPosition_Invalid"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); int pos = root.KeyToPosition("no_existe"); if(pos != -1) { msg = StringFormat("esperado -1 para clave inexistente, obtenido %d", pos); return false; } return true; } }; //+------------------------------------------------------------------+ //| 17. ToInt sobre entero negativo | //+------------------------------------------------------------------+ class CTestNode_ToIntNegative : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_ToIntNegative(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_ToInt_Negative"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); long val = root["neg_int"].ToInt(0); if(val != -42) { msg = StringFormat("esperado -42, obtenido %d", val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 18. ToIntSafe sobre string que parece entero -> conversion valida| //+------------------------------------------------------------------+ class CTestNode_ToIntSafeFromString : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_ToIntSafeFromString(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_ToIntSafe_FromString"; } inline int Importance() const override final { return 70; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); long val = 0; bool ok = root["number_as_string"].ToIntSafe(val); if(!ok || val != 123) { msg = StringFormat("esperado ok=true val=123, obtenido ok=%s val=%d", ok ? "true" : "false", val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 19. ToDouble sobre float pequeno | //+------------------------------------------------------------------+ class CTestNode_ToDoubleSmallFloat : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_ToDoubleSmallFloat(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_ToDouble_SmallFloat"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); double val = root["tiny_float"].ToDouble(-1.0); if(MathAbs(val - 0.0001) > 0.0000001) { msg = StringFormat("esperado 0.0001, obtenido %.8f", val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 20. ToDoubleSafe sobre nodo invalido -> false | //+------------------------------------------------------------------+ class CTestNode_ToDoubleSafeOnMissing : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_ToDoubleSafeOnMissing(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_ToDoubleSafe_OnMissingNode"; } inline int Importance() const override final { return 75; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); double val = 0.0; bool ok = root["no_existe"].ToDoubleSafe(val); if(ok) { msg = "ToDoubleSafe sobre nodo inexistente devolvio true"; return false; } return true; } }; //+------------------------------------------------------------------+ //| 21. ToBool sobre booleano true/false directos | //+------------------------------------------------------------------+ class CTestNode_ToBoolDirect : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_ToBoolDirect(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_ToBool_Direct"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); bool active = root["active"].ToBool(false); bool disabled = root["disabled"].ToBool(true); if(!active || disabled) { msg = StringFormat("esperado active=true disabled=false, obtenido active=%s disabled=%s", active ? "true" : "false", disabled ? "true" : "false"); return false; } return true; } }; //+------------------------------------------------------------------+ //| 22. IsNull sobre valor null explicito y sobre valor no-null | //+------------------------------------------------------------------+ class CTestNode_IsNull : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_IsNull(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_IsNull"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); if(!root["nothing"].IsNull()) { msg = "IsNull() sobre 'nothing' devolvio false, esperado true"; return false; } if(root["symbol"].IsNull()) { msg = "IsNull() sobre 'symbol' (string) devolvio true, esperado false"; return false; } return true; } }; //+------------------------------------------------------------------+ //| 23. IsValid / IsInvalid sobre nodo existente vs faltante | //+------------------------------------------------------------------+ class CTestNode_IsValidIsInvalid : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_IsValidIsInvalid(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_IsValid_IsInvalid"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); if(!root["symbol"].IsValid()) { msg = "IsValid() sobre 'symbol' devolvio false"; return false; } if(!root["no_existe"].IsInvalid()) { msg = "IsInvalid() sobre clave inexistente devolvio false"; return false; } return true; } }; //+------------------------------------------------------------------+ //| 24. ToArray sobre array homogeneo de floats | //+------------------------------------------------------------------+ class CTestNode_ToArrayFloats : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_ToArrayFloats(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_ToArray_Floats"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); double out[]; int count = root["float_arr"].ToArray(out); if(count != 3 || MathAbs(out[0] - 1.1) > 0.0001 || MathAbs(out[2] - 3.3) > 0.0001) { msg = StringFormat("esperado count=3 [1.1,..,3.3], obtenido count=%d out[0]=%.4f out[2]=%.4f", count, count > 0 ? out[0] : 0.0, count > 2 ? out[2] : 0.0); return false; } return true; } }; //+------------------------------------------------------------------+ //| 25. ToArray sobre array de bools (0/1) | //+------------------------------------------------------------------+ class CTestNode_ToArrayBools : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_ToArrayBools(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_ToArray_Bools"; } inline int Importance() const override final { return 75; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); int out[]; int count = root["bool_arr"].ToArray(out); // [true,false,true] if(count != 3 || out[0] != 1 || out[1] != 0 || out[2] != 1) { msg = StringFormat("esperado [1,0,1], obtenido count=%d", count); return false; } return true; } }; //+------------------------------------------------------------------+ //| 26. ExistInArrayStr sobre array de strings | //+------------------------------------------------------------------+ class CTestNode_ExistInArrayStr : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_ExistInArrayStr(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_ExistInArrayStr"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode tags = root["tags"]; if(!tags.ExistInArrayStr("major")) { msg = "ExistInArrayStr('major') devolvio false, esperado true"; return false; } if(tags.ExistInArrayStr("no_existe")) { msg = "ExistInArrayStr('no_existe') devolvio true, esperado false"; return false; } return true; } }; //+------------------------------------------------------------------+ //| 27. ExistInArrayHomogeneo sobre array de ints | //+------------------------------------------------------------------+ class CTestNode_ExistInArrayHomogeneoInt : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_ExistInArrayHomogeneoInt(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_ExistInArrayHomogeneo_Int"; } inline int Importance() const override final { return 75; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode arr = root["int_arr"]; // [1,2,3,4,5] if(!arr.ExistInArrayHomogeneo((long)3)) { msg = "ExistInArrayHomogeneo(3) devolvio false, esperado true"; return false; } if(arr.ExistInArrayHomogeneo((long)99)) { msg = "ExistInArrayHomogeneo(99) devolvio true, esperado false"; return false; } return true; } }; //+------------------------------------------------------------------+ //| 28. BeginArr / iterador de array recorre todos los elementos | //+------------------------------------------------------------------+ class CTestNode_IteratorArray : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_IteratorArray(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_Iterator_Array"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode arr = root["int_arr"]; // [1,2,3,4,5] TSN::CJsonIteratorArray it = arr.BeginArr(); long sum = 0; int n = 0; while(it.IsValid()) { sum += it.Val().ToInt(0); n++; it.Next(); } if(n != 5 || sum != 15) { msg = StringFormat("esperado n=5 sum=15, obtenido n=%d sum=%d", n, sum); return false; } return true; } }; //+------------------------------------------------------------------+ //| 29. BeginObj / iterador de objeto recorre todas las claves | //+------------------------------------------------------------------+ class CTestNode_IteratorObject : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_IteratorObject(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_Iterator_Object"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode meta = root["meta"]; // {id, nested} TSN::CJsonIteratorObj it = meta.BeginObj(); int n = 0; bool found_id = false; while(it.IsValid()) { if(it.Key() == "id") found_id = true; n++; it.Next(); } if(n != 2 || !found_id) { msg = StringFormat("esperado n=2 con clave 'id', obtenido n=%d found_id=%s", n, found_id ? "true" : "false"); return false; } return true; } }; //+------------------------------------------------------------------+ //| 30. Array de objetos: At(i)["campo"] combinado | //+------------------------------------------------------------------+ class CTestNode_ArrayOfObjects : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_ArrayOfObjects(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_ArrayOfObjects_AtThenBracket"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode arr = root["arr_of_objs"]; string name1 = arr.At(1)["name"].ToString("__MISSING__"); long id2 = arr.At(2)["id"].ToInt(-1); if(name1 != "b" || id2 != 3) { msg = StringFormat("esperado name1='b' id2=3, obtenido name1='%s' id2=%d", name1, id2); return false; } return true; } }; //+------------------------------------------------------------------+ //| 31. Array anidado: At(i).At(j) | //+------------------------------------------------------------------+ class CTestNode_NestedArrayAccess : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_NestedArrayAccess(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_NestedArray_AtAt"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); // nested_arr = [[1,2],[3,4],[5,6]] long val = root["nested_arr"].At(1).At(1).ToInt(-1); if(val != 4) { msg = StringFormat("esperado 4, obtenido %d", val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 32. Strings con escapes -> ToString desescapado correctamente | //+------------------------------------------------------------------+ class CTestNode_EscapedString : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_EscapedString(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_ToString_EscapedChars"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); string val = root["escaped_str"].ToString("__MISSING__"); // Debe contener saltos de linea y tab reales, no las secuencias literales if(StringFind(val, "\n") < 0 || StringFind(val, "\t") < 0) { msg = StringFormat("escape no resuelto correctamente, obtenido '%s'", val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 33. String vacio "" | //+------------------------------------------------------------------+ class CTestNode_EmptyString : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_EmptyString(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_ToString_EmptyString"; } inline int Importance() const override final { return 65; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); string val = root["empty_str"].ToString("__DEFAULT__"); if(val != "") { msg = StringFormat("esperado string vacio, obtenido '%s'", val); return false; } return true; } }; //+------------------------------------------------------------------+ //| 34. StrLenRaw sobre string conocido | //+------------------------------------------------------------------+ class CTestNode_StrLenRaw : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_StrLenRaw(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_StrLenRaw"; } inline int Importance() const override final { return 60; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); // "EURUSD" = 6 caracteres, sin escapes -> raw len == 6 int len = root["symbol"].StrLenRaw(); if(len != 6) { msg = StringFormat("esperado 6, obtenido %d", len); return false; } return true; } }; //+------------------------------------------------------------------+ //| 35. CompareStrWith sobre coincidencia y no-coincidencia | //+------------------------------------------------------------------+ class CTestNode_CompareStrWith : public ICiTest { private: TSN::CJsonParser* m_parser; public: CTestNode_CompareStrWith(TSN::CJsonParser* parser) : m_parser(parser) {} inline string Name() const override final { return "Node_CompareStrWith"; } inline int Importance() const override final { return 70; } bool Run(string &msg) override final { TSN::CJsonNode root = m_parser.GetRoot(); TSN::CJsonNode sym = root["symbol"]; if(!sym.CompareStrWith("EURUSD")) { msg = "CompareStrWith('EURUSD') devolvio false, esperado true"; return false; } if(sym.CompareStrWith("GBPUSD")) { msg = "CompareStrWith('GBPUSD') devolvio true, esperado false"; return false; } return true; } }; } //+------------------------------------------------------------------+ #endif // JSONPARSERBYLEO_WF_INC_NODETEST_MQH