| //+------------------------------------------------------------------+
|
| //| 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
|
| //+------------------------------------------------------------------+
|