1033 lines
No EOL
43 KiB
MQL5
1033 lines
No EOL
43 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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 ¤tIndex)
|
|
{
|
|
//--- 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 ¤tIndex)
|
|
{
|
|
//--- 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
|
|
//+------------------------------------------------------------------+ |