diff --git a/README.md b/README.md index 38ad5812..c7e7986f 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ There are myriads of [JSON](http://json.org) libraries out there, and each may e Other aspects were not so important to us: -- **Memory efficiency**. Each JSON object has an overhead of one pointer and one enumeration element (1 byte). We use the following C++ data types: `std::string` for strings, `int` or `double` for numbers, `std::map` for objects, `std::vector` for arrays, and `bool` for Booleans. We know that there are more efficient ways to store the values, but we are happy enough right now. And by the way: [Valgrind](http://valgrind.org) says our code is free of leaks. +- **Memory efficiency**. Each JSON object has an overhead of one pointer (the maximal size of a union) and one enumeration element (1 byte). We use the following C++ data types: `std::string` for strings, `int` or `double` for numbers, `std::map` for objects, `std::vector` for arrays, and `bool` for Booleans. We know that there are more efficient ways to store the values, but we are happy enough right now. And by the way: [Valgrind](http://valgrind.org) says our code is free of leaks. -- **Speed**. We currently implement the parser as naive [recursive descent parser](http://en.wikipedia.org/wiki/Recursive_descent_parser) with hand coded string handling. It is fast enough, but a [LALR-parser](http://en.wikipedia.org/wiki/LALR_parser) with a decent regular expression processor should be even faster. +- **Speed**. We currently implement the parser as naive [recursive descent parser](http://en.wikipedia.org/wiki/Recursive_descent_parser) with hand coded string handling. It is fast enough, but a [LALR-parser](http://en.wikipedia.org/wiki/LALR_parser) with a decent regular expression processor should be even faster (but would consist of more files which makes the integration harder). - **Rigourous standard compliance**. We followed the [specification](http://json.org) as close as possible, but did not invest too much in a 100% compliance with respect to Unicode support. As a result, there might be edge cases of false positives and false negatives, but as long as we have a hand-written parser, we won't invest too much to be fully compliant. @@ -48,7 +48,7 @@ j["name"] = "Niels"; // add an object inside the object j["further"]["entry"] = 42; -// add an array that is stored as std::vector +// add an array that is stored as std::vector (C++11) j["list"] = { 1, 0, 2 }; ``` @@ -63,6 +63,8 @@ j << "{ \"pi\": 3.141, \"happy\": true }"; std::cout << j; ``` +These operators work for any subclases of `std::istream` or `std::ostream`. + ### STL-like access ```cpp @@ -77,6 +79,11 @@ for (JSON::iterator it = j.begin(); it != j.end(); ++it) { std::cout << *it << '\n'; } +// C++11 style +for (auto element : j) { + std::cout << element << '\n'; +} + // getter/setter std::string tmp = j[0]; j[1] = 42; @@ -85,4 +92,42 @@ j[1] = 42; j.size(); // 3 j.empty(); // false j.type(); // JSON::array + +// create an object +JSON o; +o["foo"] = 23; +o["bar"] = false; +o["baz"] = 3.141; + +// find an entry +if (o.find("foo") != o.end()) { + // there is an entry with key "foo" +} + +// iterate the object +for (JSON::iterator it = o.begin(); it != o.end(); ++it) { + std::cout << it.key() << ':' << it.value() << '\n'; +} ``` + +### Implicit conversions + +The type of the JSON object is determined automatically by the expression to store. Likewise, the stored value is implicitly converted + +```cpp +/// strings +std::string s1 = "Hello, world!"; +JSON js = s; +std::string s2 = j; + +// Booleans +bool b1 = true; +JSON jb = b1; +bool b2 = jb; + +// numbers +int i = 42; +JSON jn = i; +double f = jn; +``` + diff --git a/src/JSON.cc b/src/JSON.cc index a0afea2b..130cfb2a 100644 --- a/src/JSON.cc +++ b/src/JSON.cc @@ -10,10 +10,20 @@ #include #include +// allow us to use "nullptr" everywhere +#include +#ifndef nullptr +#define nullptr NULL +#endif + + +///////////////////// +// HELPER FUNCTION // +///////////////////// + #ifdef __cplusplus11 using std::to_string; #else - inline std::string to_string(double f) { std::stringstream s; s << f; @@ -21,18 +31,20 @@ inline std::string to_string(double f) { } #endif -/****************** - * STATIC MEMBERS * - ******************/ + +//////////////////// +// STATIC MEMBERS // +//////////////////// #ifdef __cplusplus11 /// a mutex to ensure thread safety std::mutex JSON::_token; #endif -/******************************* - * CONSTRUCTORS AND DESTRUCTOR * - *******************************/ + +///////////////////////////////// +// CONSTRUCTORS AND DESTRUCTOR // +///////////////////////////////// JSON::JSON() : _type(null) {} @@ -133,6 +145,7 @@ JSON& JSON::operator=(const JSON& o) { return *this; } + // first delete original value switch (_type) { case (array): { delete _value.array; @@ -160,6 +173,7 @@ JSON& JSON::operator=(const JSON& o) { } } + // then copy given value from o _type = o._type; switch (_type) { case (array): { @@ -226,9 +240,9 @@ JSON::~JSON() { } -/***************************** - * OPERATORS AND CONVERSIONS * - *****************************/ +/////////////////////////////// +// OPERATORS AND CONVERSIONS // +/////////////////////////////// JSON::operator const std::string() const { switch (_type) { @@ -271,6 +285,27 @@ JSON::operator bool() const { } } +JSON::operator std::vector() const { + if (_type == array) { + return *_value.array; + } + if (_type == object) { + throw std::runtime_error("cannot cast " + _typename() + " to JSON array"); + } + + std::vector result; + result.push_back(*this); + return result; +} + +JSON::operator std::map() const { + if (_type == object) { + return *_value.object; + } else { + throw std::runtime_error("cannot cast " + _typename() + " to JSON object"); + } +} + const std::string JSON::toString() const { switch (_type) { case (null): { @@ -322,9 +357,9 @@ const std::string JSON::toString() const { } -/***************************************** - * ADDING ELEMENTS TO OBJECTS AND ARRAYS * - *****************************************/ +/////////////////////////////////////////// +// ADDING ELEMENTS TO OBJECTS AND ARRAYS // +/////////////////////////////////////////// JSON& JSON::operator+=(const JSON& o) { push_back(o); @@ -483,40 +518,54 @@ const JSON& JSON::operator[](const std::string& key) const { /// return the number of stored values size_t JSON::size() const { switch (_type) { - case (array): + case (array): { return _value.array->size(); - case (object): + } + case (object): { return _value.object->size(); - case (null): + } + case (null): { return 0; - case (string): + } + case (string): { return 1; - case (boolean): + } + case (boolean): { return 1; - case (number): + } + case (number): { return 1; - case (number_float): + } + case (number_float): { return 1; + } } } /// checks whether object is empty bool JSON::empty() const { switch (_type) { - case (array): + case (array): { return _value.array->empty(); - case (object): + } + case (object): { return _value.object->empty(); - case (null): + } + case (null): { return true; - case (string): + } + case (string): { return false; - case (boolean): + } + case (boolean): { return false; - case (number): + } + case (number): { return false; - case (number_float): + } + case (number_float): { return false; + } } } @@ -624,25 +673,36 @@ bool JSON::operator==(const JSON& o) const { return false; } +/// lexicographically compares the values +bool JSON::operator!=(const JSON& o) const { + return not operator==(o); +} /// return the type as string std::string JSON::_typename() const { switch (_type) { - case (array): + case (array): { return "array"; - case (object): + } + case (object): { return "object"; - case (null): + } + case (null): { return "null"; - case (string): + } + case (string): { return "string"; - case (boolean): + } + case (boolean): { return "boolean"; - case (number): + } + case (number): { return "number"; - case (number_float): + } + case (number_float): { return "number"; + } } } @@ -683,7 +743,7 @@ JSON::parser::~parser() { delete [] _buffer; } -void JSON::parser::error(std::string msg) { +void JSON::parser::error(std::string msg = "") { throw std::runtime_error("parse error at position " + to_string(_pos) + ": " + msg + ", last read: '" + _current + "'"); } @@ -917,8 +977,9 @@ JSON::iterator::iterator(JSON* j) : _object(j), _vi(nullptr), _oi(nullptr) { _oi = new object_t::iterator(_object->_value.object->begin()); break; } - default: + default: { break; + } } } @@ -933,8 +994,9 @@ JSON::iterator::iterator(const JSON::iterator& o) : _object(o._object), _vi(null _oi = new object_t::iterator(*(o._oi)); break; } - default: + default: { break; + } } } @@ -955,8 +1017,9 @@ JSON::iterator& JSON::iterator::operator=(const JSON::iterator& o) { _oi = new object_t::iterator(*(o._oi)); break; } - default: + default: { break; + } } return *this; } @@ -1002,12 +1065,15 @@ JSON& JSON::iterator::operator*() const { } switch (_object->_type) { - case (array): + case (array): { return **_vi; - case (object): + } + case (object): { return (*_oi)->second; - default: + } + default: { return *_object; + } } } @@ -1017,12 +1083,15 @@ JSON* JSON::iterator::operator->() const { } switch (_object->_type) { - case (array): + case (array): { return &(**_vi); - case (object): + } + case (object): { return &((*_oi)->second); - default: + } + default: { return _object; + } } } @@ -1040,12 +1109,15 @@ JSON& JSON::iterator::value() const { } switch (_object->_type) { - case (array): + case (array): { return **_vi; - case (object): + } + case (object): { return (*_oi)->second; - default: + } + default: { return *_object; + } } } @@ -1079,8 +1151,9 @@ JSON::const_iterator::const_iterator(const JSON* j) : _object(j), _vi(nullptr), _oi = new object_t::const_iterator(_object->_value.object->begin()); break; } - default: + default: { break; + } } } @@ -1095,8 +1168,9 @@ JSON::const_iterator::const_iterator(const JSON::const_iterator& o) : _object(o. _oi = new object_t::const_iterator(*(o._oi)); break; } - default: + default: { break; + } } } @@ -1111,8 +1185,9 @@ JSON::const_iterator::const_iterator(const JSON::iterator& o) : _object(o._objec _oi = new object_t::const_iterator(*(o._oi)); break; } - default: + default: { break; + } } } @@ -1133,8 +1208,9 @@ JSON::const_iterator& JSON::const_iterator::operator=(const JSON::const_iterator _oi = new object_t::const_iterator(*(o._oi)); break; } - default: + default: { break; + } } return *this; } @@ -1180,12 +1256,15 @@ const JSON& JSON::const_iterator::operator*() const { } switch (_object->_type) { - case (array): + case (array): { return **_vi; - case (object): + } + case (object): { return (*_oi)->second; - default: + } + default: { return *_object; + } } } @@ -1195,12 +1274,15 @@ const JSON* JSON::const_iterator::operator->() const { } switch (_object->_type) { - case (array): + case (array): { return &(**_vi); - case (object): + } + case (object): { return &((*_oi)->second); - default: + } + default: { return _object; + } } } @@ -1218,11 +1300,14 @@ const JSON& JSON::const_iterator::value() const { } switch (_object->_type) { - case (array): + case (array): { return **_vi; - case (object): + } + case (object): { return (*_oi)->second; - default: + } + default: { return *_object; + } } } diff --git a/src/JSON.h b/src/JSON.h index e6a3ee99..4871807a 100644 --- a/src/JSON.h +++ b/src/JSON.h @@ -5,30 +5,25 @@ #define __cplusplus11 #endif -// allow us to use "nullptr" everywhere -#include -#ifndef nullptr -#define nullptr NULL -#endif - +// STL containers #include #include #include -// additional C++11 header +// additional C++11 headers #ifdef __cplusplus11 #include #include #endif class JSON { - // forward declaration to friend this class + // forward declaration to friend this class public: class iterator; class const_iterator; - private: #ifdef __cplusplus11 + private: /// mutex to guard payload static std::mutex _token; #endif @@ -55,19 +50,32 @@ class JSON { /// a JSON value union value { + /// array as pointer to array_t array_t* array; + /// object as pointer to object_t object_t* object; + /// string as pointer to string_t string_t* string; + /// Boolean boolean_t boolean; + /// number (integer) number_t number; + /// number (float) number_float_t number_float; + /// default constructor value() {} + /// constructor for arrays value(array_t* array): array(array) {} + /// constructor for objects value(object_t* object): object(object) {} + /// constructor for strings value(string_t* string): string(string) {} + /// constructor for Booleans value(boolean_t boolean) : boolean(boolean) {} + /// constructor for numbers (integer) value(number_t number) : number(number) {} + /// constructor for numbers (float) value(number_float_t number_float) : number_float(number_float) {} }; @@ -77,22 +85,22 @@ class JSON { /// the payload value _value; - + #ifdef __cplusplus11 /// a type for array initialization typedef std::initializer_list array_init_t; #endif public: - /// create an empty (null) object + /// create a null object JSON(); - /// create an empty object according to given type + /// create an object according to given type JSON(json_t); - /// create a string object from C++ string + /// create a string object from a C++ string JSON(const std::string&); - /// create a string object from C string + /// create a string object from a C string JSON(char*); - /// create a string object from C string + /// create a string object from a C string JSON(const char*); /// create a Boolean object JSON(const bool); @@ -135,13 +143,16 @@ class JSON { operator double() const; /// implicit conversion to Boolean (only for Booleans) operator bool() const; + /// implicit conversion to JSON vector (not for objects) + operator std::vector() const; + /// implicit conversion to JSON map (only for objects) + operator std::map() const; /// write to stream friend std::ostream& operator<<(std::ostream& o, const JSON& j) { o << j.toString(); return o; } - /// write to stream friend std::ostream& operator>>(const JSON& j, std::ostream& o) { o << j.toString(); @@ -153,7 +164,6 @@ class JSON { parser(i).parse(j); return i; } - /// read from stream friend std::istream& operator<<(JSON& j, std::istream& i) { parser(i).parse(j); @@ -211,8 +221,11 @@ class JSON { /// find an element in an object (returns end() iterator otherwise) iterator find(const std::string&); + /// find an element in an object (returns end() iterator otherwise) const_iterator find(const std::string&) const; + /// find an element in an object (returns end() iterator otherwise) iterator find(const char*); + /// find an element in an object (returns end() iterator otherwise) const_iterator find(const char*) const; /// direct access to the underlying payload @@ -222,6 +235,8 @@ class JSON { /// lexicographically compares the values bool operator==(const JSON&) const; + /// lexicographically compares the values + bool operator!=(const JSON&) const; private: /// return the type as string @@ -299,26 +314,43 @@ class JSON { const_iterator cend() const; private: + /// a helper class to parse a JSON object class parser { public: + /// a parser reading from a C string parser(char*); + /// a parser reading from a C++ string parser(std::string&); + /// a parser reading from an input stream parser(std::istream&); + /// destructor of the parser ~parser(); + /// parse into a given JSON object void parse(JSON&); private: + /// read the next character, stripping whitespace bool next(); - void error(std::string = "") __attribute__((noreturn)); + /// raise an exception with an error message + void error(std::string) __attribute__((noreturn)); + /// parse a quoted string std::string parseString(); + /// parse a Boolean "true" void parseTrue(); + /// parse a Boolean "false" void parseFalse(); + /// parse a null object void parseNull(); + /// a helper function to expect a certain character void expect(char); + /// the current character char _current; + /// a buffer of the input char* _buffer; + /// the position inside the input buffer size_t _pos; + /// the length of the input buffer size_t _length; }; };