Article-22495-Dispatch-Driv.../AI JSON FILE.mqh

1033 lines
43 KiB
MQL5
Raw Permalink Normal View History

//+------------------------------------------------------------------+
//| AI JSON FILE.mqh |
//| Copyright 2026, Allan Munene Mutiiria. |
//| https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
//--- Guard against multiple inclusion of this header
#ifndef AI_JSON_FILE_MQH
#define AI_JSON_FILE_MQH
//--- Toggle verbose debug output during JSON parsing
#define DEBUG_PRINT false
//+------------------------------------------------------------------+
//| JSON value type enumeration |
//+------------------------------------------------------------------+
enum JsonValueType
{
JsonUndefined, // Unset or unknown value
JsonNull, // Explicit null literal
JsonBoolean, // Boolean literal (true or false)
JsonInteger, // Integer numeric literal
JsonDouble, // Floating-point numeric literal
JsonString, // String literal in quotes
JsonArray, // Array of values
JsonObject // Object of key-value pairs
};
//+------------------------------------------------------------------+
//| JSON value class |
//+------------------------------------------------------------------+
class JsonValue
{
public:
//--- Hold child elements for arrays and objects
JsonValue m_children[];
//--- Hold the key name when this value is inside an object
string m_key;
//--- Hold a temporary key while parsing object key-value pairs
string m_temporaryKey;
//--- Point to the parent value for hierarchy traversal
JsonValue *m_parent;
//--- Store the value type for this node
JsonValueType m_type;
//--- Store the boolean representation of the value
bool m_booleanValue;
//--- Store the integer representation of the value
long m_integerValue;
//--- Store the floating-point representation of the value
double m_doubleValue;
//--- Store the string representation of the value
string m_stringValue;
//--- Define the encoding code page used for character conversion
static int encodingCodePage;
//+---------------------------------------------------------------+
//| Default constructor |
//+---------------------------------------------------------------+
JsonValue()
{
//--- Reset all members to defaults
Reset();
}
//+---------------------------------------------------------------+
//| Construct with parent and type |
//+---------------------------------------------------------------+
JsonValue(JsonValue *parent, JsonValueType type)
{
//--- Reset all members to defaults
Reset();
//--- Apply the requested type
m_type = type;
//--- Link to the supplied parent
m_parent = parent;
}
//+---------------------------------------------------------------+
//| Construct from type and string value |
//+---------------------------------------------------------------+
JsonValue(JsonValueType type, string value)
{
//--- Reset all members to defaults
Reset();
//--- Populate the value from the string representation
SetFromString(type, value);
}
//+---------------------------------------------------------------+
//| Construct from integer literal |
//+---------------------------------------------------------------+
JsonValue(const int integerValue)
{
//--- Reset all members to defaults
Reset();
//--- Mark the value as an integer
m_type = JsonInteger;
//--- Store the integer value
m_integerValue = integerValue;
//--- Mirror the value as a double
m_doubleValue = (double)m_integerValue;
//--- Mirror the value as a string
m_stringValue = IntegerToString(m_integerValue);
//--- Derive boolean from non-zero check
m_booleanValue = m_integerValue != 0;
}
//+---------------------------------------------------------------+
//| Copy constructor |
//+---------------------------------------------------------------+
JsonValue(const JsonValue &other)
{
//--- Reset all members to defaults
Reset();
//--- Copy all data from the source value
CopyFrom(other);
}
//+---------------------------------------------------------------+
//| Destructor |
//+---------------------------------------------------------------+
~JsonValue()
{
//--- Release members to defaults
Reset();
}
//+---------------------------------------------------------------+
//| Reset all members to default state |
//+---------------------------------------------------------------+
void Reset()
{
//--- Clear parent link
m_parent = NULL;
//--- Clear stored key
m_key = "";
//--- Clear temporary parsing key
m_temporaryKey = "";
//--- Reset type to undefined
m_type = JsonUndefined;
//--- Reset boolean storage
m_booleanValue = false;
//--- Reset integer storage
m_integerValue = 0;
//--- Reset double storage
m_doubleValue = 0;
//--- Reset string storage
m_stringValue = "";
//--- Empty the children array
ArrayResize(m_children, 0);
}
//+---------------------------------------------------------------+
//| Copy key and data from source value |
//+---------------------------------------------------------------+
bool CopyFrom(const JsonValue &source)
{
//--- Copy the key name
m_key = source.m_key;
//--- Copy the actual value data and children
CopyDataFrom(source);
//--- Indicate success
return true;
}
//+---------------------------------------------------------------+
//| Copy value data and children from source |
//+---------------------------------------------------------------+
void CopyDataFrom(const JsonValue &source)
{
//--- Copy the value type
m_type = source.m_type;
//--- Copy the boolean representation
m_booleanValue = source.m_booleanValue;
//--- Copy the integer representation
m_integerValue = source.m_integerValue;
//--- Copy the double representation
m_doubleValue = source.m_doubleValue;
//--- Copy the string representation
m_stringValue = source.m_stringValue;
//--- Copy nested children recursively
CopyChildrenFrom(source);
}
//+---------------------------------------------------------------+
//| Copy children array and re-link parent pointers |
//+---------------------------------------------------------------+
void CopyChildrenFrom(const JsonValue &source)
{
//--- Resize the children array to match the source
int numChildren = ArrayResize(m_children, ArraySize(source.m_children));
//--- Iterate through each child slot
for(int index = 0; index < numChildren; index++)
{
//--- Copy the child value
m_children[index] = source.m_children[index];
//--- Re-link the child to point to this object as parent
m_children[index].m_parent = GetPointer(this);
}
}
//+---------------------------------------------------------------+
//| Find a child by its key name |
//+---------------------------------------------------------------+
JsonValue *FindChildByKey(string key)
{
//--- Walk children in reverse to find the matching key
for(int index = ArraySize(m_children) - 1; index >= 0; --index)
{
//--- Return pointer when key matches
if(m_children[index].m_key == key)
return GetPointer(m_children[index]);
}
//--- Indicate not found
return NULL;
}
//+---------------------------------------------------------------+
//| Object index operator (string key) |
//+---------------------------------------------------------------+
JsonValue *operator[](string key)
{
//--- Promote undefined node to object on first access
if(m_type == JsonUndefined)
m_type = JsonObject;
//--- Try to find an existing child with the requested key
JsonValue *value = FindChildByKey(key);
//--- Return the existing child if found
if(value)
return value;
//--- Build a new child placeholder bound to this parent
JsonValue newValue(GetPointer(this), JsonUndefined);
//--- Assign the requested key to the new child
newValue.m_key = key;
//--- Append the child internally and capture its pointer
value = AddChildInternal(newValue);
//--- Return the newly added child
return value;
}
//+---------------------------------------------------------------+
//| Array index operator (integer index) |
//+---------------------------------------------------------------+
JsonValue *operator[](int index)
{
//--- Promote undefined node to array on first access
if(m_type == JsonUndefined)
m_type = JsonArray;
//--- Grow the array until the requested index exists
while(index >= ArraySize(m_children))
{
//--- Build a new placeholder child bound to this parent
JsonValue newValue(GetPointer(this), JsonUndefined);
//--- Append the child and bail out if pointer is invalid
if(CheckPointer(AddChildInternal(newValue)) == POINTER_INVALID)
return NULL;
}
//--- Return pointer to the requested child slot
return GetPointer(m_children[index]);
}
//+---------------------------------------------------------------+
//| Assignment from another JsonValue |
//+---------------------------------------------------------------+
void operator=(const JsonValue &value)
{
//--- Delegate to copy routine
CopyFrom(value);
}
//+---------------------------------------------------------------+
//| Assignment from string literal |
//+---------------------------------------------------------------+
void operator=(string stringValue)
{
//--- Choose string type when value is non-null, else null type
m_type = (stringValue != NULL) ? JsonString : JsonNull;
//--- Store the string representation
m_stringValue = stringValue;
//--- Derive an integer view of the string
m_integerValue = StringToInteger(m_stringValue);
//--- Derive a double view of the string
m_doubleValue = StringToDouble(m_stringValue);
//--- Derive boolean from non-null check
m_booleanValue = stringValue != NULL;
}
//+---------------------------------------------------------------+
//| Assignment from integer literal |
//+---------------------------------------------------------------+
void operator=(const int integerValue)
{
//--- Mark the type as integer
m_type = JsonInteger;
//--- Store the integer value
m_integerValue = integerValue;
//--- Mirror as double
m_doubleValue = (double)m_integerValue;
//--- Mirror as string
m_stringValue = IntegerToString(m_integerValue);
//--- Derive boolean from non-zero check
m_booleanValue = integerValue != 0;
}
//+---------------------------------------------------------------+
//| Return the boolean representation |
//+---------------------------------------------------------------+
bool ToBoolean() const
{
//--- Return cached boolean value
return m_booleanValue;
}
//+---------------------------------------------------------------+
//| Return the string representation |
//+---------------------------------------------------------------+
string ToString()
{
//--- Return cached string value
return m_stringValue;
}
//+---------------------------------------------------------------+
//| Return the integer representation |
//+---------------------------------------------------------------+
long ToInteger() const
{
//--- Return cached integer value
return m_integerValue;
}
//+---------------------------------------------------------------+
//| Return the double representation |
//+---------------------------------------------------------------+
double ToDouble() const
{
//--- Return cached double value
return m_doubleValue;
}
//+---------------------------------------------------------------+
//| Populate value from a typed string source |
//+---------------------------------------------------------------+
void SetFromString(JsonValueType type, string stringValue)
{
//--- Apply the requested type
m_type = type;
//--- Branch on the requested type
switch(m_type)
{
case JsonBoolean:
//--- Convert string to boolean via integer
m_booleanValue = (StringToInteger(stringValue) != 0);
//--- Mirror boolean as integer
m_integerValue = (long)m_booleanValue;
//--- Mirror boolean as double
m_doubleValue = (double)m_booleanValue;
//--- Cache the original string
m_stringValue = stringValue;
break;
case JsonString:
//--- Unescape any JSON escape sequences in the string
m_stringValue = UnescapeString(stringValue);
//--- Promote to null type when the string is null
m_type = (m_stringValue != NULL) ? JsonString : JsonNull;
//--- Derive integer view from the string
m_integerValue = StringToInteger(m_stringValue);
//--- Derive double view from the string
m_doubleValue = StringToDouble(m_stringValue);
//--- Derive boolean from non-null check
m_booleanValue = m_stringValue != NULL;
break;
}
}
//+---------------------------------------------------------------+
//| Extract a substring from a character array |
//+---------------------------------------------------------------+
string GetSubstringFromArray(char &jsonCharacterArray[], int startPosition, int substringLength)
{
//--- Return empty string when the requested length is invalid
if(substringLength <= 0)
return "";
//--- Build a temporary character buffer
char temporaryArray[];
//--- Copy the requested range from the source array
ArrayCopy(temporaryArray, jsonCharacterArray, 0, startPosition, substringLength);
//--- Convert the buffer to a string using the active code page
return CharArrayToString(temporaryArray, 0, WHOLE_ARRAY, encodingCodePage);
}
//+---------------------------------------------------------------+
//| Add a child JsonValue |
//+---------------------------------------------------------------+
JsonValue *AddChild(const JsonValue &item)
{
//--- Promote undefined node to array on first child added
if(m_type == JsonUndefined)
m_type = JsonArray;
//--- Delegate to internal append routine
return AddChildInternal(item);
}
//+---------------------------------------------------------------+
//| Add a child from a string value |
//+---------------------------------------------------------------+
JsonValue *AddChild(string stringValue)
{
//--- Build a string-typed JsonValue from the input
JsonValue item(JsonString, stringValue);
//--- Append it as a child
return AddChild(item);
}
//+---------------------------------------------------------------+
//| Add a child from an integer value |
//+---------------------------------------------------------------+
JsonValue *AddChild(const int integerValue)
{
//--- Build an integer-typed JsonValue from the input
JsonValue item(integerValue);
//--- Append it as a child
return AddChild(item);
}
//+---------------------------------------------------------------+
//| Internal append helper that grows the children array |
//+---------------------------------------------------------------+
JsonValue *AddChildInternal(const JsonValue &item)
{
//--- Capture the current size as the insertion index
int currentSize = ArraySize(m_children);
//--- Grow the children array by one slot
ArrayResize(m_children, currentSize + 1);
//--- Copy the new child into the new slot
m_children[currentSize] = item;
//--- Set the parent pointer on the new child
m_children[currentSize].m_parent = GetPointer(this);
//--- Return pointer to the new child slot
return GetPointer(m_children[currentSize]);
}
//+---------------------------------------------------------------+
//| Serialize this value into a JSON string |
//+---------------------------------------------------------------+
string SerializeToString()
{
//--- Build the output buffer
string jsonString;
//--- Run the recursive serializer
SerializeToString(jsonString);
//--- Return the assembled JSON string
return jsonString;
}
//+---------------------------------------------------------------+
//| Serialize this value into an existing buffer |
//+---------------------------------------------------------------+
void SerializeToString(string &jsonString, bool includeKey = false, bool includeComma = false)
{
//--- Skip undefined nodes entirely
if(m_type == JsonUndefined)
return;
//--- Emit a separator comma when requested
if(includeComma)
jsonString += ",";
//--- Emit the key prefix when this node lives in an object
if(includeKey)
jsonString += StringFormat("\"%s\":", m_key);
//--- Cache child count for the array and object branches
int numChildren = ArraySize(m_children);
//--- Branch on this node's type
switch(m_type)
{
case JsonNull:
//--- Emit JSON null literal
jsonString += "null";
break;
case JsonBoolean:
//--- Emit JSON true or false literal
jsonString += (m_booleanValue ? "true" : "false");
break;
case JsonInteger:
//--- Emit integer literal
jsonString += IntegerToString(m_integerValue);
break;
case JsonDouble:
//--- Emit double literal
jsonString += DoubleToString(m_doubleValue);
break;
case JsonString:
{
//--- Escape special characters before emitting
string escaped = EscapeString(m_stringValue);
//--- Emit quoted string when non-empty, else null
jsonString += (StringLen(escaped) > 0) ? StringFormat("\"%s\"", escaped) : "null";
}
break;
case JsonArray:
{
//--- Emit array opening bracket
jsonString += "[";
//--- Recurse into each child without keys
for(int index = 0; index < numChildren; index++)
m_children[index].SerializeToString(jsonString, false, index > 0);
//--- Emit array closing bracket
jsonString += "]";
}
break;
case JsonObject:
{
//--- Emit object opening brace
jsonString += "{";
//--- Recurse into each child including keys
for(int index = 0; index < numChildren; index++)
m_children[index].SerializeToString(jsonString, true, index > 0);
//--- Emit object closing brace
jsonString += "}";
}
break;
}
}
//+---------------------------------------------------------------+
//| Deserialize JSON content from a character array |
//+---------------------------------------------------------------+
bool DeserializeFromArray(char &jsonCharacterArray[], int arrayLength, int &currentIndex)
{
//--- Define the set of valid numeric characters
string validNumericCharacters = "0123456789+-.eE";
//--- Track the start position of the current token
int startPosition = currentIndex;
//--- Walk through each character of the input
for(; currentIndex < arrayLength; currentIndex++)
{
//--- Read the current character
char currentCharacter = jsonCharacterArray[currentIndex];
//--- Stop when reaching a null terminator
if(currentCharacter == 0)
break;
//--- Branch on the current character
switch(currentCharacter)
{
case '\t':
case '\r':
case '\n':
case ' ':
//--- Skip whitespace and advance the token start
startPosition = currentIndex + 1;
break;
case '[':
{
//--- Mark token start past the bracket
startPosition = currentIndex + 1;
//--- Reject if the type is already set
if(m_type != JsonUndefined)
return false;
//--- Mark this node as an array
m_type = JsonArray;
//--- Advance past the opening bracket
currentIndex++;
//--- Build a child slot for parsing array entries
JsonValue childValue(GetPointer(this), JsonUndefined);
//--- Parse each child element until end of array
while(childValue.DeserializeFromArray(jsonCharacterArray, arrayLength, currentIndex))
{
//--- Append the parsed child when valid
if(childValue.m_type != JsonUndefined)
AddChildInternal(childValue);
//--- Advance past numeric or array tokens
if(childValue.m_type == JsonInteger || childValue.m_type == JsonDouble || childValue.m_type == JsonArray)
currentIndex++;
//--- Reset the child slot for the next iteration
childValue.Reset();
//--- Re-link the child to this parent
childValue.m_parent = GetPointer(this);
//--- Stop on closing bracket
if(jsonCharacterArray[currentIndex] == ']')
break;
//--- Advance past the comma separator
currentIndex++;
//--- Bail out if we ran past the end of input
if(currentIndex >= arrayLength)
return false;
}
//--- Verify the array closed properly
return (jsonCharacterArray[currentIndex] == ']' || jsonCharacterArray[currentIndex] == 0);
}
case ']':
//--- Validate that this closes a parent array
return (m_parent && m_parent.m_type == JsonArray);
case ':':
{
//--- Reject when no temporary key is staged
if(m_temporaryKey == "")
return false;
//--- Build a placeholder child for the value
JsonValue childValue(GetPointer(this), JsonUndefined);
//--- Append it and capture the pointer
JsonValue *addedChild = AddChildInternal(childValue);
//--- Promote the temporary key onto the new child
addedChild.m_key = m_temporaryKey;
//--- Clear the staged key
m_temporaryKey = "";
//--- Advance past the colon
currentIndex++;
//--- Recurse to parse the value portion
if(!addedChild.DeserializeFromArray(jsonCharacterArray, arrayLength, currentIndex))
return false;
}
break;
case ',':
{
//--- Mark token start past the comma
startPosition = currentIndex + 1;
//--- Reject when there is neither parent nor object context
if(!m_parent && m_type != JsonObject)
return false;
//--- Reject when parent is not an array or object
if(m_parent && m_parent.m_type != JsonArray && m_parent.m_type != JsonObject)
return false;
//--- Return up the stack when an array element completes
if(m_parent && m_parent.m_type == JsonArray && m_type == JsonUndefined)
return true;
}
break;
case '{':
{
//--- Mark token start past the brace
startPosition = currentIndex + 1;
//--- Reject if the type is already set
if(m_type != JsonUndefined)
return false;
//--- Mark this node as an object
m_type = JsonObject;
//--- Advance past the opening brace
currentIndex++;
//--- Recurse to parse object body
if(!DeserializeFromArray(jsonCharacterArray, arrayLength, currentIndex))
return false;
//--- Verify the object closed properly
return (jsonCharacterArray[currentIndex] == '}' || jsonCharacterArray[currentIndex] == 0);
}
break;
case '}':
//--- Validate that this closes the current object
return (m_type == JsonObject);
case 't':
case 'T':
case 'f':
case 'F':
{
//--- Reject if the type is already set
if(m_type != JsonUndefined)
return false;
//--- Mark this node as boolean
m_type = JsonBoolean;
//--- Detect the literal 'true'
if(currentIndex + 3 < arrayLength && StringCompare(GetSubstringFromArray(jsonCharacterArray, currentIndex, 4), "true", false) == 0)
{
//--- Store boolean as true
m_booleanValue = true;
//--- Advance past the literal
currentIndex += 3;
return true;
}
//--- Detect the literal 'false'
if(currentIndex + 4 < arrayLength && StringCompare(GetSubstringFromArray(jsonCharacterArray, currentIndex, 5), "false", false) == 0)
{
//--- Store boolean as false
m_booleanValue = false;
//--- Advance past the literal
currentIndex += 4;
return true;
}
//--- Reject malformed boolean literal
return false;
}
break;
case 'n':
case 'N':
{
//--- Reject if the type is already set
if(m_type != JsonUndefined)
return false;
//--- Mark this node as null
m_type = JsonNull;
//--- Detect the literal 'null'
if(currentIndex + 3 < arrayLength && StringCompare(GetSubstringFromArray(jsonCharacterArray, currentIndex, 4), "null", false) == 0)
{
//--- Advance past the literal
currentIndex += 3;
return true;
}
//--- Reject malformed null literal
return false;
}
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
case '+':
case '.':
{
//--- Reject if the type is already set
if(m_type != JsonUndefined)
return false;
//--- Track whether the number contains a decimal or exponent
bool isDouble = false;
//--- Capture the start of the numeric token
int startOfNumber = currentIndex;
//--- Scan forward through valid numeric characters
while(jsonCharacterArray[currentIndex] != 0 && currentIndex < arrayLength)
{
//--- Advance to the next character
currentIndex++;
//--- Stop scanning when an invalid numeric character appears
if(StringFind(validNumericCharacters, GetSubstringFromArray(jsonCharacterArray, currentIndex, 1)) < 0)
break;
//--- Detect a decimal point or exponent marker
if(!isDouble)
isDouble = (jsonCharacterArray[currentIndex] == '.' || jsonCharacterArray[currentIndex] == 'e' || jsonCharacterArray[currentIndex] == 'E');
}
//--- Capture the numeric substring
m_stringValue = GetSubstringFromArray(jsonCharacterArray, startOfNumber, currentIndex - startOfNumber);
//--- Branch based on integer vs double
if(isDouble)
{
//--- Mark the value as double
m_type = JsonDouble;
//--- Convert to double
m_doubleValue = StringToDouble(m_stringValue);
//--- Mirror as integer
m_integerValue = (long)m_doubleValue;
//--- Derive boolean from non-zero check
m_booleanValue = m_integerValue != 0;
}
else
{
//--- Mark the value as integer
m_type = JsonInteger;
//--- Convert to integer
m_integerValue = StringToInteger(m_stringValue);
//--- Mirror as double
m_doubleValue = (double)m_integerValue;
//--- Derive boolean from non-zero check
m_booleanValue = m_integerValue != 0;
}
//--- Step back so the outer loop sees the next token
currentIndex--;
return true;
}
break;
case '\"':
{
//--- Branch when this string is a key inside an object
if(m_type == JsonObject)
{
//--- Advance past the opening quote
currentIndex++;
//--- Capture the start of the key
int startOfString = currentIndex;
//--- Walk through the quoted key
if(!ExtractStringFromArray(jsonCharacterArray, arrayLength, currentIndex))
return false;
//--- Stage the parsed key for the next colon to consume
m_temporaryKey = GetSubstringFromArray(jsonCharacterArray, startOfString, currentIndex - startOfString);
}
else
{
//--- Reject if the type is already set
if(m_type != JsonUndefined)
return false;
//--- Mark this node as a string
m_type = JsonString;
//--- Advance past the opening quote
currentIndex++;
//--- Capture the start of the string content
int startOfString = currentIndex;
//--- Walk through the quoted string
if(!ExtractStringFromArray(jsonCharacterArray, arrayLength, currentIndex))
return false;
//--- Populate this value from the captured string
SetFromString(JsonString, GetSubstringFromArray(jsonCharacterArray, startOfString, currentIndex - startOfString));
return true;
}
}
break;
}
}
//--- Return success when end of input is reached cleanly
return true;
}
//+---------------------------------------------------------------+
//| Walk a quoted string and honor escape sequences |
//+---------------------------------------------------------------+
bool ExtractStringFromArray(char &jsonCharacterArray[], int arrayLength, int &currentIndex)
{
//--- Walk forward through the array until the closing quote
for(; jsonCharacterArray[currentIndex] != 0 && currentIndex < arrayLength; currentIndex++)
{
//--- Read the current character
char currentCharacter = jsonCharacterArray[currentIndex];
//--- Stop on an unescaped closing quote
if(currentCharacter == '\"')
break;
//--- Handle escape sequences when a backslash appears
if(currentCharacter == '\\' && currentIndex + 1 < arrayLength)
{
//--- Advance past the backslash
currentIndex++;
//--- Read the escape selector character
currentCharacter = jsonCharacterArray[currentIndex];
//--- Branch on the escape selector
switch(currentCharacter)
{
case '/':
case '\\':
case '\"':
case 'b':
case 'f':
case 'r':
case 'n':
case 't':
//--- Accept simple single-character escapes
break;
case 'u':
{
//--- Advance past the 'u' marker
currentIndex++;
//--- Walk through the four required hex digits
for(int hexIndex = 0; hexIndex < 4 && currentIndex < arrayLength && jsonCharacterArray[currentIndex] != 0; hexIndex++, currentIndex++)
{
//--- Reject when a non-hex digit appears
if(!((jsonCharacterArray[currentIndex] >= '0' && jsonCharacterArray[currentIndex] <= '9') || (jsonCharacterArray[currentIndex] >= 'A' && jsonCharacterArray[currentIndex] <= 'F') || (jsonCharacterArray[currentIndex] >= 'a' && jsonCharacterArray[currentIndex] <= 'f')))
return false;
}
//--- Step back so the outer loop advances correctly
currentIndex--;
break;
}
default:
//--- Pass through unknown escapes silently
break;
}
}
}
//--- Indicate successful traversal
return true;
}
//+---------------------------------------------------------------+
//| Escape a string for JSON output |
//+---------------------------------------------------------------+
string EscapeString(string value)
{
//--- Build input and output character buffers
ushort inputCharacters[], escapedCharacters[];
//--- Convert the input string to a character array
int inputLength = StringToShortArray(value, inputCharacters);
//--- Reserve worst-case space (each character may double)
if(ArrayResize(escapedCharacters, 2 * inputLength) != 2 * inputLength)
return NULL;
//--- Track the write position in the output buffer
int escapedIndex = 0;
//--- Walk through each input character
for(int inputIndex = 0; inputIndex < inputLength; inputIndex++)
{
//--- Branch on the current character
switch(inputCharacters[inputIndex])
{
case '\\':
//--- Emit escaped backslash
escapedCharacters[escapedIndex] = '\\';
escapedIndex++;
escapedCharacters[escapedIndex] = '\\';
escapedIndex++;
break;
case '"':
//--- Emit escaped double quote
escapedCharacters[escapedIndex] = '\\';
escapedIndex++;
escapedCharacters[escapedIndex] = '"';
escapedIndex++;
break;
case '/':
//--- Emit escaped forward slash
escapedCharacters[escapedIndex] = '\\';
escapedIndex++;
escapedCharacters[escapedIndex] = '/';
escapedIndex++;
break;
case 8:
//--- Emit escaped backspace
escapedCharacters[escapedIndex] = '\\';
escapedIndex++;
escapedCharacters[escapedIndex] = 'b';
escapedIndex++;
break;
case 12:
//--- Emit escaped form feed
escapedCharacters[escapedIndex] = '\\';
escapedIndex++;
escapedCharacters[escapedIndex] = 'f';
escapedIndex++;
break;
case '\n':
//--- Emit escaped line feed
escapedCharacters[escapedIndex] = '\\';
escapedIndex++;
escapedCharacters[escapedIndex] = 'n';
escapedIndex++;
break;
case '\r':
//--- Emit escaped carriage return
escapedCharacters[escapedIndex] = '\\';
escapedIndex++;
escapedCharacters[escapedIndex] = 'r';
escapedIndex++;
break;
case '\t':
//--- Emit escaped tab
escapedCharacters[escapedIndex] = '\\';
escapedIndex++;
escapedCharacters[escapedIndex] = 't';
escapedIndex++;
break;
default:
//--- Emit the character verbatim
escapedCharacters[escapedIndex] = inputCharacters[inputIndex];
escapedIndex++;
break;
}
}
//--- Convert the output buffer back to a string
return ShortArrayToString(escapedCharacters, 0, escapedIndex);
}
//+---------------------------------------------------------------+
//| Unescape a string parsed from JSON input |
//+---------------------------------------------------------------+
string UnescapeString(string value)
{
//--- Build input and output character buffers
ushort inputCharacters[], unescapedCharacters[];
//--- Convert the input string to a character array
int inputLength = StringToShortArray(value, inputCharacters);
//--- Reserve identical-length space (output never grows)
if(ArrayResize(unescapedCharacters, inputLength) != inputLength)
return NULL;
//--- Track output and input positions
int outputIndex = 0, inputIndex = 0;
//--- Walk through each input character
while(inputIndex < inputLength)
{
//--- Read the current character
ushort currentCharacter = inputCharacters[inputIndex];
//--- Detect a backslash escape sequence
if(currentCharacter == '\\' && inputIndex < inputLength - 1)
{
//--- Branch on the escape selector
switch(inputCharacters[inputIndex + 1])
{
case '\\':
//--- Decode escaped backslash
currentCharacter = '\\';
inputIndex++;
break;
case '"':
//--- Decode escaped double quote
currentCharacter = '"';
inputIndex++;
break;
case '/':
//--- Decode escaped forward slash
currentCharacter = '/';
inputIndex++;
break;
case 'b':
//--- Decode escaped backspace
currentCharacter = 8;
inputIndex++;
break;
case 'f':
//--- Decode escaped form feed
currentCharacter = 12;
inputIndex++;
break;
case 'n':
//--- Decode escaped line feed
currentCharacter = '\n';
inputIndex++;
break;
case 'r':
//--- Decode escaped carriage return
currentCharacter = '\r';
inputIndex++;
break;
case 't':
//--- Decode escaped tab
currentCharacter = '\t';
inputIndex++;
break;
}
}
//--- Write the decoded character to output
unescapedCharacters[outputIndex] = currentCharacter;
//--- Advance the output position
outputIndex++;
//--- Advance the input position
inputIndex++;
}
//--- Convert the output buffer back to a string
return ShortArrayToString(unescapedCharacters, 0, outputIndex);
}
};
//--- Initialize the static encoding code page to UTF-8
int JsonValue::encodingCodePage = CP_UTF8;
#endif // AI_JSON_FILE_MQH
//+------------------------------------------------------------------+