Created
March 28, 2020 22:24
-
-
Save minhphuc429/72a99568a5faf5536b41b7114c7c0469 to your computer and use it in GitHub Desktop.
Libraries: JSON Parser MQL4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// $Id: hash.mqh 125 2014-03-03 08:38:32Z ydrol $ | |
#ifndef YDROL_HASH_MQH | |
#define YDROL_HASH_MQH | |
//#property strict | |
/* | |
This is losely ported from a C version I have which was in turn modified from hashtable.c by Christopher Clark. | |
Copyright (C) 2014, Andrew Lord (NICKNAME=lordy) <[email protected]> | |
Copyright (C) 2002, 2004 Christopher Clark <[email protected]> | |
2014/02/21 - Readded PrimeNumber sizes and auto rehashing when load factor hit. | |
*/ | |
/// Any value stored in a Hash must be a subclass of HashValue | |
class HashValue { | |
}; | |
/// Linked list of values - there will be one list for each hash value | |
class HashEntry { | |
public: | |
string _key; | |
HashValue * _val; | |
HashEntry *_next; | |
HashEntry *_ordered_prev; | |
HashEntry *_ordered_next; | |
HashEntry() { | |
_key=NULL; | |
_val=NULL; | |
_next=NULL; | |
_ordered_prev=NULL; | |
_ordered_next=NULL; | |
} | |
HashEntry(string key,HashValue* val) { | |
_key=key; | |
_val=val; | |
_next=NULL; | |
_ordered_prev=NULL; | |
_ordered_next=NULL; | |
} | |
~HashEntry() { | |
} | |
}; | |
/// Convenience class for storing strings as hash values. | |
class HashString : public HashValue { | |
private: | |
string val; | |
public: | |
HashString(string v) { val=v;} | |
string getVal() { return val; } | |
}; | |
/// Convenience class for storing doubles as hash values. | |
class HashDouble : public HashValue { | |
private: | |
double val; | |
public: | |
HashDouble(double v) { val=v;} | |
double getVal() { return val; } | |
}; | |
/// Convenience class for storing ints as hash values. | |
class HashInt : public HashValue { | |
private: | |
int val; | |
public: | |
HashInt(int v) { val=v;} | |
int getVal() { return val; } | |
}; | |
/// Convenience class for storing longs as hash values. | |
class HashLong : public HashValue { | |
private: | |
long val; | |
public: | |
HashLong(datetime v) { val=v;} | |
long getVal() { return val; } | |
}; | |
/// Convenience class for storing datetimes as hash values. | |
class HashDatetime : public HashValue { | |
private: | |
datetime val; | |
public: | |
HashDatetime(datetime v) { val=v;} | |
datetime getVal() { return val; } | |
}; | |
/// | |
/// Hash class allows objects to be stored in a table index by strings. | |
/// the stored Objects must be a sub class of the HashValue class. | |
/// | |
/// There are some convenience classes to hold atomic types as values HashString,HashDouble,HashInt | |
/// | |
///EXAMPLE: | |
/// | |
/// <pre> | |
/// class myClass: public HashValue { | |
/// public: int v; | |
/// myClass(int a) { v = a;} | |
/// }; | |
/// | |
/// // Create the objects as needed | |
/// | |
/// myClass *a = new myClass(1); | |
/// myClass *b = new myClass(2); | |
/// myClass *c = new myClass(3); | |
/// | |
/// // Then to insert into hash etc. | |
/// | |
/// Hash* h = new Hash(193,true); | |
/// // 'true' means when the hash will adopt the values and delete them when they are removed from the hash or when the hash is deleted. | |
/// | |
/// h.hPut("a",a); | |
/// h.hPut("b",b); | |
/// h.hPut("c",c); | |
/// | |
/// myClass *d = h.hGet("b"); | |
/// | |
/// etc. | |
/// | |
/// // Iterate over hash | |
/// HashLoop *l | |
/// for (l = new HashLoop(h) ; l.hasNext() ; l.next() ) { | |
/// string key = l.key(); | |
/// MyClass *c = l.val(); | |
/// } | |
/// delete l; | |
/// | |
/// // Delete from hash - This will also delete 'a' because we set the 'adopt' flag on the hash. | |
/// h.hDel("a"); | |
/// | |
/// //Delete the hash - this will also delete 'b' and 'c' because of the adopt flag. | |
/// delete h; | |
/// </pre> | |
class Hash : public HashValue { | |
private: | |
/// Number of slots in the hashtable. | |
/// this should be approx number of elements to store. Depending on hash algorithm | |
/// it may optimally be a prime or a power of two etc. but probably not important | |
/// for MQL4 performance. A future optimisation might be to move the hashcode function to a DLL?? | |
uint _hashSlots; | |
/// Number of elements at which hash will get resized. | |
int _resizeThreshold; | |
/// number of things in the hash | |
int _hashEntryCount; | |
/// an array of linked lists (HashEntry). one for each hash value. | |
/// To store an object against a string(key) - get the string hashcode, then insert pair (key,val) into the linked list for that hashcode. | |
/// To fetch an object against a string(key) - get the string hashcode, get linked-list at that hashcode index, then search for the key and return the val. | |
HashEntry* _buckets[]; | |
HashEntry* _ordered_first; | |
HashEntry* _ordered_last; | |
/// If true the hash will free(delete) values as they are removed, or at cleanup. | |
bool _adoptValues; | |
int _errCode; | |
string _errText; | |
void init(uint size,bool adoptValues) | |
{ | |
_ordered_first = NULL; | |
_ordered_last = NULL; | |
_hashSlots = 0; | |
_hashEntryCount = 0; | |
clearError(); | |
setAdoptValues(adoptValues); | |
rehash(size); | |
} | |
// Hash table distribution is better when size is prime, eg if hash function procduces numbers | |
// that are multiples of x, then there may be grouping occuring around gcd(x,slots) gcd(2x,slots) etc | |
// using a prime size helps spread the distribution. | |
uint size2prime(uint size) { | |
int pmax=ArraySize(_primes); | |
for(int p=0 ; p<pmax; p++ ) { | |
if (_primes[p] >= size) { | |
return _primes[p]; | |
} | |
} | |
return size; | |
} | |
/// Primes that approx double in size, used for hash table sizes to avoid gcd causing bunching | |
static uint _primes[]; | |
uint hash32(string s) | |
{ // FNV-1a 32 hash algorithm | |
ushort c[]; | |
uint hval = 0x811c9dc5; | |
const uint FNV_32_PRIME = 0x01000193; | |
if (s != NULL) | |
{ | |
int n = StringToShortArray(s,c); | |
// n is one bigger than the length of s because it includes the terminating zero | |
// so do not execute the following loop for this terminating zero. | |
for(int i = 0 ; i < n-1 ; i++) | |
{ | |
ushort code = c[i]; | |
if(code < 128) | |
{ | |
hval ^= code; | |
} | |
else if(code < 2048) | |
{ | |
hval ^= 192 + (code >> 6); | |
hval *= FNV_32_PRIME; | |
hval ^= 128 + (code & 63); | |
} | |
else // code < 65536, because ushort | |
{ | |
hval ^= 224 + (code >> 12); | |
hval *= FNV_32_PRIME; | |
hval ^= 128 + ((code>>6) & 63); | |
hval *= FNV_32_PRIME; | |
hval ^= 128 + (code & 63); | |
} | |
hval *= FNV_32_PRIME; | |
} | |
} | |
return hval % _hashSlots; | |
} | |
uint hash64(string s) | |
{ // FNV-1a 64 hash algorithm | |
ushort c[]; | |
ulong hval = 0xCBF29CE484222325; | |
const ulong FNV_64_PRIME = 0x00000100000001B3; | |
if (s != NULL) | |
{ | |
int n = StringToShortArray(s,c); | |
// n is one bigger than the length of s because it includes the terminating zero | |
// so do not execute the following loop for this terminating zero. | |
for(int i = 0 ; i < n-1 ; i++) | |
{ | |
ushort code = c[i]; | |
if(code < 128) | |
{ | |
hval ^= code; | |
} | |
else if(code < 2048) | |
{ | |
hval ^= 192 + (code >> 6); | |
hval *= FNV_64_PRIME; | |
hval ^= 128 + (code & 63); | |
} | |
else // code < 65536, because ushort | |
{ | |
hval ^= 224 + (code >> 12); | |
hval *= FNV_64_PRIME; | |
hval ^= 128 + ((code>>6) & 63); | |
hval *= FNV_64_PRIME; | |
hval ^= 128 + (code & 63); | |
} | |
hval *= FNV_64_PRIME; | |
} | |
} | |
return (uint)(hval % _hashSlots); | |
} | |
void clearError() { | |
setError(0,""); | |
} | |
void setError(int e,string m) { | |
_errCode = e; | |
_errText = m; | |
//error((string)e,m); | |
} | |
public: | |
/// Constructor: Create a Hash Object | |
Hash() { | |
init(17,true); | |
} | |
/// Constructor: Create a Hash Object | |
/// @param adoptValues : If true the hash destructor will <b>delete</b> all dynamically allocated hash values. | |
Hash(bool adoptValues) { | |
init(17,adoptValues); | |
} | |
/// Constructor: Create a Hash Object | |
/// @param size : Approximate size (actual size will be a larger prime number close to a power of 2) | |
/// @param adoptValues : If true the hash destructor will <b>delete</b> all dynamically allocated hash values. | |
Hash(int size,bool adoptValues) { | |
init(size,adoptValues); | |
} | |
~Hash() { | |
// Free entries. | |
for(uint i = 0 ; i< _hashSlots ; i++) { | |
HashEntry *nextEntry = NULL; | |
for(HashEntry *entry = _buckets[i] ; entry!= NULL ; entry = nextEntry ) | |
{ | |
nextEntry = entry._next; | |
if (_adoptValues && entry._val != NULL && CheckPointer(entry._val) == POINTER_DYNAMIC ) { | |
delete entry._val; | |
} | |
delete entry; | |
} | |
_buckets[i] = NULL; | |
} | |
} | |
/// Return any error that has occured. This should be used when | |
/// retriving values in a Hash that may contain NULLs. hGet() | |
/// methods can return NULL if not found, in which case getErrorCode | |
/// will be set. | |
int getErrCode() { | |
return _errCode; | |
} | |
/// Return text of the error message. | |
string getErrText() { | |
return _errText; | |
} | |
/// If true the hash destructor will <b>delete</b> all dynamically allocated hash values. | |
void setAdoptValues(bool v) { | |
_adoptValues = v; | |
} | |
/// True if the hash destructor will <b>delete</b> all dynamically allocated hash values. | |
bool getAdoptValues() { | |
return _adoptValues; | |
} | |
private: | |
uint _foundIndex; // After find() is called is set to hashindex for name whether found or not. | |
HashEntry* _foundEntry; // After find() is called is set to the HashEntry that contains the key. | |
HashEntry* _foundPrev; // After find() is called is set to the HashEntry before the entry | |
// (could use double linked list but requires more memory). | |
/// Look for the required entry for key 'name' true if found. | |
bool find(string keyName) { | |
//Alert("finding"); | |
bool found = false; | |
// Get the index using the hashcode of the string | |
_foundIndex = hash64(keyName); | |
if (_foundIndex>_hashSlots ) { | |
setError(1,"hGet: bad hashIndex="+(string)_foundIndex+" size "+(string)_hashSlots); | |
} else { | |
// Search the linked list determined by the index. | |
_foundPrev = NULL; | |
for(HashEntry *e = _buckets[_foundIndex] ; e != NULL ; e = e._next ) { | |
if (e._key == keyName) { | |
_foundEntry = e; | |
found=true; | |
break; | |
} | |
// Track the item before the target item in case deleting from single linked list. | |
_foundPrev = e; | |
} | |
} | |
return found; | |
} | |
public: | |
/// This is used by the HashLoop class to get start of LinkedList at bucket[i] | |
HashEntry*getEntry(int i) { | |
return _buckets[i]; | |
} | |
/// Return the number of slots/buckets (not number of elements) | |
uint getSlots() { | |
return _hashSlots; | |
} | |
/// Return the number of elements in the Hash | |
int getCount() { | |
return _hashEntryCount; | |
} | |
/// Change the hash size and re-allocate values to new buckets. | |
bool rehash(uint newSize) { | |
bool ret = false; | |
HashEntry* oldTable[]; | |
uint oldSize = _hashSlots; | |
newSize = size2prime(newSize); | |
//info("rehashing from "+(string)_hashSlots+" to "+(string)newSize+" "+(string)GetTickCount()); | |
if (newSize <= getSlots()) { | |
setError(2,"rehash "+(string)newSize+" <= "+(string)_hashSlots); | |
} else if (ArrayResize(_buckets,newSize) != newSize) { | |
setError(3,"unable to resize "); | |
} else if (ArrayResize(oldTable,oldSize) != oldSize) { | |
setError(4,"unable to resize old copy "); | |
} else { | |
uint i; | |
//Copy old table. | |
for(i = 0 ; i < oldSize ; i++ ) oldTable[i] = _buckets[i]; | |
// Init new entries - not sure if MQL does this anyway | |
for(i = 0 ; i<newSize ; i++ ) _buckets[i] = NULL; | |
// Move entries to new slots | |
_hashSlots = newSize; | |
_resizeThreshold = (int)_hashSlots / 4 * 3; // Just use the default load factor value of Javas HashTable | |
// Look through all slots | |
for(uint oldHashCode = 0 ; oldHashCode<oldSize ; oldHashCode++ ) { | |
HashEntry *next = NULL; | |
// Walk linked list | |
for(HashEntry *e = oldTable[oldHashCode] ; e != NULL ; e = next ) { | |
next = e._next; | |
uint newHashCode = hash64(e._key); | |
// Insert at head of new list. | |
e._next = _buckets[newHashCode]; | |
_buckets[newHashCode] = e; | |
} | |
oldTable[oldHashCode] = NULL; | |
} | |
ret = true; | |
} | |
return ret; | |
} | |
/// Check if the hash contains the given key | |
/// @param keyName : The key | |
/// @return: true if found otherwise false | |
bool hContainsKey(string keyName) { | |
return find(keyName); | |
} | |
/// Fetch a value using string key | |
/// @return :HashValue associated with the key (or NULL if none found) | |
/// If the Hashtable contains legitimate NULL values then also check errCode() | |
/// Examples: | |
/// If not storing nulls use | |
/// obj = hash.hGet(x); if (obj != NULL) OK | |
/// | |
/// If storing nulls use | |
/// obj = hash.hGet(x); if (obj != NULL || hash.errCode() == 0 ) OK | |
HashValue* hGet(string keyName) { | |
HashValue *obj = NULL; | |
clearError(); | |
bool found=false; | |
if (find(keyName)) { | |
obj = _foundEntry._val; | |
} else { | |
//If Hash contains nulls then also check the errorCode=0 when retrieving | |
if (!found) { | |
setError(1,"not found"); | |
} | |
} | |
return obj; | |
} | |
/// Convenience method for getting values from a HashString value (see hPutString()) | |
string hGetString(string keyName) { | |
string ret = NULL; | |
HashString *v = hGet(keyName); | |
if (v != NULL) { | |
ret = v.getVal(); | |
} | |
return ret; | |
} | |
/// Convenience method for getting values from a HashDouble value (see hPutDouble()) | |
double hGetDouble(string keyName) { | |
double ret = NULL; | |
HashDouble *v = hGet(keyName); | |
if (v != NULL) { | |
ret = v.getVal(); | |
} | |
return ret; | |
} | |
/// Convenience method for getting values from a HashInt value (see hPutInt()) | |
int hGetInt(string keyName) { | |
int ret = NULL; | |
HashInt *v = hGet(keyName); | |
if (v != NULL) { | |
ret = v.getVal(); | |
} | |
return ret; | |
} | |
/// Convenience method for getting values from a HashLong ( see hPutLong()) | |
long hGetLong(string keyName) { | |
long ret = NULL; | |
HashLong *v = hGet(keyName); | |
if (v != NULL) { | |
ret = v.getVal(); | |
} | |
return ret; | |
} | |
/// Convenience method for getting values from a HashDatetime ( see hPutDatetime()) | |
datetime hGetDatetime(string keyName) { | |
datetime ret = NULL; | |
HashDatetime *v = hGet(keyName); | |
if (v != NULL) { | |
ret = v.getVal(); | |
} | |
return ret; | |
} | |
/// Store a hash value against the <b>keyName</b> key. This will overwrite any existing | |
/// value. It adoptValues is set, it will also free the value if applicable. | |
/// @param keyName : key name | |
/// @param obj : Value to store | |
/// @return the previous value of the key or NULL if there wasnt one | |
void hPut(string keyName,HashValue *obj) { | |
clearError(); | |
if (find(keyName)) { | |
// Replace entry contents | |
if (_adoptValues && _foundEntry._val != NULL && CheckPointer(_foundEntry._val) == POINTER_DYNAMIC ) { | |
delete _foundEntry._val; | |
} | |
_foundEntry._val = obj; | |
} else { | |
// Insert new entry at head of list | |
HashEntry* e = new HashEntry(keyName,obj); | |
HashEntry* first = _buckets[_foundIndex]; | |
e._next = first; | |
_buckets[_foundIndex] = e; | |
_hashEntryCount++; | |
if(NULL==_ordered_last) | |
{ | |
_ordered_first = e; | |
_ordered_last = e; | |
e._ordered_prev = NULL; | |
e._ordered_next = NULL; | |
} | |
else | |
{ | |
e._ordered_prev = _ordered_last; | |
e._ordered_next = NULL; | |
_ordered_last._ordered_next = e; | |
_ordered_last = e; | |
} | |
//info((string)_hashEntryCount+" vs. "+(string)_resizeThreshold); | |
// Auto Resize if number of entries hits _resizeThreshold | |
if (_hashEntryCount > _resizeThreshold ) { | |
rehash(_hashSlots/2*3); // this will snap to the next prime | |
} | |
} | |
} | |
/// Store a string as hash value (HashString) | |
/// @the previous value of the key or NULL if there wasnt one | |
void hPutString(string keyName,string s) { | |
HashString *v = new HashString(s); | |
hPut(keyName,v); | |
} | |
/// Store a double as hash value (HashDouble) | |
/// @the previous value of the key or NULL if there wasnt one | |
void hPutDouble(string keyName,double d) { | |
HashDouble *v = new HashDouble(d); | |
hPut(keyName,v); | |
} | |
/// Store an int as hash value (HashInt) | |
/// @the previous value of the key or NULL if there wasnt one | |
void hPutInt(string keyName,int i) { | |
HashInt *v = new HashInt(i); | |
hPut(keyName,v); | |
} | |
/// Store a datetime as hash value (HashLong) | |
/// @the previous value of the key or NULL if there wasnt one | |
void hPutLong(string keyName,long i) { | |
HashLong *v = new HashLong(i); | |
hPut(keyName,v); | |
} | |
/// Store a datetime as hash value (HashDatetime) | |
/// @the previous value of the key or NULL if there wasnt one | |
void hPutDatetime(string keyName,datetime i) { | |
HashDatetime *v = new HashDatetime(i); | |
hPut(keyName,v); | |
} | |
/// Delete an entry from the hash. | |
bool hDel(string keyName) { | |
bool found = false; | |
clearError(); | |
if (find(keyName)) { | |
HashEntry *next = _foundEntry._next; | |
if (_foundPrev != NULL) { | |
//Remove entry from the middle of the list. | |
_foundPrev._next = next; | |
} else { | |
// remove from head of list | |
_buckets[_foundIndex] = next; | |
} | |
if(NULL == _foundEntry._ordered_prev) | |
{ | |
_ordered_first = _foundEntry._ordered_next; | |
} | |
else | |
{ | |
_foundEntry._ordered_prev._ordered_next = _foundEntry._ordered_next; | |
} | |
if(NULL == _foundEntry._ordered_next) | |
{ | |
_ordered_last = _foundEntry._ordered_prev; | |
} | |
else | |
{ | |
_foundEntry._ordered_next._ordered_prev = _foundEntry._ordered_prev; | |
} | |
if (_adoptValues && _foundEntry._val != NULL&& CheckPointer(_foundEntry._val) == POINTER_DYNAMIC) { | |
delete _foundEntry._val; | |
} | |
delete _foundEntry; | |
_hashEntryCount--; | |
found=true; | |
} | |
return found; | |
} | |
HashEntry* get_ordered_first() { return _ordered_first; } | |
HashEntry* get_ordered_last() { return _ordered_last; } | |
}; | |
uint Hash::_primes[] = { | |
17, 53, 97, 193, 389, | |
769, 1543, 3079, 6151, | |
12289, 24593, 49157, 98317, | |
196613, 393241, 786433, 1572869, | |
3145739, 6291469, 12582917, 25165843, | |
50331653, 100663319, 201326611, 402653189, | |
805306457, 1610612741}; | |
/// Class to iterate over a Hash using ... | |
/// <pre> | |
/// HashLoop *l | |
/// for (l = new HashLoop(h) ; l.hasNext() ; l.next() ) { | |
/// string key = l.key(); | |
/// MyClass *c = l.val(); | |
/// } | |
/// delete l; | |
/// </pre> | |
class HashLoop { | |
private: | |
HashEntry *_currentEntry; | |
Hash *_hash; | |
public: | |
/// Create iterator for a hash - move to first item | |
HashLoop(Hash *h, bool reset_to_last_object=false) { | |
setHash(h, reset_to_last_object); | |
} | |
~HashLoop() {}; | |
/// Clear current state and move to first item (if any). | |
void reset(bool reset_to_last_object=false) { | |
_currentEntry = reset_to_last_object ? _hash.get_ordered_last() : _hash.get_ordered_first(); | |
} | |
/// Change the hash over which to iterate. | |
void setHash(Hash *h, bool reset_to_last_object=false) { | |
_hash = h; | |
reset(reset_to_last_object); | |
} | |
/// Check if current item is valid and so the functions | |
/// key() and val() will not return NULL. | |
bool isValid() { | |
bool ret = ( _currentEntry != NULL); | |
//config("hasNext=",ret); | |
return ret; | |
} | |
/// hasNext(): same as isValid(), only for compatibility reasons with older versions | |
bool hasNext() { | |
bool ret = ( _currentEntry != NULL); | |
//config("hasNext=",ret); | |
return ret; | |
} | |
/// Move to next item. | |
void next() { | |
// Advance | |
if (_currentEntry != NULL) { | |
_currentEntry = _currentEntry._ordered_next; | |
} | |
} | |
/// Move to previous item. | |
void prev() { | |
if (_currentEntry != NULL) { | |
_currentEntry = _currentEntry._ordered_prev; | |
} | |
} | |
/// Return the key name of the current item. | |
string key() { | |
if (_currentEntry != NULL) { | |
return _currentEntry._key; | |
} else { | |
return NULL; | |
} | |
} | |
/// Return the value. | |
HashValue *val() { | |
if (_currentEntry != NULL) { | |
return _currentEntry._val; | |
} else { | |
return NULL; | |
} | |
} | |
/// Convenience functions for retriving int from a current HashInt entry | |
int valInt() { | |
return ((HashInt *)val()).getVal(); | |
} | |
/// Convenience functions for retriving int from a current HashString entry | |
string valString() { | |
return ((HashString *)val()).getVal(); | |
} | |
/// Convenience functions for retriving int from a current HashDouble entry | |
double valDouble() { | |
return ((HashDouble *)val()).getVal(); | |
} | |
/// Convenience functions for retriving int from a current HashLong entry | |
long valLong() { | |
return ((HashLong *)val()).getVal(); | |
} | |
/// Convenience functions for retriving int from a current HashDatetime entry | |
datetime valDatetime() { | |
return ((HashDatetime *)val()).getVal(); | |
} | |
}; | |
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// $Id: json.mqh 4 2015-06-24 13:11:09Z ydrol $ | |
#ifndef YDROL_JSON_MQH | |
#define YDROL_JSON_MQH | |
// (C)2014 Andrew Lord forex@[email protected] | |
// Parse a JSON String - Adapted for mql4++ from my gawk implementation | |
// ( https://code.google.com/p/oversight/source/browse/trunk/bin/catalog/json.awk ) | |
/* | |
TODO the constants true|false|null could be represented as fixed objects. | |
To do this the deleting of _hash and _array must skip these objects. | |
TODO test null | |
TODO Parse Unicode Escape | |
*/ | |
//#define TOLERATE_TAB_CR_NL_IN_STRINGS | |
/* | |
See json_demo for examples. | |
This requires the hash.mqh ( http://codebase.mql4.com/9238 , http://lordy.co.nf/hash ) | |
*/ | |
#include <hash.mqh> | |
/// Different types of JSON Values | |
enum ENUM_JSON_TYPE { JSON_NULL, JSON_OBJECT , JSON_ARRAY, JSON_NUMBER, JSON_STRING , JSON_BOOL }; | |
class JSONString ; | |
/// | |
/// Generic class for all JSON types (Number, String, Bool, Array, Object ) | |
/// It is a subclass of HashValue so it can be stored in an JSONObject hash | |
/// | |
class JSONValue : public HashValue { | |
private: | |
ENUM_JSON_TYPE _type; | |
int _pos, _line, _column; | |
public: | |
JSONValue(int pos=0, int line=0, int column=0) : _pos(pos), _line(line), _column(column) { } | |
~JSONValue() {} | |
ENUM_JSON_TYPE getType() { return _type; } | |
void setType(ENUM_JSON_TYPE t) { _type = t; } | |
/// True if JSONValue is a instance of JSONString | |
bool isString() { return _type == JSON_STRING; } | |
/// True if JSONValue is a instance of JSONNull | |
bool isNull() { return _type == JSON_NULL; } | |
/// True if JSONValue is a instance of JSONObject | |
bool isObject() { return _type == JSON_OBJECT; } | |
/// True if JSONValue is a instance of JSONArray | |
bool isArray() { return _type == JSON_ARRAY; } | |
/// True if JSONValue is a instance of JSONNumber | |
bool isNumber() { return _type == JSON_NUMBER; } | |
bool isInt() { return _type == JSON_NUMBER && ((JSONNumber*)GetPointer(this)).isInt(); } | |
bool isIntType() { return _type == JSON_NUMBER && ((JSONNumber*)GetPointer(this)).isIntType(); } | |
bool isLong() { return _type == JSON_NUMBER && ((JSONNumber*)GetPointer(this)).isLong(); } | |
bool isLongType() { return _type == JSON_NUMBER && ((JSONNumber*)GetPointer(this)).isLongType(); } | |
bool isDouble() { return _type == JSON_NUMBER && ((JSONNumber*)GetPointer(this)).isDouble(); } | |
/// True if JSONValue is a instance of JSONBool | |
bool isBool() { return _type == JSON_BOOL; } | |
// Override in child classes | |
virtual string toString() { | |
return ""; | |
} | |
// Some convenience getters to cast to the subtype. - this is bad OO design! | |
/// If this JSONValue is an instance of JSONString return the string (or cast will fail) | |
string getString() { return ((JSONString *)GetPointer(this)).getString(); } | |
/// If this JSONValue is an instance of JSONNumber return the double (or cast will fail) | |
double getDouble() { return ((JSONNumber *)GetPointer(this)).getDouble(); } | |
/// If this JSONValue is an instance of JSONNumber return the long (or cast will fail) | |
long getLong() { return ((JSONNumber *)GetPointer(this)).getLong(); } | |
/// If this JSONValue is an instance of JSONNumber return the int (or cast will fail) | |
int getInt() { return ((JSONNumber *)GetPointer(this)).getInt(); } | |
/// If this JSONValue is an instance of JSONBool return the bool (or cast will fail) | |
bool getBool() { return ((JSONBool *)GetPointer(this)).getBool(); } | |
/// Get the string value of the JSONValue, without Program termination | |
/// @param val : String object from which value will be extracted. | |
/// @param out : The string than was extracted. | |
/// @return true if OK else false | |
static bool getString(JSONValue *val,string &out) | |
{ | |
if (val != NULL && val.isString()) { | |
out = val.getString(); | |
return true; | |
} | |
return false; | |
} | |
/// Get the bool value of the JSONValue, without Program termination | |
/// @param val : String object from which value will be extracted. | |
/// @param out : The bool than was extracted. | |
/// @return true if OK else false | |
static bool getBool(JSONValue *val,bool &out) | |
{ | |
if (val != NULL && val.isBool()) { | |
out = val.getBool(); | |
return true; | |
} | |
return false; | |
} | |
/// Get the double value of the JSONValue, without Program termination | |
/// @param val : String object from which value will be extracted. | |
/// @param out : The double than was extracted. | |
/// @return true if OK else false | |
static bool getDouble(JSONValue *val,double &out) | |
{ | |
if (val != NULL && val.isDouble()) { | |
out = val.getDouble(); | |
return true; | |
} | |
return false; | |
} | |
/// Get the long value of the JSONValue, without Program termination | |
/// @param val : String object from which value will be extracted. | |
/// @param out : The long than was extracted. | |
/// @return true if OK else false | |
static bool getLong(JSONValue *val,long &out) | |
{ | |
if (val != NULL && val.isLong()) { | |
out = val.getLong(); | |
return true; | |
} | |
return false; | |
} | |
static bool getLongType(JSONValue *val,long &out) | |
{ | |
if (val != NULL && val.isLongType()) { | |
out = val.getLong(); | |
return true; | |
} | |
return false; | |
} | |
/// Get the int value of the JSONValue, without Program termination | |
/// @param val : String object from which value will be extracted. | |
/// @param out : The int than was extracted. | |
/// @return true if OK else false | |
static bool getInt(JSONValue *val,int &out) | |
{ | |
if (val != NULL && val.isInt()) { | |
out = val.getInt(); | |
return true; | |
} | |
return false; | |
} | |
static bool getIntType(JSONValue *val,int &out) | |
{ | |
if (val != NULL && val.isIntType()) { | |
out = val.getInt(); | |
return true; | |
} | |
return false; | |
} | |
static bool isString(JSONValue *val) | |
{ | |
return val != NULL && val.isString(); | |
} | |
static bool isBool(JSONValue *val) | |
{ | |
return val != NULL && val.isBool(); | |
} | |
static bool isNumber(JSONValue *val) | |
{ | |
return val != NULL && val.isNumber(); | |
} | |
static bool isDouble(JSONValue *val) | |
{ | |
return val != NULL && val.isNumber() && ((JSONNumber*)val).isDouble(); | |
} | |
static bool isLong(JSONValue *val) | |
{ | |
return val != NULL && val.isNumber() && ((JSONNumber*)val).isLong(); | |
} | |
static bool isLongType(JSONValue *val) | |
{ | |
return val != NULL && val.isNumber() && ((JSONNumber*)val).isLongType(); | |
} | |
static bool isInt(JSONValue *val) | |
{ | |
return val != NULL && val.isNumber() && ((JSONNumber*)val).isInt(); | |
} | |
static bool isIntType(JSONValue *val) | |
{ | |
return val != NULL && val.isNumber() && ((JSONNumber*)val).isIntType(); | |
} | |
static bool isArray(JSONValue *val) | |
{ | |
return val != NULL && val.isArray(); | |
} | |
static bool isObject(JSONValue *val) | |
{ | |
return val != NULL && val.isObject(); | |
} | |
static bool isNull(JSONValue *val) | |
{ | |
return val != NULL && val.isNull(); | |
} | |
int get_char_position() | |
{ | |
return _pos; | |
} | |
int get_line() | |
{ | |
return _line; | |
} | |
int get_column() | |
{ | |
return _column; | |
} | |
}; | |
// ----------------------------------------- | |
/// Class to represent a JSON String | |
class JSONString : public JSONValue { | |
private: | |
string _string; | |
public: | |
JSONString(string s, int pos=0, int line=0, int column=0) : JSONValue(pos, line, column) | |
{ | |
setString(s); | |
setType(JSON_STRING); | |
} | |
JSONString(int pos=0, int line=0, int column=0) : JSONValue(pos, line, column) | |
{ | |
setType(JSON_STRING); | |
} | |
string getString() { return _string; } | |
void setString(string v) { _string = v; } | |
string toString() | |
{ | |
ushort uc; | |
int slen=StringLen(_string), clen=0, i; | |
for(i=0; i<slen; i++) | |
{ | |
uc = StringGetCharacter(_string, i); | |
if(uc==0x22 /* " */ || uc==0x5C /* \ */) | |
{ | |
clen += 2; // \" or \\ | |
} | |
else if(uc>=0x20) | |
{ | |
clen++; | |
} | |
else if(uc>=0x08 && uc<=0x0D && uc!=0x0B) | |
{ | |
/* backspace U+0008 */ | |
/* tab U+0009 */ | |
/* line feed U+000A */ | |
/* form feed U+000C */ | |
/* carriage return U+000D */ | |
clen += 2; | |
} | |
else | |
{ | |
clen += 6; // Unicode-Escape in the Basic Multilingual Plane (U+0000 bis U+FFFF), e.G. \u005C | |
} | |
} | |
if(clen==slen) | |
{ | |
return "\"" + _string + "\""; | |
} | |
string str; | |
StringInit(str, clen); | |
clen=0; | |
for(i=0; i<slen; i++) | |
{ | |
uc = StringGetCharacter(_string, i); | |
if(uc==0x22 /* " */ || uc==0x5C /* \ */) | |
{ | |
StringSetCharacter(str, clen++, 0x5C /* \ */); | |
StringSetCharacter(str, clen++, uc); | |
} | |
else if(uc>=0x20) | |
{ | |
StringSetCharacter(str, clen++, uc); | |
} | |
else if(uc>=0x08 && uc<=0x0D && uc!=0x0B) | |
{ | |
StringSetCharacter(str, clen++, 0x5C /* \ */); | |
if(0x08==uc) | |
{ /* backspace U+0008 */ | |
StringSetCharacter(str, clen++, 'b'); | |
} | |
else if(0x09==uc) | |
{ /* tab U+0009 */ | |
StringSetCharacter(str, clen++, 't'); | |
} | |
else if(0x0A==uc) | |
{ /* line feed U+000A */ | |
StringSetCharacter(str, clen++, 'n'); | |
} | |
else if(0x0C==uc) | |
{ /* form feed U+000C */ | |
StringSetCharacter(str, clen++, 'f'); | |
} | |
else if(0x0D==uc) | |
{ /* carriage return U+000D */ | |
StringSetCharacter(str, clen++, 'r'); | |
} | |
} | |
else | |
{ | |
StringSetCharacter(str, clen++, 0x5C /* \ */); | |
StringSetCharacter(str, clen++, 0x75 /* u */); | |
for(int j=0; j<4; j++) | |
{ | |
ushort hex = (uc & 0xF000) >> 12; | |
if(hex < 10) | |
{ | |
StringSetCharacter(str, clen++, (ushort)((ushort)'0' + hex)); | |
} | |
else | |
{ | |
StringSetCharacter(str, clen++, (ushort)(((ushort)'A' - 10) + hex)); | |
} | |
uc = (uc & 0x0FFF) << 4; | |
} | |
} | |
} | |
return "\"" + str +"\""; | |
} | |
}; | |
// ----------------------------------------- | |
/// Class to represent a JSON Bool | |
class JSONBool : public JSONValue { | |
private: | |
bool _bool; | |
public: | |
JSONBool(bool b, int pos=0, int line=0, int column=0) : JSONValue(pos, line, column) | |
{ | |
setBool(b); | |
setType(JSON_BOOL); | |
} | |
JSONBool(int pos=0, int line=0, int column=0) : JSONValue(pos, line, column) | |
{ | |
setType(JSON_BOOL); | |
} | |
bool getBool() { return _bool; } | |
void setBool(bool v) { _bool = v; } | |
string toString() { return (string)_bool; } | |
}; | |
// ----------------------------------------- | |
/// A JSON number may be internall replresented as either an MQL4 double or a long depending on how it was parsed. | |
/// If one type is set the other is zeroed. | |
class JSONNumber : public JSONValue { | |
private: | |
long _long; | |
double _dbl; | |
bool type_is_double; | |
public: | |
JSONNumber(long l, int pos=0, int line=0, int column=0) : JSONValue(pos, line, column) | |
{ | |
_long = l; | |
_dbl = 0; | |
type_is_double = false; | |
setType(JSON_NUMBER); | |
} | |
JSONNumber(double d, int pos=0, int line=0, int column=0) : JSONValue(pos, line, column) | |
{ | |
_long = 0; | |
_dbl = d; | |
type_is_double = true; | |
setType(JSON_NUMBER); | |
} | |
/// Get the long value, (cast) from internal double if necessary. | |
long getLong() { | |
if(type_is_double) { | |
return (long)_dbl; | |
} else { | |
return _long; | |
} | |
} | |
/// Get the int value, (cast) from internal value. | |
int getInt() { | |
if (type_is_double) { | |
return (int)_dbl; | |
} else { | |
return (int)_long; | |
} | |
} | |
/// Get the double value, (cast) from internal long if necessary. | |
double getDouble() | |
{ | |
if (!type_is_double) { | |
return (double)_long; | |
} else { | |
return _dbl; | |
} | |
} | |
bool isIntType() { // Is int type (e.g. parsed from 5 but not from 5.0) ? | |
return false==type_is_double && _long==((long)((int)_long)); | |
} | |
bool isInt() { // Is int type or convertible lossless to int ? | |
return isIntType() || (true==type_is_double && _dbl==((double)((int)_dbl))); | |
} | |
bool isLongType() { // Is long type (e.g. parsed from 5 but not from 5.0) ? | |
return false==type_is_double; | |
} | |
bool isLong() { // Is long type or convertible lossless to long ? | |
return false==type_is_double || (true==type_is_double && _dbl==((double)((long)_dbl))); | |
} | |
bool isDouble() { // Is double type or convertible lossless to double ? | |
// Returns true in most cases, but for large long numbers a conversion to double may not be lossless | |
// since the number of mantissa bits of a double is less than 64, but a long type has 64 bits. | |
return type_is_double || _long==((long)((double)_long)); | |
} | |
bool isDoubleType() { // Is double type (e.g. parsed from 5.0 but not from 5) ? | |
return type_is_double; | |
} | |
string toString() { | |
if (type_is_double) | |
{ | |
string str = (string)_dbl; | |
if(StringFind(str, ".")>=0 || StringFind(str, "E")>=0 || StringFind(str, "e")>=0) | |
{ | |
if(StringLen(str)>=2 && (StringGetCharacter(str, 0)=='+' || StringGetCharacter(str, 0)=='-') && StringGetCharacter(str, 1)=='.') | |
{ | |
str = StringSubstr(str, 0, 1) + "0" + StringSubstr(str, 1); // JSON does not allow the notation e.g. .5 instead of 0.5 . | |
} | |
else if(StringLen(str)>=1 && StringGetCharacter(str, 0)=='.') | |
{ | |
str = "0" + str; // JSON does not allow the notation e.g. .5 instead of 0.5 . | |
} | |
return str; | |
} | |
else | |
{ | |
return str + ".0"; | |
} | |
} | |
else | |
{ | |
return (string)_long; | |
} | |
} | |
}; | |
// ----------------------------------------- | |
/// This class should not be necessary, but null is genrally infrequent so | |
/// I havent bothered to code it away yet. | |
class JSONNull : public JSONValue { | |
public: | |
JSONNull(int pos=0, int line=0, int column=0) : JSONValue(pos, line, column) | |
{ | |
setType(JSON_NULL); | |
} | |
~JSONNull() {} | |
string toString() | |
{ | |
return "null"; | |
} | |
}; | |
//forward declaration | |
class JSONArray ; | |
/// This represents a JSONObject which is represented internally as a Hash | |
class JSONObject : public JSONValue { | |
private: | |
Hash *_hash; | |
public: | |
JSONObject(int pos=0, int line=0, int column=0) : JSONValue(pos, line, column) | |
{ | |
setType(JSON_OBJECT); | |
_hash = NULL; | |
} | |
~JSONObject() | |
{ | |
if (_hash != NULL) delete _hash; | |
} | |
/// Lookup key and get associated string value - halt program if wrong type(cast error) or doesnt exist(null pointer) | |
string getString(string key) | |
{ | |
return getValue(key).getString(); | |
} | |
/// Lookup key and get associated bool value - halt program if wrong type(cast error) or doesnt exist(null pointer) | |
bool getBool(string key) | |
{ | |
return getValue(key).getBool(); | |
} | |
/// Lookup key and get associated double value - halt program if wrong type(cast error) or doesnt exist(null pointer) | |
double getDouble(string key) | |
{ | |
return getValue(key).getDouble(); | |
} | |
/// Lookup key and get associated long value - halt program if wrong type(cast error) or doesnt exist(null pointer) | |
long getLong(string key) | |
{ | |
return getValue(key).getLong(); | |
} | |
/// Lookup key and get associated int value - halt program if wrong type(cast error) or doesnt exist(null pointer) | |
int getInt(string key) | |
{ | |
return getValue(key).getInt(); | |
} | |
bool isString(string key) | |
{ | |
return isString(getValue(key)); | |
} | |
bool isNumber(string key) | |
{ | |
return isNumber(getValue(key)); | |
} | |
bool isDouble(string key) | |
{ | |
return isDouble(getValue(key)); | |
} | |
bool isInt(string key) | |
{ | |
return isInt(getValue(key)); | |
} | |
bool isIntType(string key) | |
{ | |
return isIntType(getValue(key)); | |
} | |
bool isLong(string key) | |
{ | |
return isLong(getValue(key)); | |
} | |
bool isLongType(string key) | |
{ | |
return isLongType(getValue(key)); | |
} | |
bool isBool(string key) | |
{ | |
return isBool(getValue(key)); | |
} | |
bool isArray(string key) | |
{ | |
return isArray(getValue(key)); | |
} | |
bool isObject(string key) | |
{ | |
return isObject(getValue(key)); | |
} | |
bool isNull(string key) | |
{ | |
return isNull(getValue(key)); | |
} | |
/// Lookup key and get associated string value, return false if failure. | |
bool getString(string key,string &out) | |
{ | |
return getString(getValue(key),out); | |
} | |
/// Lookup key and get associated bool value, return false if failure. | |
bool getBool(string key,bool &out) | |
{ | |
return getBool(getValue(key),out); | |
} | |
/// Lookup key and get associated double value, return false if failure. | |
bool getDouble(string key,double &out) | |
{ | |
return getDouble(getValue(key),out); | |
} | |
/// Lookup key and get associated long value, return false if failure. | |
bool getLong(string key,long &out) | |
{ | |
return getLong(getValue(key),out); | |
} | |
bool getLongType(string key,long &out) | |
{ | |
return getLongType(getValue(key),out); | |
} | |
/// Lookup key and get associated int value, return false if failure. | |
bool getInt(string key,int &out) | |
{ | |
return getInt(getValue(key),out); | |
} | |
bool getIntType(string key,int &out) | |
{ | |
return getIntType(getValue(key),out); | |
} | |
/// Lookup key and get associated array, NULL if not present. Cast failure if not an Array. | |
JSONArray *getArray(string key) | |
{ | |
return getValue(key); | |
} | |
/// Lookup key and get associated Object, NULL if not present. Cast failure if not an Object. | |
JSONObject *getObject(string key) | |
{ | |
return getValue(key); | |
} | |
/// Lookup key and get associated value - best for data whose structure might change as any type can safely be returned. | |
JSONValue *getValue(string key) | |
{ | |
if (_hash == NULL) { | |
return NULL; | |
} | |
return (JSONValue*)_hash.hGet(key); | |
} | |
/// Store the value against the specified key string - Used by the parser. | |
void put(string key,JSONValue *v) | |
{ | |
if (_hash == NULL) _hash = new Hash(); | |
_hash.hPut(key,v); | |
} | |
string toString() { | |
string s = "{"; | |
if (_hash != NULL) { | |
HashLoop *l; | |
int n=0; | |
for(l = new HashLoop(_hash) ; l.isValid() ; l.next() ) { | |
JSONValue *v = (JSONValue *)(l.val()); | |
s = s + (++n==1?"":",") + "\"" + l.key() + "\" : " + v.toString(); | |
} | |
delete l; | |
} | |
s = s + "}"; | |
return s; | |
} | |
/// Return the internal Hash - Used by JSONIterator | |
Hash *getHash() { | |
return _hash; | |
} | |
}; | |
/// This is a JSONArray which is represented internally as a MQL4 dynamic array of JSONValue * | |
class JSONArray : public JSONValue { | |
private: | |
int _size; | |
JSONValue *_array[]; | |
public: | |
JSONArray(int pos=0, int line=0, int column=0) : JSONValue(pos, line, column) | |
{ | |
setType(JSON_ARRAY); | |
_size = 0; | |
} | |
~JSONArray() { | |
// clean up array | |
for(int i = ArrayRange(_array,0)-1 ; i >= 0 ; i-- ) { | |
if (CheckPointer(_array[i]) == POINTER_DYNAMIC ) delete _array[i]; | |
} | |
} | |
// Getters for Objects (key lookup ) -------------------------------------- | |
/// Lookup string value by array index - halt program if wrong type(cast error) or doesnt exist(null pointer) | |
string getString(int index) | |
{ | |
return getValue(index).getString(); | |
} | |
/// Lookup bool value by array index - halt program if wrong type(cast error) or doesnt exist(null pointer) | |
bool getBool(int index) | |
{ | |
return getValue(index).getBool(); | |
} | |
/// Lookup double value by array index - halt program if wrong type(cast error) or doesnt exist(null pointer) | |
double getDouble(int index) | |
{ | |
return getValue(index).getDouble(); | |
} | |
/// Lookup long value by array index - halt program if wrong type(cast error) or doesnt exist(null pointer) | |
long getLong(int index) | |
{ | |
return getValue(index).getLong(); | |
} | |
/// Lookup int value by array index - halt program if wrong type(cast error) or doesnt exist(null pointer) | |
int getInt(int index) | |
{ | |
return getValue(index).getInt(); | |
} | |
bool isString(int index) | |
{ | |
return isString(getValue(index)); | |
} | |
bool isNumber(int index) | |
{ | |
return isNumber(getValue(index)); | |
} | |
bool isDouble(int index) | |
{ | |
return isDouble(getValue(index)); | |
} | |
bool isInt(int index) | |
{ | |
return isInt(getValue(index)); | |
} | |
bool isIntType(int index) | |
{ | |
return isIntType(getValue(index)); | |
} | |
bool isLong(int index) | |
{ | |
return isLong(getValue(index)); | |
} | |
bool isLongType(int index) | |
{ | |
return isLongType(getValue(index)); | |
} | |
bool isBool(int index) | |
{ | |
return isBool(getValue(index)); | |
} | |
bool isArray(int index) | |
{ | |
return isArray(getValue(index)); | |
} | |
bool isObject(int index) | |
{ | |
return isObject(getValue(index)); | |
} | |
bool isNull(int index) | |
{ | |
return isNull(getValue(index)); | |
} | |
/// Lookup JSONString by array index. NULL if not present. Cast failure if not an Object. | |
bool getString(int index,string &out) | |
{ | |
return getString(getValue(index),out); | |
} | |
/// Lookup JSONBool by array index. NULL if not present. Cast failure if not an Object. | |
bool getBool(int index,bool &out) | |
{ | |
return getBool(getValue(index),out); | |
} | |
/// Lookup JSONNumber by array index. NULL if not present. Cast failure if not an Object. | |
bool getDouble(int index,double &out) | |
{ | |
return getDouble(getValue(index),out); | |
} | |
/// Lookup JSONNumber by array index. NULL if not present. Cast failure if not an Object. | |
bool getLong(int index,long &out) | |
{ | |
return getLong(getValue(index),out); | |
} | |
bool getLongType(int index,long &out) | |
{ | |
return getLongType(getValue(index),out); | |
} | |
/// Lookup JSONNumber by array index. NULL if not present. Cast failure if not an Object. | |
bool getInt(int index,int &out) | |
{ | |
return getInt(getValue(index),out); | |
} | |
bool getIntType(int index,int &out) | |
{ | |
return getIntType(getValue(index),out); | |
} | |
/// Lookup array child by index, NULL if not present. Cast failure if not an Array. | |
JSONArray *getArray(int index) | |
{ | |
return getValue(index); | |
} | |
/// Lookup object child by index, NULL if not present. Cast failure if not an Array. | |
JSONObject *getObject(int index) | |
{ | |
return getValue(index); | |
} | |
/// The following method allows any type to be returned. Use this when parsing unpredictable data | |
JSONValue *getValue(int index) | |
{ | |
if(index<0 || index>=_size) | |
{ | |
return NULL; | |
} | |
else | |
{ | |
return _array[index]; | |
} | |
} | |
/// Used by the Parser when building the array | |
bool put(int index, JSONValue *v) | |
{ | |
if (index >= _size) { | |
int oldSize = _size; | |
int newSize = ArrayResize(_array, index+1, (index+(1+3)) / 4); | |
if (newSize <= index) return false; | |
_size = newSize; | |
// initialise | |
for(int i = oldSize ; i< newSize ; i++ ) _array[i] = NULL; | |
} | |
// Delete old entry if any | |
if (_array[index] != NULL) delete _array[index]; | |
//set new entry | |
_array[index] = v; | |
return true; | |
} | |
string toString() { | |
string s = "["; | |
if (_size > 0) { | |
s = s + _array[0].toString(); | |
for(int i = 1 ; i< _size ; i++ ) { | |
s = s + "," + _array[i].toString(); | |
} | |
} | |
s = s + "]"; | |
return s; | |
} | |
int size() { | |
return _size; | |
} | |
}; | |
/// Parse JSON text using a simple recursive descent parser | |
/// Exmaple | |
/// | |
/// <pre> | |
/// string s = "{ \"firstName\": \"John\","+ | |
/// " \"lastName\": \"Smith\","+ | |
/// " \"age\": 25,"+ | |
/// " \"address\": { \"streetAddress\": \"21 2nd Street\", \"city\": \"New York\", \"state\": \"NY\", \"postalCode\": \"10021\" },"+ | |
/// " \"phoneNumber\": [ { \"type\": \"home\", \"number\": \"212 555-1234\" }, { \"type\": \"fax\", \"number\": \"646 555-4567\" } ],"+ | |
/// " \"gender\":{ \"type\":\"male\" } }"; | |
/// | |
/// JSONParser *parser = new JSONParser(); | |
/// | |
/// JSONValue *jv = parser.parse(s); | |
/// | |
/// if (jv == NULL) { | |
/// | |
/// Print("error:"+(string)parser.getErrorCode()+parser.getErrorMessage()); | |
/// | |
/// } else { | |
/// | |
/// Print("PARSED:"+jv.toString()); | |
/// | |
/// if (jv.isObject()) { | |
/// | |
/// JSONObject *jo = jv; | |
/// | |
/// // Direct access - will throw null pointer if wrong getter used. | |
/// | |
/// Print("firstName:" + jo.getString("firstName")); | |
/// Print("city:" + jo.getObject("address").getString("city")); | |
/// Print("phone:" + jo.getArray("phoneNumber").getObject(0).getString("number")); | |
/// | |
/// // Safe access in case JSON data is missing or different. | |
/// | |
/// if (jo.getString("firstName",s) ) Print("firstName = "+s); | |
/// | |
/// // Loop over object returning JSONValue | |
/// | |
/// JSONIterator *it = new JSONIterator(jo); | |
/// for( ; it.isValid() ; it.next()) { | |
/// Print("loop:"+it.key()+" = "+it.val().toString()); | |
/// } | |
/// delete it; | |
/// } | |
/// delete jv; | |
/// } | |
/// delete parser; | |
/// </pre> | |
class JSONParser { | |
private: | |
/// Current parse position | |
int _pos; | |
/// The input string is expanded into an array of ushort (wchar) | |
ushort _in[]; | |
/// Length of string | |
int _len; | |
/// The original input string | |
string _instr; | |
int _tab_size; | |
int _errCode; | |
string _errMsg; | |
int _line; | |
int _column; | |
int _begin_object_pos; | |
int _begin_object_line; | |
int _begin_object_column; | |
void setError(int code=1,string msg="") { | |
_errCode |= code; | |
if(msg != "") | |
{ | |
if (_errMsg == "") { | |
_errMsg = "JSONParser::Error " + msg; | |
} else { | |
_errMsg = _errMsg + "\n" + msg; | |
} | |
} | |
} | |
/// Parse a JSON Object | |
JSONObject *parseObject() | |
{ | |
JSONObject *o = new JSONObject(_pos, _line, _column); | |
skipSpace(); | |
if (expect('{')) { | |
while (_errCode == 0) { | |
skipSpace(); | |
if(_pos >= _len) | |
{ | |
setError(5, "unexpected end of file while parsing a JSONObject"); | |
break; | |
} | |
if (_in[_pos] != '"') | |
{ | |
if(!expect('}')) { | |
setError(2,"expected \" or } "); | |
} | |
break; | |
} | |
// Read the key | |
string key = parseString(); | |
if (_errCode != 0 || key == NULL) | |
break; | |
skipSpace(); | |
if (!expect(':')) | |
{ | |
setError(2, "expected : "); | |
break; | |
} | |
// read the value | |
JSONValue *v = parseValue(); | |
if (_errCode != 0 ) break; | |
o.put(key,v); | |
skipSpace(); | |
if (!expectOptional(',')) | |
{ | |
if(!expect('}')) { | |
setError(2,"expected , or } "); | |
} | |
break; | |
} | |
} | |
} | |
if (_errCode != 0) { | |
delete o; | |
o = NULL; | |
} | |
return o; | |
} | |
bool isDigit0to9(ushort c) { | |
return (c >= '0' && c <= '9' ); | |
} | |
void skipSpace() { | |
for( ; _pos<_len ; _pos++) | |
{ | |
ushort c = _in[_pos]; | |
if(c == '\n') | |
{ | |
_line++; | |
_column = 1; | |
} | |
else if(c == ' ') | |
{ | |
_column++; | |
} | |
else if(c == '\t') | |
{ | |
_column += _tab_size - ((_column-1) % _tab_size); | |
} | |
else if(c != '\r') | |
{ | |
break; | |
} | |
} | |
} | |
bool expect(ushort c) | |
{ | |
if(_pos >= _len) | |
{ | |
setError(1, "expected " + | |
ShortToString(c) + " (" + (string)c + ")" + | |
" got end of file"); | |
return false; | |
} | |
else if (c == _in[_pos]) { | |
_pos++; | |
_column++; | |
return true; | |
} else { | |
setError(1, ""); | |
return false; | |
} | |
} | |
bool expectOptional(ushort c) | |
{ | |
bool ret=false; | |
if (_pos < _len && c == _in[_pos]) { | |
_pos++; | |
_column++; | |
ret = true; | |
} | |
return ret; | |
} | |
string parseString() | |
{ | |
_begin_object_pos = _pos; | |
_begin_object_line = _line; | |
_begin_object_column = _column; | |
string ret = ""; | |
if(expect('"')) { | |
while(true) { | |
int end=_pos; | |
ushort c; | |
for( ; end < _len && (c=_in[end]) != '"' && c != '\\' ; end++) | |
{ | |
#ifdef TOLERATE_TAB_CR_NL_IN_STRINGS | |
// According to the JSON standard, control characters like \r, \n, \t are not allowed inside | |
// strings. But if TOLERATE_TAB_CR_NL_IN_STRINGS is #defined, they are tolerated rather than raising an error. | |
if(c == '\n') | |
{ | |
_line++; | |
_column = 1; | |
} | |
else if(c == '\t') | |
{ | |
_column += _tab_size - ((_column-1) % _tab_size); | |
} | |
else if(c != '\r') | |
{ | |
_column++; | |
} | |
#else | |
if(c < 0x20) | |
{ | |
setError(4,"Control characters not allowed in JSON strings. Found character with decimal ASCII code " + (string)c + " ."); | |
_pos = end; | |
break; | |
} | |
else | |
{ | |
_column++; | |
} | |
#endif | |
} | |
if(_errCode != 0) break; | |
if (end >= _len) { | |
setError(5, "unexpected end of file while parsing a string"); | |
_pos = end; | |
break; | |
} | |
// Check if character was escaped. | |
// TODO \" \\ \/ \b \f \n \r \t \u0000 | |
if (c == '\\') { | |
// Add partial string and get more | |
if(end>_pos) | |
{ | |
// If end==_pos, the following statement must not be executed because | |
// StringSubstr() would not insert an empty string - as one could believe. | |
// Instead, StringSubstr() would insert the whole rest part of _instr beginning from | |
// the position _pos. | |
ret = ret + StringSubstr(_instr,_pos,end-_pos); | |
} | |
end++; | |
_column++; | |
if (end >= _len) { | |
_pos = end; | |
setError(5, "unexpected end of file after escape \\ inside of string"); | |
break; | |
} else { | |
c = 0; | |
int nrdigit; | |
switch(_in[end]) { | |
case '"': | |
case '\\': | |
case '/': | |
c = _in[end]; | |
break; | |
case 'b': c = 8; break; // backspace - 8 | |
case 'f': c = 12; break; // form feed 12 | |
case 'n': c = '\n'; break; | |
case 'r': c = '\r'; break; | |
case 't': c = '\t'; break; | |
case 'u': | |
for(nrdigit=0, c=0; nrdigit<4; nrdigit++) | |
{ | |
end++; | |
_column++; | |
if (end >= _len) { | |
_pos = end; | |
setError(5, "unexpected end of file after escape \\u inside of string"); | |
break; | |
} | |
if(_in[end]>='0' && _in[end]<='9') | |
{ | |
c = c*16 + (_in[end]-'0'); | |
} | |
else if(_in[end]>='a' && _in[end]<='f') | |
{ | |
c = c*16 + (_in[end]-'a' + 10); | |
} | |
else if(_in[end]>='A' && _in[end]<='F') | |
{ | |
c = c*16 + (_in[end]-'A' + 10); | |
} | |
else | |
{ | |
_pos = end; | |
setError(4,"unicode escape must be followed by four hexadecimal digits"); | |
break; | |
} | |
} | |
break; | |
default: | |
_pos = end; | |
setError(4,"unknown escape"); | |
break; | |
} | |
if (_errCode != 0) break; | |
ret = ret + ShortToString(c); | |
end++; | |
_column++; | |
_pos = end; | |
} | |
} else if (c == '"') { | |
// End of string | |
if(end>_pos) | |
{ | |
// If end==_pos, the following statement must not be executed because | |
// StringSubstr() would not insert an empty string - as one could believe. | |
// Instead, StringSubstr() would insert the whole rest part of _instr beginning from | |
// the position _pos. | |
ret = ret + StringSubstr(_instr,_pos,end-_pos); | |
} | |
end++; | |
_column++; | |
_pos = end; | |
break; | |
} | |
} | |
} | |
if (_errCode != 0) { | |
ret = NULL; | |
} | |
return ret; | |
} | |
JSONValue *parseValue() | |
{ | |
JSONValue *ret = NULL; | |
skipSpace(); | |
string s; | |
if(_pos >= _len) | |
{ | |
setError(5, "unexpected end of file while parsing a JSONValue"); | |
return NULL; | |
} | |
if (_in[_pos] == '[') { | |
ret = (JSONValue*)parseArray(); | |
} else if (_in[_pos] == '{') { | |
ret = (JSONValue*)parseObject(); | |
} else if (_in[_pos] == '"') { | |
s = parseString(); | |
ret = (JSONValue*)new JSONString(s, _begin_object_pos, _begin_object_line, _begin_object_column); | |
} else if (isDigit0to9(_in[_pos]) || _in[_pos]=='+' || _in[_pos]=='-' || _in[_pos]=='.') { | |
bool isDouble = false; | |
long sign; | |
int i; | |
_begin_object_pos = _pos; | |
_begin_object_line = _line; | |
_begin_object_column = _column; | |
if (_in[_pos] == '-') { | |
sign = -1; | |
_pos++; | |
_column++; | |
} else if (_in[_pos] == '+') { | |
sign = 1; | |
_pos++; | |
_column++; | |
} else { | |
sign = 1; | |
} | |
i=_pos; | |
while(i < _len && isDigit0to9(_in[i])) { | |
i++; | |
} | |
if(i < _len && _in[i]=='.') | |
{ | |
isDouble = true; | |
while(++i < _len && isDigit0to9(_in[i])) | |
{ } | |
} | |
if(i < _len && (_in[i]=='e' || _in[i]=='E')) | |
{ | |
isDouble = true; | |
if(++i < _len && (_in[i]=='+' || _in[i]=='-')) | |
i++; | |
if(i >= _len || !isDigit0to9(_in[i])) | |
{ | |
setError(5, "error parsing a number"); | |
return NULL; | |
} | |
while(++i < _len && isDigit0to9(_in[i])) | |
{ } | |
} | |
s = StringSubstr(_instr,_pos,i-_pos); | |
if(isDouble) { | |
double d = sign * StringToDouble(s); | |
ret = (JSONValue*)new JSONNumber(d, _begin_object_pos, _begin_object_line, _begin_object_column); // Create a Number as double only | |
} else { | |
long l = sign * StringToInteger(s); | |
ret = (JSONValue*)new JSONNumber(l, _begin_object_pos, _begin_object_line, _begin_object_column); // Create a Number as a long | |
} | |
_column += i-_pos; | |
_pos = i; | |
} else if (_in[_pos] == 't' && StringSubstr(_instr,_pos,4) == "true") { | |
ret = (JSONValue*)new JSONBool(true, _pos, _line, _column); | |
_pos += 4; | |
} else if (_in[_pos] == 'f' && StringSubstr(_instr,_pos,5) == "false") { | |
ret = (JSONValue*)new JSONBool(false, _pos, _line, _column); | |
_pos += 5; | |
} else if (_in[_pos] == 'n' && StringSubstr(_instr,_pos,4) == "null") { | |
ret = (JSONValue*)new JSONNull(_pos, _line, _column); | |
_pos += 4; | |
} else { | |
setError(3, "error parsing a JSONValue"); | |
} | |
if (_errCode != 0 && ret != NULL ) { | |
delete ret; | |
ret = NULL; | |
} | |
return ret; | |
} | |
JSONArray *parseArray() | |
{ | |
JSONArray *ret = new JSONArray(_pos, _line, _column); | |
int index = 0; | |
skipSpace(); | |
if (expect('[')) { | |
skipSpace(); | |
if (_pos >= _len) { | |
setError(5, "unexpected end of file while parsing a JSONArray"); | |
delete ret; | |
return NULL; | |
} | |
if(!expectOptional(']')) { | |
while (_errCode == 0) { | |
// read the value | |
JSONValue *v = parseValue(); | |
if (_errCode != 0) break; | |
if (!ret.put(index++,v)) { | |
setError(3,"memory error adding "+(string)index); | |
break; | |
} | |
skipSpace(); | |
if (!expectOptional(',')) | |
{ | |
if(!expect(']')) { | |
setError(2,"JSONArray: expected , or ] "); | |
} | |
break; | |
} | |
skipSpace(); | |
} | |
} | |
} | |
if (_errCode != 0 ) { | |
delete ret; | |
ret = NULL; | |
} | |
return ret; | |
} | |
public: | |
int getErrorCode() | |
{ | |
return _errCode; | |
} | |
string getErrorMessage() | |
{ | |
return _errMsg; | |
} | |
int get_char_position() | |
{ | |
return _pos; | |
} | |
int get_line() | |
{ | |
return _line; | |
} | |
int get_column() | |
{ | |
return _column; | |
} | |
JSONParser(int tab_size=3) | |
{ // tab_size is only used for counting columns when there is a TAB in the JSON code. | |
// The column number can be read out with get_column() in case of an error (when parse() returns NULL). | |
_tab_size = tab_size; | |
} | |
/// Parse a sequnce of characters and return a JSONValue. | |
JSONValue *parse( | |
string s ///< Serialized JSON text | |
) | |
{ | |
int inLen; | |
JSONValue *ret = NULL; | |
_instr = s; | |
_len = StringToShortArray(_instr,_in); // nul '0' is added to length | |
_pos = 0; | |
_line = 1; | |
_column = 1; | |
_errCode = 0; | |
_errMsg = ""; | |
inLen = StringLen(_instr); | |
if (_len != inLen + 1 /* nul */ ) { | |
setError(1, "unable to create array " + (string)inLen + " got " + (string)_len); | |
} else { | |
_len --; | |
ret = parseValue(); | |
if (_errCode != 0) { | |
_errMsg = _errMsg + " in line " + (string)_line + " column " + (string)_column + " [" + StringSubstr(_instr,_pos,10) + "...]"; | |
} | |
} | |
return ret; | |
} | |
}; | |
/// Class to iterate over a JSONObject (not a JSONArray) | |
class JSONIterator { | |
private: | |
HashLoop * _l; | |
public: | |
// Create iterator and move to first item | |
JSONIterator(JSONObject *jo) | |
{ | |
_l = new HashLoop(jo.getHash()); | |
} | |
~JSONIterator() | |
{ | |
delete _l; | |
} | |
// Check if current item is valid and so the function | |
// val() will not return NULL. | |
bool isValid() | |
{ | |
return _l.isValid(); | |
} | |
// Check if current item is valid and so the function | |
// val() will not return NULL. | |
// Deprecated since the name hasNext is misleading | |
bool hasNext() | |
{ | |
return _l.isValid(); | |
} | |
// Move to next item | |
void next() { | |
_l.next(); | |
} | |
// Return item | |
JSONValue *val() | |
{ | |
return (JSONValue *) (_l.val()); | |
} | |
// Return key | |
string key() | |
{ | |
return _l.key(); | |
} | |
}; | |
/* | |
void json_demo() | |
{ | |
string s = "{ \"firstName\": \"John\","+ | |
" \"lastName\": \"Smith\","+ | |
" \"age\": 25,"+ | |
" \"address\": { \"streetAddress\": \"21 2nd Street\", \"city\": \"New York\", \"state\": \"NY\", \"postalCode\": \"10021\" },"+ | |
" \"phoneNumber\": [ { \"type\": \"home\", \"number\": \"212 555-1234\" }, { \"type\": \"fax\", \"number\": \"646 555-4567\" } ],"+ | |
" \"gender\":{ \"type\":\"male\" } }"; | |
JSONParser *parser = new JSONParser(); | |
JSONValue *jv = parser.parse(s); | |
Print("json:"); | |
if (jv == NULL) { | |
Print("error:"+(string)parser.getErrorCode()+parser.getErrorMessage()); | |
} else { | |
Print("PARSED:"+jv.toString()); | |
if (jv.isObject()) { | |
JSONObject *jo = jv; | |
// Direct access - will throw null pointer if wrong getter used. | |
Print("firstName:" + jo.getString("firstName")); | |
Print("city:" + jo.getObject("address").getString("city")); | |
Print("phone:" + jo.getArray("phoneNumber").getObject(0).getString("number")); | |
// Safe access in case JSON data is missing or different. | |
if (jo.getString("firstName",s) ) Print("firstName = "+s); | |
// Loop over object returning JSONValue | |
JSONIterator *it = new JSONIterator(jo); | |
for( ; it.isValid() ; it.next()) { | |
Print("loop:"+it.key()+" = "+it.val().toString()); | |
} | |
delete it; | |
} | |
delete jv; | |
} | |
delete parser; | |
} | |
*/ | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment