406 lines
11 KiB
MQL5
406 lines
11 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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
|