diff --git a/libresapi/src/api/json.cpp b/libresapi/src/api/json.cpp index c0981d903..23984f1cb 100644 --- a/libresapi/src/api/json.cpp +++ b/libresapi/src/api/json.cpp @@ -1,3 +1,5 @@ +#define nullptr 0 + #include "json.h" #include #include @@ -9,6 +11,7 @@ #include #include #include +#include #ifndef WIN32 #define _stricmp strcasecmp @@ -108,37 +111,49 @@ Value& Value::operator =(const Value& v) Value& Value::operator [](size_t idx) { - assert(mValueType == ArrayVal); + if (mValueType != ArrayVal) + throw std::runtime_error("json mValueType==ArrayVal required"); + return mArrayVal[idx]; } const Value& Value::operator [](size_t idx) const { - assert(mValueType == ArrayVal); + if (mValueType != ArrayVal) + throw std::runtime_error("json mValueType==ArrayVal required"); + return mArrayVal[idx]; } Value& Value::operator [](const std::string& key) { - assert(mValueType == ObjectVal); + if (mValueType != ObjectVal) + throw std::runtime_error("json mValueType==ObjectVal required"); + return mObjectVal[key]; } Value& Value::operator [](const char* key) { - assert(mValueType == ObjectVal); + if (mValueType != ObjectVal) + throw std::runtime_error("json mValueType==ObjectVal required"); + return mObjectVal[key]; } const Value& Value::operator [](const char* key) const { - assert(mValueType == ObjectVal); + if (mValueType != ObjectVal) + throw std::runtime_error("json mValueType==ObjectVal required"); + return mObjectVal[key]; } const Value& Value::operator [](const std::string& key) const { - assert(mValueType == ObjectVal); + if (mValueType != ObjectVal) + throw std::runtime_error("json mValueType==ObjectVal required"); + return mObjectVal[key]; } @@ -157,22 +172,140 @@ size_t Value::size() const bool Value::HasKey(const std::string &key) const { - assert(mValueType == ObjectVal); + if (mValueType != ObjectVal) + throw std::runtime_error("json mValueType==ObjectVal required"); + return mObjectVal.HasKey(key); } int Value::HasKeys(const std::vector &keys) const { - assert(mValueType == ObjectVal); + if (mValueType != ObjectVal) + throw std::runtime_error("json mValueType==ObjectVal required"); + return mObjectVal.HasKeys(keys); } int Value::HasKeys(const char **keys, int key_count) const { - assert(mValueType == ObjectVal); + if (mValueType != ObjectVal) + throw std::runtime_error("json mValueType==ObjectVal required"); + return mObjectVal.HasKeys(keys, key_count); } +int Value::ToInt() const +{ + if (!IsNumeric()) + throw std::runtime_error("json mValueType==IsNumeric() required"); + + return mIntVal; +} + +float Value::ToFloat() const +{ + if (!IsNumeric()) + throw std::runtime_error("json mValueType==IsNumeric() required"); + + return mFloatVal; +} + +double Value::ToDouble() const +{ + if (!IsNumeric()) + throw std::runtime_error("json mValueType==IsNumeric() required"); + + return mDoubleVal; +} + +bool Value::ToBool() const +{ + if (mValueType != BoolVal) + throw std::runtime_error("json mValueType==BoolVal required"); + + return mBoolVal; +} + +const std::string& Value::ToString() const +{ + if (mValueType != StringVal) + throw std::runtime_error("json mValueType==StringVal required"); + + return mStringVal; +} + +Object Value::ToObject() const +{ + if (mValueType != ObjectVal) + throw std::runtime_error("json mValueType==ObjectVal required"); + + return mObjectVal; +} + +Array Value::ToArray() const +{ + if (mValueType != ArrayVal) + throw std::runtime_error("json mValueType==ArrayVal required"); + + return mArrayVal; +} + +Value::operator int() const +{ + if (!IsNumeric()) + throw std::runtime_error("json mValueType==IsNumeric() required"); + + return mIntVal; +} + +Value::operator float() const +{ + if (!IsNumeric()) + throw std::runtime_error("json mValueType==IsNumeric() required"); + + return mFloatVal; +} + +Value::operator double() const +{ + if (!IsNumeric()) + throw std::runtime_error("json mValueType==IsNumeric() required"); + + return mDoubleVal; +} + +Value::operator bool() const +{ + if (mValueType != BoolVal) + throw std::runtime_error("json mValueType==BoolVal required"); + + return mBoolVal; +} + +Value::operator std::string() const +{ + if (mValueType != StringVal) + throw std::runtime_error("json mValueType==StringVal required"); + + return mStringVal; +} + +Value::operator Object() const +{ + if (mValueType != ObjectVal) + throw std::runtime_error("json mValueType==ObjectVal required"); + + return mObjectVal; +} + +Value::operator Array() const +{ + if (mValueType != ArrayVal) + throw std::runtime_error("json mValueType==ArrayVal required"); + + return mArrayVal; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// Array::Array() @@ -420,7 +553,7 @@ std::string json::Serialize(const Value& v) { str = "{"; Object obj = v.ToObject(); - for (Object::ValueMap::const_iterator it = obj.begin(); it != obj.end(); it++) + for (Object::ValueMap::const_iterator it = obj.begin(); it != obj.end(); ++it) { if (!first) str += std::string(","); @@ -435,7 +568,7 @@ std::string json::Serialize(const Value& v) { str = "["; Array a = v.ToArray(); - for (Array::ValueVector::const_iterator it = a.begin(); it != a.end(); it++) + for (Array::ValueVector::const_iterator it = a.begin(); it != a.end(); ++it) { if (!first) str += std::string(","); @@ -447,7 +580,8 @@ std::string json::Serialize(const Value& v) str += "]"; } - //else error + // else it's not valid JSON, as a JSON data structure must be an array or an object. We'll return an empty string. + return str; } @@ -556,7 +690,7 @@ static std::string UnescapeJSONString(const std::string& str) { std::string s = ""; - for (int i = 0; i < str.length(); i++) + for (std::string::size_type i = 0; i < str.length(); i++) { char c = str[i]; if ((c == '\\') && (i + 1 < str.length())) @@ -577,7 +711,7 @@ static std::string UnescapeJSONString(const std::string& str) case 'f' : s.push_back('\f'); break; case 'u' : skip_ahead = 5; hex_str = str.substr(i + 4, 2); - hex = (unsigned int)std::strtoul(hex_str.c_str(), NULL, 16); + hex = (unsigned int)std::strtoul(hex_str.c_str(), nullptr, 16); s.push_back((char)hex); break; @@ -605,6 +739,7 @@ static Value DeserializeValue(std::string& str, bool* had_error, std::stack= (double)INT_MIN) && (tmp_val <= (double)INT_MAX)) - v = Value(atoi(temp_val.c_str())); + char* end_char; + errno = 0; + long int ival = strtol(temp_val.c_str(), &end_char, 10); + if (*end_char != '\0') + { + // invalid character sequence, not a number + *had_error = true; + return Value(); + } + else if ((errno == ERANGE) && ((ival == LONG_MAX) || (ival == LONG_MIN))) + { + // value is out of range for a long int, should be a double then. See if we can convert it correctly. + errno = 0; + double dval = strtod(temp_val.c_str(), &end_char); + if ((errno != 0) || (*end_char != '\0')) + { + // error in conversion or it's too big for a double + *had_error = true; + return Value(); + } + + v = Value(dval); + } + else if ((ival >= INT_MIN) && (ival <= INT_MAX)) + { + // valid integer range + v = Value((int)ival); + } else - v = Value(tmp_val); + { + // probably running on a very old OS since this block implies that long isn't the same size as int. + // int is guaranteed to be at least 16 bits and long 32 bits...however nowadays they're almost + // always the same 32 bit size. But it's possible someone is running this on a very old architecture + // so for correctness, we'll error out here + *had_error = true; + return Value(); + } } str = str.substr(i, str.length()); @@ -715,11 +941,13 @@ static Value DeserializeArray(std::string& str, std::stack& dept str = Trim(str); + // Arrays begin and end with [], so if we don't find one, it's an error if ((str[0] == '[') && (str[str.length() - 1] == ']')) str = str.substr(1, str.length() - 2); else return Value(); + // extract out all values from the array (remember, a value can also be an array or an object) while (str.length() > 0) { std::string tmp; @@ -776,11 +1004,13 @@ static Value DeserializeObj(const std::string& _str, std::stack& std::string str = Trim(_str); + // Objects begin and end with {} so if we don't find a pair, it's an error if ((str[0] != '{') && (str[str.length() - 1] != '}')) return Value(); else str = str.substr(1, str.length() - 2); + // Get all key/value pairs in this object... while (str.length() > 0) { // Get the key name @@ -797,6 +1027,8 @@ static Value DeserializeObj(const std::string& _str, std::stack& bool had_error = false; str = str.substr(colon_idx + 1, str.length()); + + // We have the key, now extract the value from the string obj[key] = DeserializeValue(str, &had_error, depth_stack); if (had_error) return Value(); diff --git a/libresapi/src/api/json.h b/libresapi/src/api/json.h index 9a5b4612b..30b054259 100644 --- a/libresapi/src/api/json.h +++ b/libresapi/src/api/json.h @@ -26,6 +26,55 @@ CHANGELOG: ========== + + 8/31/2014: + --------- + * Fixed bug from last update that broke false/true boolean usage. Courtesy of Vasi B. + * Change postfix increment of iterators in Serialize to prefix, courtesy of Vasi B. + * More improvements to validity checking of non string/object/array types. Should + catch even more invalid usage types such as -1jE5, falsee, trueeeeeee + {"key" : potato} (that should be {"key" : "potato"}), etc. + * Switched to strtol and strtod from atof/atoi in Serialize for better error handling. + * Fix for GCC order of initialization warnings, courtsey of Vasi B. + + 8/17/2014: + ---------- + * Better error handling (and bug fixing) for invalid JSON. Previously, something such as: + {"def": j[{"a": 100}],"abc": 123} + would result in at best, a crash, and at worst, nothing when this was passed to + the Deserialize method. Note that the "j" is invalid in this example. This led + to further fixes for other invalid syntax: + - Use of multiple 'e', for example: 1ee4 is not valid + - Use of '.' when not preceded by a digit is invalid. For example: .1 is + incorrect, but 0.1 is fine. + - Using 'e' when not preceded by a digit. For example, e4 isn't valid but 1e4 is. + + The deserialize method should properly handle these problems and when there's an + error, it returns a Value object with the NULLVal type. Check this type to see + if there's an error. + + Issue reported by Imre Pechan. + + 7/21/2014: + ---------- + * All asserts removed and replaced with exceptions, as per request from many users. + Instead of asserting, functions will throw a std::runtime_error with + appropriate error message. + * Added versions of the Value::To* functions that take a default parameter. + In the event of an error (like calling Value::ToInt() when it's type is an Object), + the default value you specified will be returned. Courtesy of PeterSvP + * Fixed type mismatch warning, courtesy of Per Rovegård + * Initialized some variables in the various Value constructors to defaults for + better support with full blast g++ warnings, courtesy of Mark Odell. + * Changed Value::ToString to return a const std::string& instead of std::string + to avoid unnecessary copying. + * Improved some commenting + * Fixed a bug where a capital E for scientific notation numbers wasn't + recognized, only lowercase e. + * VASTLY OVERHAULED AND IMPROVED THE README FILE, PLEASE CONSULT IT FOR + IN DEPTH USAGE AND EXAMPLES. + + 2/8/2014: --------- MAJOR BUG FIXES, all courtesy of Per Rovegård, Ph.D. @@ -70,7 +119,7 @@ 1/27/2014 ---------- - * Deserialize will now return a NULLType Value instance if there was an + * Deserialize will now return a NULLVal Value instance if there was an error instead of asserting. This way you can handle however you want to invalid JSON being passed in. As a top level object must be either an array or an object, a NULL value return indicates an invalid result. @@ -187,8 +236,10 @@ #include #include #include -#include +#include + +// PLEASE SEE THE README FOR USAGE INFORMATION AND EXAMPLES. Comments will be kept to a minimum to reduce clutter. namespace json { enum ValueType @@ -205,10 +256,15 @@ namespace json class Value; + // Represents a JSON object which is of the form {string:value, string:value, ...} Where string is the "key" name and is + // of the form "" or "characters". Value is either of: string, number, object, array, boolean, null class Object { public: + // This is the type used to store key/value pairs. If you want to get an iterator for this class to iterate over its members, + // use this. + // For example: Object::ValueMap::iterator my_iterator; typedef std::map ValueMap; protected: @@ -229,6 +285,11 @@ namespace json inline friend bool operator <=(const Object& lhs, const Object& rhs) {return !operator>(lhs, rhs);} inline friend bool operator >=(const Object& lhs, const Object& rhs) {return !operator<(lhs, rhs);} + // Just like a std::map, you can get the value for a key by using the index operator. You could also + // use this to insert a value if it doesn't exist, or overwrite it if it does. Example: + // Value my_val = my_object["some key name"]; + // my_object["some key name"] = "overwriting the value with this new string value"; + // my_object["new key name"] = "a new key being inserted"; Value& operator [](const std::string& key); const Value& operator [](const std::string& key) const; Value& operator [](const char* key); @@ -239,11 +300,12 @@ namespace json ValueMap::iterator begin(); ValueMap::iterator end(); - // Find will return end() if the key can't be found, just like std::map does. + // Find will return end() if the key can't be found, just like std::map does. ->first will be the key (a std::string), + // ->second will be the Value. ValueMap::iterator find(const std::string& key); ValueMap::const_iterator find(const std::string& key) const; - // Convenience wrapper to find to search for a key + // Convenience wrapper to search for a key bool HasKey(const std::string& key) const; // Checks if the object contains all the keys in the array. If it does, returns -1. @@ -258,10 +320,14 @@ namespace json }; + // Represents a JSON Array which is of the form [value, value, ...] where value is either of: string, number, object, array, boolean, null class Array { public: + // This is the type used to store values. If you want to get an iterator for this class to iterate over its members, + // use this. + // For example: Array::ValueVector::iterator my_array_iterator; typedef std::vector ValueVector; protected: @@ -305,6 +371,7 @@ namespace json size_t size() const; }; + // Represents a JSON value which is either of: string, number, object, array, boolean, null class Value { protected: @@ -321,18 +388,23 @@ namespace json public: Value() : mValueType(NULLVal), mIntVal(0), mFloatVal(0), mDoubleVal(0), mBoolVal(false) {} - Value(int v) : mValueType(IntVal), mIntVal(v), mFloatVal((float)v), mDoubleVal((double)v) {} - Value(float v) : mValueType(FloatVal), mFloatVal(v), mIntVal((int)v), mDoubleVal((double)v) {} - Value(double v) : mValueType(DoubleVal), mDoubleVal(v), mIntVal((int)v), mFloatVal((float)v) {} - Value(const std::string& v) : mValueType(StringVal), mStringVal(v) {} - Value(const char* v) : mValueType(StringVal), mStringVal(v) {} - Value(const Object& v) : mValueType(ObjectVal), mObjectVal(v) {} - Value(const Array& v) : mValueType(ArrayVal), mArrayVal(v) {} - Value(const bool v) : mValueType(BoolVal), mBoolVal(v) {} + Value(int v) : mValueType(IntVal), mIntVal(v), mFloatVal((float)v), mDoubleVal((double)v), mBoolVal(false) {} + Value(float v) : mValueType(FloatVal), mIntVal((int)v), mFloatVal(v), mDoubleVal((double)v), mBoolVal(false) {} + Value(double v) : mValueType(DoubleVal), mIntVal((int)v), mFloatVal((float)v), mDoubleVal(v), mBoolVal(false) {} + Value(const std::string& v) : mValueType(StringVal), mIntVal(), mFloatVal(), mDoubleVal(), mStringVal(v), mBoolVal(false) {} + Value(const char* v) : mValueType(StringVal), mIntVal(), mFloatVal(), mDoubleVal(), mStringVal(v), mBoolVal(false) {} + Value(const Object& v) : mValueType(ObjectVal), mIntVal(), mFloatVal(), mDoubleVal(), mObjectVal(v), mBoolVal(false) {} + Value(const Array& v) : mValueType(ArrayVal), mIntVal(), mFloatVal(), mDoubleVal(), mArrayVal(v), mBoolVal(false) {} + Value(bool v) : mValueType(BoolVal), mIntVal(), mFloatVal(), mDoubleVal(), mBoolVal(v) {} Value(const Value& v); + // Use this to determine the underlying type that this Value class represents. It will be one of the + // ValueType enums as defined at the top of this file. ValueType GetType() const {return mValueType;} + // Convenience method that checks if this type is an int/double/float + bool IsNumeric() const {return (mValueType == IntVal) || (mValueType == DoubleVal) || (mValueType == FloatVal);} + Value& operator =(const Value& v); friend bool operator ==(const Value& lhs, const Value& rhs); @@ -343,7 +415,9 @@ namespace json inline friend bool operator >=(const Value& lhs, const Value& rhs) {return !operator<(lhs, rhs);} - // For use with Array/ObjectVal types, respectively + // If this value represents an object or array, you can use the [] indexing operator + // just like you would with the native json::Array or json::Object classes. + // THROWS A std::runtime_error IF NOT AN ARRAY OR OBJECT. Value& operator [](size_t idx); const Value& operator [](size_t idx) const; Value& operator [](const std::string& key); @@ -351,19 +425,30 @@ namespace json Value& operator [](const char* key); const Value& operator [](const char* key) const; + // If this value represents an object, these methods let you check if a single key or an array of + // keys is contained within it. + // THROWS A std::runtime_error IF NOT AN OBJECT. bool HasKey(const std::string& key) const; int HasKeys(const std::vector& keys) const; int HasKeys(const char* keys[], int key_count) const; - // non-operator versions - int ToInt() const {assert(IsNumeric()); return mIntVal;} - float ToFloat() const {assert(IsNumeric()); return mFloatVal;} - double ToDouble() const {assert(IsNumeric()); return mDoubleVal;} - bool ToBool() const {assert(mValueType == BoolVal); return mBoolVal;} - std::string ToString() const {assert(mValueType == StringVal); return mStringVal;} - Object ToObject() const {assert(mValueType == ObjectVal); return mObjectVal;} - Array ToArray() const {assert(mValueType == ArrayVal); return mArrayVal;} + // non-operator versions, **will throw a std::runtime_error if invalid with an appropriate error message** + int ToInt() const; + float ToFloat() const; + double ToDouble() const; + bool ToBool() const; + const std::string& ToString() const; + Object ToObject() const; + Array ToArray() const; + + // These versions do the same as above but will return your specified default value in the event there's an error, and thus **don't** throw an exception. + int ToInt(int def) const {return IsNumeric() ? mIntVal : def;} + float ToFloat(float def) const {return IsNumeric() ? mFloatVal : def;} + double ToDouble(double def) const {return IsNumeric() ? mDoubleVal : def;} + bool ToBool(bool def) const {return (mValueType == BoolVal) ? mBoolVal : def;} + const std::string& ToString(const std::string& def) const {return (mValueType == StringVal) ? mStringVal : def;} + // Please note that as per C++ rules, implicitly casting a Value to a std::string won't work. // This is because it could use the int/float/double/bool operators as well. So to assign a @@ -372,15 +457,13 @@ namespace json // Or you can now do: // my_string = my_value.ToString(); // - operator int() const {assert(IsNumeric()); return mIntVal;} - operator float() const {assert(IsNumeric()); return mFloatVal;} - operator double() const {assert(IsNumeric()); return mDoubleVal;} - operator bool() const {assert(mValueType == BoolVal); return mBoolVal;} - operator std::string() const {assert(mValueType == StringVal); return mStringVal;} - operator Object() const {assert(mValueType == ObjectVal); return mObjectVal;} - operator Array() const {assert(mValueType == ArrayVal); return mArrayVal;} - - bool IsNumeric() const {return (mValueType == IntVal) || (mValueType == DoubleVal) || (mValueType == FloatVal);} + operator int() const; + operator float() const; + operator double() const; + operator bool() const; + operator std::string() const; + operator Object() const; + operator Array() const; // Returns 1 for anything not an Array/ObjectVal size_t size() const; @@ -392,10 +475,16 @@ namespace json //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Converts a JSON Object or Array instance into a JSON string representing it. + // Converts a JSON Object or Array instance into a JSON string representing it. RETURNS EMPTY STRING ON ERROR. + // As per JSON specification, a JSON data structure must be an array or an object. Thus, you must either pass in a + // json::Array, json::Object, or a json::Value that has an Array or Object as its underlying type. std::string Serialize(const Value& obj); - // If there is an error, Value will be NULLType + // If there is an error, Value will be NULLVal. Pass in a valid JSON string (such as one returned from Serialize, or obtained + // elsewhere) to receive a Value in return that represents the JSON structure. Check the type of Value by calling GetType(). + // It will be ObjectVal or ArrayVal (or NULLVal if invalid JSON). The Value class contains the operator [] for indexing in the + // case that the underlying type is an object or array. You may, if you prefer, create an object or array from the Value returned + // by this method by simply passing it into the constructor. Value Deserialize(const std::string& str); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////