JsonParserByLeo/Wf/Inc/DomTest.mqh

884 lines
29 KiB
MQL5
Raw Permalink Normal View History

2026-07-21 09:42:30 -05:00
//+------------------------------------------------------------------+
//| 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",
2026-07-21 10:38:19 -05:00
symbol, bid, active ? "true" : "false", neg);
2026-07-21 09:42:30 -05:00
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();
2026-07-21 10:38:19 -05:00
//parser.PrintCintaTypes(0,WHOLE_ARRAY);
2026-07-21 09:42:30 -05:00
TSN::CDomNodeBase* root;
TSN::CDomNodeManager manager;
2026-07-21 10:38:19 -05:00
//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));
2026-07-21 09:42:30 -05:00
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)";
2026-07-21 10:38:19 -05:00
if(leaf != NULL)
delete leaf;
2026-07-21 09:42:30 -05:00
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&))
2026-07-21 10:38:19 -05:00
2026-07-21 09:42:30 -05:00
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",
2026-07-21 10:38:19 -05:00
removed ? "true" : "false", before, after, still_has ? "true" : "false");
2026-07-21 09:42:30 -05:00
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<long> 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