Skip to content

Instantly share code, notes, and snippets.

@sndwch
Last active June 7, 2024 01:38
Show Gist options
  • Save sndwch/b9092e08c910a6ea3f98 to your computer and use it in GitHub Desktop.
Save sndwch/b9092e08c910a6ea3f98 to your computer and use it in GitHub Desktop.
For MetaTrader 4. Creates JSON objects from MQL types
#property copyright "erdeszt"
#property version "1.00"
#property library
#include <Object.mqh>
#include <Arrays/ArrayObj.mqh>
#include <Arrays/ArrayString.mqh>
#include <mqlcommon/Erdeszt/Map.mqh>
#include <mqlcommon/Erdeszt/SmartPtr.mqh>
#include <mqlcommon/Erdeszt/Utils.mqh>
const ushort CHAR_TAB = 9;
const ushort CHAR_LF = 10;
const ushort CHAR_CR = 13;
const ushort CHAR_SPACE = 32;
const ushort CHAR_QUOTE = 34;
const ushort CHAR_COMMA = 44;
const ushort CHAR_DOT = 46;
const ushort CHAR_COLON = 58;
const ushort CHAR_BACKSLASH = 92;
const ushort CHAR_LBRACKET = 91;
const ushort CHAR_RBRACKET = 93;
const ushort CHAR_LBRACE = 123;
const ushort CHAR_RBRACE = 125;
enum JsonType
{ JsonNull
, JsonBool
, JsonNumber
, JsonString
, JsonArray
, JsonObject
};
enum BoolTrue { True };
enum BoolFalse { False };
string jsonTypeToString(const JsonType type) {
switch (type) {
case JsonNull: return "Null";
case JsonBool: return "Bool";
case JsonNumber: return "Number";
case JsonString: return "String";
case JsonArray: return "Array";
case JsonObject: return "Object";
}
exit("Invalid json type: " + (string)type);
return "";
}
class JsonValue : CObject {
private:
const JsonType mType;
const bool mBoolValue;
const double mNumberValue;
const string mStringValue;
const CArrayObj* mArrayValue;
Map* mObjectValue;
public:
// Constructors/Destructor
JsonValue() : mType(JsonNull) {}
JsonValue(const BoolTrue _) : mType(JsonBool) , mBoolValue(true) {}
JsonValue(const BoolFalse _) : mType(JsonBool) , mBoolValue(false) {}
JsonValue(const double number) : mType(JsonNumber), mNumberValue(number) {}
JsonValue(const string str) : mType(JsonString), mStringValue(str) {}
JsonValue(const CArrayObj* array) : mType(JsonArray) , mArrayValue(array) {}
JsonValue(Map* map) : mType(JsonObject), mObjectValue(map) {}
~JsonValue() { delete mArrayValue; delete mObjectValue; }
// Accessors
const JsonType type() const { return mType; }
const bool boolValue() const {
assert(mType == JsonBool, "JsonValue::boolValue() called on non bool value!");
return mBoolValue;
}
const string stringValue() const {
assert(mType == JsonString, "JsonValue::stringValue() called on non string value!");
return mStringValue;
}
const double numberValue() const {
assert(mType == JsonNumber, "JsonValue::numberValue() called on non number value!");
return mNumberValue;
}
const CArrayObj* arrayValue() const {
assert(mType == JsonArray, "JsonValue::arrayValue() called on non array value!");
return mArrayValue;
}
const Map* objectValue() const {
assert(mType == JsonObject, "JsonValue::objectValue() called on non object value!");
return mObjectValue;
}
// Shortcuts
const bool isTrue() const { return boolValue(); }
const bool isFalse() const { return !boolValue(); }
const string str() const { return stringValue(); }
const JsonValue* at(const int idx) const {
assert(mType == JsonArray, "JsonValue::at() called on non array value!");
assert(idx < mArrayValue.Total() - 1 && idx >= 0, "JsonValue::at() called with index larger than the length!");
return (JsonValue*)mArrayValue.At(idx);
}
JsonValue* get(const string key) const {
assert(mType == JsonObject, "JsonValue::get() called on non object value!");
return (JsonValue*)mObjectValue.lookup(key);
}
CArrayString* keys() const {
assert(mType == JsonObject, "JsonValue::keys() called on non object value!");
return mObjectValue.keys();
}
};
class JsonParseResult : CObject {
private:
JsonValue *mValue;
const string mRest;
const bool mSuccessful;
const string mError;
public:
JsonParseResult(JsonValue *value, const string rest)
: mValue(value), mRest(rest), mSuccessful(true) {}
JsonParseResult(const string error, const string rest)
: mError(error), mRest(rest), mSuccessful(false) {}
bool isSuccessful() const { return mSuccessful; }
JsonValue* value() { return mValue; }
string rest() const { return mRest; }
string error() const { return mError; }
JsonParseResult* clone() {
return mSuccessful
? new JsonParseResult(mValue, mRest)
: new JsonParseResult(mError, mRest);
}
};
class JsonParser : CObject {
private:
bool _isPrefix(const string prefix, const string str) const {
return prefix == StringSubstr(str, 0, StringLen(prefix));
}
bool _isNumAt(const string str, const int idx) const {
ushort chr = StringGetCharacter(str, idx);
return chr >= 48 && chr <= 57;
}
bool _isAlphaAt(const string str, const int idx) const {
ushort chr = StringGetCharacter(str, idx);
return (chr >= 65 && chr <= 90) || (chr >= 97 && chr <= 122);
}
bool _isSpaceAt(const string str, const int idx) const {
const ushort charCode = StringGetCharacter(str, idx);
return charCode == CHAR_TAB
|| charCode == CHAR_LF
|| charCode == CHAR_CR
|| charCode == CHAR_SPACE;
}
string _trimLeft(const string str) const {
const int strLen = StringLen(str);
int idx = 0;
if (!_isSpaceAt(str, idx)) {
return str;
}
while (idx < strLen && _isSpaceAt(str, idx++)) {}
return StringSubstr(str, idx - 1);
}
JsonParseResult* _tryParseNull(const string str) const {
if (_isPrefix("null", str)) {
return new JsonParseResult(new JsonValue(), StringSubstr(str, 4));
}
return new JsonParseResult("Expceted `null`", str);
}
JsonParseResult* _tryParseBool(const string str) const {
if (_isPrefix("true", str)) {
return new JsonParseResult(new JsonValue(True), StringSubstr(str, 4));
}
if (_isPrefix("false", str)) {
return new JsonParseResult(new JsonValue(False), StringSubstr(str, 5));
}
return new JsonParseResult("Expceted `true` or `false`", str);
}
JsonParseResult* _tryParseNumber(const string str) const {
int idx = -1;
int strLen = StringLen(str);
while (idx < strLen && _isNumAt(str, ++idx)) {}
if (idx == 0) {
return new JsonParseResult("Numeric value expected", str);
}
if (StringGetCharacter(str, idx) == CHAR_DOT) {
idx++;
while (idx < strLen && _isNumAt(str, ++idx)) {}
return new JsonParseResult(new JsonValue(StringToDouble(StringSubstr(str, 0, idx))), StringSubstr(str, idx));
}
return new JsonParseResult(new JsonValue(StringToDouble(StringSubstr(str, 0, idx))), StringSubstr(str, idx));
}
JsonParseResult* _tryParseString(const string str) const {
if (StringGetCharacter(str, 0) != CHAR_QUOTE) {
return new JsonParseResult("Quoted string expected", str);
}
const int strLen = StringLen(str);
int idx = 0;
bool escaped = false;
while (++idx < strLen) {
if (StringGetCharacter(str, idx) == CHAR_BACKSLASH && !escaped) {
escaped = true;
continue;
}
if (StringGetCharacter(str, idx) == CHAR_QUOTE && !escaped) {
return new JsonParseResult(new JsonValue(StringSubstr(str, 1, idx - 1)), StringSubstr(str, idx + 1));
}
escaped = false;
}
return new JsonParseResult("Unexpected", str);
}
JsonParseResult* _tryParseArray(const string str) const {
// Open bracket
if (StringGetCharacter(str, 0) != CHAR_LBRACKET) {
return new JsonParseResult("Left bracket expected!", str);
}
CArrayObj* items = new CArrayObj();
string rest = _trimLeft(StringSubstr(str, 1));
bool empty = true;
while (true) {
SmartPtr pItemResult = parse(rest);
JsonParseResult* itemResult = pItemResult.value();
rest = _trimLeft(itemResult.rest());
// Try to parse an item
if (!itemResult.isSuccessful()) {
// If we already have items then we should have one here
if (!empty) {
delete items;
return new JsonParseResult("Json value expected(Array)!", rest);
}
// Or a closing bracket
if (StringGetCharacter(rest, 0) != CHAR_RBRACKET) {
delete items;
return new JsonParseResult("Right bracket expected!", rest);
}
return new JsonParseResult(new JsonValue(items), _trimLeft(StringSubstr(rest, 1)));
}
if (empty) { empty = false; }
items.Add(itemResult.value());
// Parse a comma
if (StringGetCharacter(rest, 0) != CHAR_COMMA) {
// Or a closing bracket
if (StringGetCharacter(rest, 0) != CHAR_RBRACKET) {
delete items;
return new JsonParseResult("Right bracket or comma expected!", rest);
}
return new JsonParseResult(new JsonValue(items), _trimLeft(StringSubstr(rest, 1)));
}
rest = _trimLeft(StringSubstr(rest, 1));
}
return new JsonParseResult("This line in JsonParser::_tryParseArray should never be reached!", str);
}
JsonParseResult* _tryParseObject(const string str) const {
// Open curly brace
if (StringGetCharacter(str, 0) != CHAR_LBRACE) {
return new JsonParseResult("Left curly brace expected!", str);
}
Map* objs = new Map();
string rest = _trimLeft(StringSubstr(str, 1));
bool empty = true;
while (true) {
SmartPtr pKeyResult(_tryParseString(rest));
JsonParseResult* keyResult = pKeyResult.value();
rest = _trimLeft(keyResult.rest());
// Trty to parse a key
if (!keyResult.isSuccessful()) {
// If the object is not empty we need a value here
if (!empty) {
delete objs;
return new JsonParseResult("Json value expected(Object:1)", rest);
}
// Or a closing brace
if (StringGetCharacter(rest, 0) != CHAR_RBRACE) {
delete objs;
return new JsonParseResult("Right curly brace expected!", rest);
}
return new JsonParseResult(new JsonValue(objs), _trimLeft(StringSubstr(rest, 1)));
}
const string key = keyResult.value().str();
delete keyResult.value();
rest = _trimLeft(rest);
// Parse a colon
if (StringGetCharacter(rest, 0) != CHAR_COLON) {
delete objs;
return new JsonParseResult("Colon expected!", rest);
}
rest = _trimLeft(StringSubstr(rest, 1));
// Parse the value
SmartPtr pValueResult(parse(rest));
JsonParseResult* valueResult = pValueResult.value();
rest = _trimLeft(valueResult.rest());
if (!valueResult.isSuccessful()) {
delete objs;
return new JsonParseResult("Json value expected(Object:2)!", rest);
}
if (empty) { empty = false; }
objs.insert(key, valueResult.value());
// Parse a comma
if (StringGetCharacter(rest, 0) != CHAR_COMMA) {
// Or a brace
if (StringGetCharacter(rest, 0) != CHAR_RBRACE) {
delete objs;
return new JsonParseResult("Right brace expected!", rest);
}
return new JsonParseResult(new JsonValue(objs), _trimLeft(StringSubstr(rest, 1)));
}
rest = _trimLeft(StringSubstr(rest, 1));
}
return new JsonParseResult("Object parsing is not implemented yet", str);
}
public:
JsonParser() {}
JsonParseResult* parse(const string jsonString) const {
SmartPtr pParseResults(new CArrayObj());
CArrayObj* parseResults = pParseResults.value();
JsonParseResult* bestResult = NULL;
parseResults.Add(_tryParseNull(jsonString));
parseResults.Add(_tryParseBool(jsonString));
parseResults.Add(_tryParseNumber(jsonString));
parseResults.Add(_tryParseString(jsonString));
parseResults.Add(_tryParseArray(jsonString));
parseResults.Add(_tryParseObject(jsonString));
for (int i = 0, l = parseResults.Total(); i < l; i++) {
JsonParseResult* result = parseResults.At(i);
if (result.isSuccessful()) {
return result.clone();
}
if (null(bestResult)) {
bestResult = result;
}
if (StringLen(bestResult.rest()) > StringLen(result.rest())) {
bestResult = result;
}
}
return bestResult.clone();
}
};
class JsonPrinter : CObject {
public:
JsonPrinter() {}
string stringify(JsonValue* json) const {
switch (json.type()) {
case JsonNull: return "null";
case JsonBool: return json.boolValue() ? "true" : "false";
case JsonString: return "\"" + json.stringValue() + "\"";
case JsonNumber: return (string)json.numberValue();
case JsonArray: {
SmartPtr pItems(new CArrayString());
CArrayString* items = pItems.value();
const CArrayObj* arrayItems = json.arrayValue();
for (int i = 0, l = arrayItems.Total(); i < l; i++) {
items.Add(stringify((JsonValue*)arrayItems.At(i)));
}
return "[" + join(items) + "]";
}
case JsonObject: {
SmartPtr pKeys(json.keys());
CArrayString* keys = pKeys.value();
SmartPtr pItems(new CArrayString());
CArrayString* items = pItems.value();
for (int i = 0, l = keys.Total(); i < l; i++) {
items.Add("\"" + keys.At(i) + "\": " + stringify(json.get(keys.At(i))));
}
return "{ " + join(items) + " }";
}
}
exit("Invalid json type: " + (string)json.type());
return "";
}
};
class Json : CObject {
private:
const JsonParser mParser;
const JsonPrinter mPrinter;
public:
Json() {}
JsonParseResult* parse(const string jsonString) const { return mParser.parse(jsonString); }
string stringify(JsonValue* json) const { return mPrinter.stringify(json); }
};
#property copyright "erdeszt"
#property version "1.00"
#property library
#include <Object.mqh>
#include <Arrays/ArrayString.mqh>
#include <mqlcommon/Erdeszt/SmartPtr.mqh>
#include <mqlcommon/Erdeszt/Utils.mqh>
class Node {
private:
Node* mLeft;
Node* mRight;
string mKey;
CObject* mValue;
public:
Node(const string key, CObject* value, Node* left, Node* right)
: mKey(key)
, mValue(value)
, mLeft(left)
, mRight(right)
{}
Node(const string key, CObject* &value)
: mKey(key)
, mValue(value)
{}
string key() const { return mKey; }
void setKey(const string key) { mKey = key; }
CObject* value() const { return mValue; }
void setValue(CObject* value) { mValue = value; }
Node* left() const { return mLeft; }
void setLeft(Node* left) { mLeft = left; }
Node* right() const { return mRight; }
void setRight(Node* right) { mRight = right; }
};
enum NodePosition {
NP_EMPTY_TREE = -1,
NP_IN_PLACE = 0,
NP_LEFT = 1,
NP_RIGHT = 2
};
class NodeWithPosition : CObject {
private:
Node* mNode;
NodePosition mPosition;
public:
NodeWithPosition(Node* node, NodePosition position)
: mNode(node)
, mPosition(position)
{}
Node* node() const { return mNode; }
NodePosition position() const { return mPosition; }
};
class Map {
private:
Node* mRoot;
const bool mFreeNodeValueOnRemove;
uint mSize;
NodeWithPosition* _findNodeOrPlace(const string key) const {
if (null(mRoot)) {
return new NodeWithPosition(NULL, NP_EMPTY_TREE);
}
Node* node = mRoot;
// This loop should never terminate normally, returns should break out of the function at appropriate times.
// When we found the key, or we have to go left/right but there's no left/right node.
// These invariants should prevent node to become null.
while (notNull(node)) {
if (StringCompare(node.key(), key) == EQ) {
return new NodeWithPosition(node, NP_IN_PLACE);
}
if (StringCompare(node.key(), key) == LT) {
if (null(node.right())) {
return new NodeWithPosition(node, NP_RIGHT);
}
node = node.right();
}
else {
if (null(node.left())) {
return new NodeWithPosition(node, NP_LEFT);
}
node = node.left();
}
}
Print("ERROR|The impossible happened, this line should never be reached in Map::_findKeyOrPlace");
return NULL;
}
void _deleteNode(Node* node) {
if (mFreeNodeValueOnRemove && !null(node)) {
delete node.value();
}
if (notNull(node)) {
if (notNull(node.left())) { _deleteNode(node.left()); }
if (notNull(node.right())) { _deleteNode(node.right()); }
}
delete node;
mSize--;
}
Node* _newNode(const string key, CObject* value) {
mSize++;
return new Node(key, value);
}
void _copyNodeAndDeleteSource(Node* source, Node* dest) {
dest.setKey(source.key());
dest.setValue(source.value());
dest.setLeft(source.left());
dest.setRight(source.right());
_deleteNode(source);
}
public:
Map(const bool freeNodeValueOnRemove = true)
: mSize(0)
, mFreeNodeValueOnRemove(freeNodeValueOnRemove)
{}
~Map(void) { _deleteNode(mRoot); }
uint size(void) const { return mSize; }
void insert(const string key, CObject* value) {
SmartPtr pNodePosition(_findNodeOrPlace(key));
NodePosition position = ((NodeWithPosition*)pNodePosition.value()).position();
Node* node = ((NodeWithPosition*)pNodePosition.value()).node();
if (position == NP_EMPTY_TREE) {
mRoot = _newNode(key, value);
}
else if (position == NP_IN_PLACE) {
node.setValue(value);
}
else if (position == NP_RIGHT) {
node.setRight(_newNode(key, value));
}
else {
node.setLeft(_newNode(key, value));
}
}
CObject* lookup(const string key) const {
SmartPtr pNodePosition(_findNodeOrPlace(key));
if (((NodeWithPosition*)pNodePosition.value()).position() == NP_IN_PLACE) {
return ((NodeWithPosition*)pNodePosition.value()).node().value();
}
return NULL;
}
bool has(const string key) const {
SmartPtr pNodePosition(_findNodeOrPlace(key));
return ((NodeWithPosition*)pNodePosition.value()).position() == NP_IN_PLACE;
}
void remove(const string key) {
SmartPtr pNodePosition(_findNodeOrPlace(key));
Node* node = ((NodeWithPosition*)pNodePosition.value()).node();
if (((NodeWithPosition*)pNodePosition.value()).position() != NP_IN_PLACE) {
return;
}
// No children, just delete
if (null(node.left()) && null(node.right())) {
_deleteNode(node);
}
// Only right chlid replace node with right child
else if (null(node.left())) {
_copyNodeAndDeleteSource(node.right(), node);
}
// Only left child, replace node with left child
else if (null(node.right())) {
_copyNodeAndDeleteSource(node.left(), node);
}
// Two children
else {
Node* min = node.right();
// Node to delete
Node* target;
// Find smallest(leftmost) child in the right subtree of the node
// This value is bigger then all children in node's left subtree and
// smaller then all children in node' right subtree
while (notNull(min.left())) {
min = min.left();
}
// Replace node with min node
node.setValue(min.value());
node.setKey(min.key());
target = min;
// If node had right child(can't have left because it's leftmost)
// then move it to the min node's place
if (notNull(min.right())) {
min = min.right();
}
_deleteNode(target);
}
}
CArrayString* keys(void) {
return keys(mRoot, new CArrayString());
}
CArrayString* keys(Node* node, CArrayString* _keys) {
if (node == NULL) {
return _keys;
}
keys(node.left(), _keys);
_keys.Add(node.key());
keys(node.right(), _keys);
return _keys;
}
};
#property copyright "erdeszt"
#property version "1.00"
#property library
#include <Object.mqh>
class SmartPtr : CObject {
private:
CObject* mValue;
public:
SmartPtr(CObject* value) : mValue(value) {}
~SmartPtr() { delete mValue; }
CObject* value() const { return mValue; }
};
#property copyright "erdeszt"
#property version "1.00"
#property library
#include <Arrays/ArrayString.mqh>
enum Ordering {
LT = -1,
EQ = 0,
GT = 1
};
template<typename T>
bool null(const T* ptr) {
return ptr == NULL || CheckPointer(ptr) == POINTER_INVALID;
}
template<typename T>
bool notNull(const T* ptr) {
return !null(ptr);
}
void exit(const string msg) {
Print("UNRECOVERABLE ERROR OCCURED: " + msg);
Print("LAST ERROR CODE: " + (string)GetLastError());
ExpertRemove();
}
bool assert(const bool predicate, const string msg) {
if (!predicate) {
exit(msg);
}
return true;
}
string join(CArrayString* strs, const string sep = ", ") {
const int strsLen = strs.Total();
if (strsLen == 0) { return ""; }
string result = strs.At(0);
for (int i = 1; i < strsLen; i++) {
StringAdd(result, sep + strs.At(i));
}
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment