//+------------------------------------------------------------------+ //| JsonBuilder.mqh | //| Copyright 2026, Niquel Mendoza. | //| https://www.mql5.com/ | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Niquel Mendoza." #property link "https://www.mql5.com/" #property strict #ifndef JSONPARSERBYLEO_SRC_JSONBUILDER_MQH #define JSONPARSERBYLEO_SRC_JSONBUILDER_MQH namespace TSN { //+------------------------------------------------------------------+ //| Escape lookup tables (index = char code 0..127) | //| g_arr_jsonb_type_c: 0 = safe (copy as-is) | //| 1 = two-char escape: backslash + g_arr_jsonb_next_c[c] | //| 2 = \u00XX hex escape | //+------------------------------------------------------------------+ const uchar g_arr_jsonb_type_c[128] = { 2, 2, 2, 2, 2, 2, 2, 2, // 0x00-0x07 1, 1, 1, 2, 1, 1, 2, 2, // 0x08-0x0F (\b=8,\t=9,\n=10, \f=12,\r=13) 2, 2, 2, 2, 2, 2, 2, 2, // 0x10-0x17 2, 2, 2, 2, 2, 2, 2, 2, // 0x18-0x1F 0, 0, 1, 0, 0, 0, 0, 0, // 0x20-0x27 ('"'=34) 0, 0, 0, 0, 0, 0, 0, 0, // 0x28-0x2F 0, 0, 0, 0, 0, 0, 0, 0, // 0x30-0x37 0, 0, 0, 0, 0, 0, 0, 0, // 0x38-0x3F 0, 0, 0, 0, 0, 0, 0, 0, // 0x40-0x47 0, 0, 0, 0, 0, 0, 0, 0, // 0x48-0x4F 0, 0, 0, 0, 0, 0, 0, 0, // 0x50-0x57 0, 0, 0, 0, 1, 0, 0, 0, // 0x58-0x5F ('\\'=92) 0, 0, 0, 0, 0, 0, 0, 0, // 0x60-0x67 0, 0, 0, 0, 0, 0, 0, 0, // 0x68-0x6F 0, 0, 0, 0, 0, 0, 0, 0, // 0x70-0x77 0, 0, 0, 0, 0, 0, 0, 0 // 0x78-0x7F }; //--- Second char of two-char escape (only valid where g_arr_jsonb_type_c==1) const uchar g_arr_jsonb_next_c[128] = { 0, 0, 0, 0, 0, 0, 0, 0, // 0x00-0x07 'b', 't', 'n', 0, 'f', 'r', 0, 0, // 0x08-0x0F 0, 0, 0, 0, 0, 0, 0, 0, // 0x10-0x17 0, 0, 0, 0, 0, 0, 0, 0, // 0x18-0x1F 0, 0, '"', 0, 0, 0, 0, 0, // 0x20-0x27 0, 0, 0, 0, 0, 0, 0, 0, // 0x28-0x2F 0, 0, 0, 0, 0, 0, 0, 0, // 0x30-0x37 0, 0, 0, 0, 0, 0, 0, 0, // 0x38-0x3F 0, 0, 0, 0, 0, 0, 0, 0, // 0x40-0x47 0, 0, 0, 0, 0, 0, 0, 0, // 0x48-0x4F 0, 0, 0, 0, 0, 0, 0, 0, // 0x50-0x57 0, 0, 0, 0, '\\', 0, 0, 0, // 0x58-0x5F 0, 0, 0, 0, 0, 0, 0, 0, // 0x60-0x67 0, 0, 0, 0, 0, 0, 0, 0, // 0x68-0x6F 0, 0, 0, 0, 0, 0, 0, 0, // 0x70-0x77 0, 0, 0, 0, 0, 0, 0, 0 // 0x78-0x7F }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #define JSONBUILDER_EXPAND(need) if(m_pos + (need) >= m_cap) { m_cap += (need) + 4096; ArrayResize(m_buf, m_cap, m_cap); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class CJsonBuilder { public: uchar m_buf[]; int m_pos; private: int m_cap; bool m_stack_first[64]; int m_sp; //--- void PutRaw(const string& s); void PutRaw(const uchar& s[]); void PutRawNoRef(const string s); void PutEncodedStr(const string& s); public: CJsonBuilder(int initial_cap = 1024); ~CJsonBuilder() {} //--- void Clear(); //--- __forceinline string Build() const { return CharArrayToString(m_buf, 0, m_pos, CP_UTF8); } //--- Structure CJsonBuilder* Obj(); CJsonBuilder* EndObj(); CJsonBuilder* Arr(); CJsonBuilder* EndArr(); //--- Key / Values CJsonBuilder* Key(const string& k); CJsonBuilder* ValS(const string& v); CJsonBuilder* ValSNoRef(const string v); CJsonBuilder* Val(int v); CJsonBuilder* Val(long v); CJsonBuilder* Val(double v); CJsonBuilder* Val(bool v); CJsonBuilder* ValidJson(const uchar& v[]); CJsonBuilder* ValidJson(const string& fragment); CJsonBuilder* ValidJsonNoRef(const string fragment); CJsonBuilder* Null(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CJsonBuilder::CJsonBuilder(int initial_cap) { m_cap = initial_cap; ArrayResize(m_buf, m_cap); m_pos = 0; m_sp = 0; m_stack_first[0] = true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #define JSON_BUILDER_PUT(c) \ JSONBUILDER_EXPAND(1) \ m_buf[m_pos++] = c; \ //--- #define JSON_BUILDER_COMMA \ if(!m_stack_first[m_sp]) \ { \ JSON_BUILDER_PUT(',') \ } \ m_stack_first[m_sp] = false; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CJsonBuilder::Clear() { m_pos = 0; m_sp = 0; m_stack_first[0] = true; } //+------------------------------------------------------------------+ void CJsonBuilder::PutRaw(const uchar& s[]) { //--- const int l = ArraySize(s); const int max_bytes = l * 4 + 1; JSONBUILDER_EXPAND(max_bytes) //--- m_pos += ArrayCopy(m_buf, s, 0, m_pos, WHOLE_ARRAY); } //+------------------------------------------------------------------+ void CJsonBuilder::PutRaw(const string& s) { //--- const int l = StringLen(s); const int max_bytes = l * 4 + 1; JSONBUILDER_EXPAND(max_bytes) //--- int written = StringToCharArray(s, m_buf, m_pos, WHOLE_ARRAY, CP_UTF8); if(written > 0 && m_buf[m_pos + written - 1] == 0) written--; m_pos += written; } //+------------------------------------------------------------------+ void CJsonBuilder::PutRawNoRef(const string s) { //--- const int l = StringLen(s); const int max_bytes = l * 4 + 1; JSONBUILDER_EXPAND(max_bytes) //--- int written = StringToCharArray(s, m_buf, m_pos, WHOLE_ARRAY, CP_UTF8); if(written > 0 && m_buf[m_pos + written - 1] == 0) written--; m_pos += written; } //+------------------------------------------------------------------+ void CJsonBuilder::PutEncodedStr(const string& s) { //--- JSON_BUILDER_PUT('"') //--- const int l = StringLen(s); //--- for(int i = 0; i < l; i++) { //--- const ushort c = s[i]; //--- if(c >= 128) { //--- UTF-8 multibyte uchar temp[]; int tlen = StringToCharArray(ShortToString(c), temp, 0, WHOLE_ARRAY, CP_UTF8); if(tlen > 0 && temp[tlen - 1] == 0) tlen--; JSONBUILDER_EXPAND(tlen) ArrayCopy(m_buf, temp, m_pos, 0, tlen); m_pos += tlen; continue; } //--- ASCII lookup const uchar esc = g_arr_jsonb_type_c[c]; if(esc == 0) { JSONBUILDER_EXPAND(1) m_buf[m_pos++] = (uchar)c; } else if(esc == 1) { JSONBUILDER_EXPAND(2) m_buf[m_pos++] = '\\'; m_buf[m_pos++] = g_arr_jsonb_next_c[c]; } else // esc == 2: \u00XX { JSONBUILDER_EXPAND(6) m_buf[m_pos++] = '\\'; m_buf[m_pos++] = 'u'; m_buf[m_pos++] = '0'; m_buf[m_pos++] = '0'; //--- uchar t = uchar((c >> 4) & 0xF); m_buf[m_pos++] = (uchar)(t < 10 ? '0' + t : 'a' + t - 10); t = uchar(c & 0xF); m_buf[m_pos++] = (uchar)(t < 10 ? '0' + t : 'a' + t - 10); } } //--- JSON_BUILDER_PUT('"') } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::Obj() { JSON_BUILDER_COMMA JSON_BUILDER_PUT('{') if(m_sp < 63) { m_sp++; m_stack_first[m_sp] = true; } return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::EndObj() { if(m_sp > 0) m_sp--; JSON_BUILDER_PUT('}') m_stack_first[m_sp] = false; return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::Arr() { JSON_BUILDER_COMMA JSON_BUILDER_PUT('[') if(m_sp < 63) { m_sp++; m_stack_first[m_sp] = true; } return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::EndArr() { if(m_sp > 0) m_sp--; JSON_BUILDER_PUT(']') m_stack_first[m_sp] = false; return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::Key(const string& k) { JSON_BUILDER_COMMA PutEncodedStr(k); JSON_BUILDER_PUT(':') m_stack_first[m_sp] = true; return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::ValS(const string& v) { JSON_BUILDER_COMMA PutEncodedStr(v); return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::ValSNoRef(const string v) { JSON_BUILDER_COMMA PutEncodedStr(v); return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::Val(int v) { JSON_BUILDER_COMMA PutRawNoRef((string)v); return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::Val(long v) { JSON_BUILDER_COMMA PutRawNoRef((string)v); return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::Val(double v) { JSON_BUILDER_COMMA PutRawNoRef((string)v); return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::Val(bool v) { JSON_BUILDER_COMMA PutRawNoRef(v ? "true" : "false"); return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::ValidJson(const uchar& v[]) { JSON_BUILDER_COMMA PutRaw(v); return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::ValidJson(const string& fragment) { JSON_BUILDER_COMMA PutRaw(fragment); return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::ValidJsonNoRef(const string fragment) { JSON_BUILDER_COMMA PutRaw(fragment); return &this; } //+------------------------------------------------------------------+ CJsonBuilder* CJsonBuilder::Null() { JSON_BUILDER_COMMA PutRaw("null"); return &this; } //--- CJsonBuilder g_json_builder; } // namespace TSN //+------------------------------------------------------------------+ #endif // JSONPARSERBYLEO_SRC_JSONBUILDER_MQH