- tidied code

- added more documentation
This commit is contained in:
Niels 2013-07-11 14:19:11 +02:00
parent 52c2cb8ce7
commit 9a567d9d2d
3 changed files with 243 additions and 81 deletions

View file

@ -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: 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. - **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 // add an object inside the object
j["further"]["entry"] = 42; 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 }; j["list"] = { 1, 0, 2 };
``` ```
@ -63,6 +63,8 @@ j << "{ \"pi\": 3.141, \"happy\": true }";
std::cout << j; std::cout << j;
``` ```
These operators work for any subclases of `std::istream` or `std::ostream`.
### STL-like access ### STL-like access
```cpp ```cpp
@ -77,6 +79,11 @@ for (JSON::iterator it = j.begin(); it != j.end(); ++it) {
std::cout << *it << '\n'; std::cout << *it << '\n';
} }
// C++11 style
for (auto element : j) {
std::cout << element << '\n';
}
// getter/setter // getter/setter
std::string tmp = j[0]; std::string tmp = j[0];
j[1] = 42; j[1] = 42;
@ -85,4 +92,42 @@ j[1] = 42;
j.size(); // 3 j.size(); // 3
j.empty(); // false j.empty(); // false
j.type(); // JSON::array 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;
```

View file

@ -10,10 +10,20 @@
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
// allow us to use "nullptr" everywhere
#include <cstddef>
#ifndef nullptr
#define nullptr NULL
#endif
/////////////////////
// HELPER FUNCTION //
/////////////////////
#ifdef __cplusplus11 #ifdef __cplusplus11
using std::to_string; using std::to_string;
#else #else
inline std::string to_string(double f) { inline std::string to_string(double f) {
std::stringstream s; std::stringstream s;
s << f; s << f;
@ -21,18 +31,20 @@ inline std::string to_string(double f) {
} }
#endif #endif
/******************
* STATIC MEMBERS * ////////////////////
******************/ // STATIC MEMBERS //
////////////////////
#ifdef __cplusplus11 #ifdef __cplusplus11
/// a mutex to ensure thread safety /// a mutex to ensure thread safety
std::mutex JSON::_token; std::mutex JSON::_token;
#endif #endif
/*******************************
* CONSTRUCTORS AND DESTRUCTOR * /////////////////////////////////
*******************************/ // CONSTRUCTORS AND DESTRUCTOR //
/////////////////////////////////
JSON::JSON() : _type(null) {} JSON::JSON() : _type(null) {}
@ -133,6 +145,7 @@ JSON& JSON::operator=(const JSON& o) {
return *this; return *this;
} }
// first delete original value
switch (_type) { switch (_type) {
case (array): { case (array): {
delete _value.array; delete _value.array;
@ -160,6 +173,7 @@ JSON& JSON::operator=(const JSON& o) {
} }
} }
// then copy given value from o
_type = o._type; _type = o._type;
switch (_type) { switch (_type) {
case (array): { case (array): {
@ -226,9 +240,9 @@ JSON::~JSON() {
} }
/***************************** ///////////////////////////////
* OPERATORS AND CONVERSIONS * // OPERATORS AND CONVERSIONS //
*****************************/ ///////////////////////////////
JSON::operator const std::string() const { JSON::operator const std::string() const {
switch (_type) { switch (_type) {
@ -271,6 +285,27 @@ JSON::operator bool() const {
} }
} }
JSON::operator std::vector<JSON>() const {
if (_type == array) {
return *_value.array;
}
if (_type == object) {
throw std::runtime_error("cannot cast " + _typename() + " to JSON array");
}
std::vector<JSON> result;
result.push_back(*this);
return result;
}
JSON::operator std::map<std::string, JSON>() const {
if (_type == object) {
return *_value.object;
} else {
throw std::runtime_error("cannot cast " + _typename() + " to JSON object");
}
}
const std::string JSON::toString() const { const std::string JSON::toString() const {
switch (_type) { switch (_type) {
case (null): { 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) { JSON& JSON::operator+=(const JSON& o) {
push_back(o); push_back(o);
@ -483,41 +518,55 @@ const JSON& JSON::operator[](const std::string& key) const {
/// return the number of stored values /// return the number of stored values
size_t JSON::size() const { size_t JSON::size() const {
switch (_type) { switch (_type) {
case (array): case (array): {
return _value.array->size(); return _value.array->size();
case (object): }
case (object): {
return _value.object->size(); return _value.object->size();
case (null): }
case (null): {
return 0; return 0;
case (string): }
case (string): {
return 1; return 1;
case (boolean): }
case (boolean): {
return 1; return 1;
case (number): }
case (number): {
return 1; return 1;
case (number_float): }
case (number_float): {
return 1; return 1;
} }
}
} }
/// checks whether object is empty /// checks whether object is empty
bool JSON::empty() const { bool JSON::empty() const {
switch (_type) { switch (_type) {
case (array): case (array): {
return _value.array->empty(); return _value.array->empty();
case (object): }
case (object): {
return _value.object->empty(); return _value.object->empty();
case (null): }
case (null): {
return true; return true;
case (string): }
case (string): {
return false; return false;
case (boolean): }
case (boolean): {
return false; return false;
case (number): }
case (number): {
return false; return false;
case (number_float): }
case (number_float): {
return false; return false;
} }
}
} }
/// return the type of the object /// return the type of the object
@ -624,26 +673,37 @@ bool JSON::operator==(const JSON& o) const {
return false; return false;
} }
/// lexicographically compares the values
bool JSON::operator!=(const JSON& o) const {
return not operator==(o);
}
/// return the type as string /// return the type as string
std::string JSON::_typename() const { std::string JSON::_typename() const {
switch (_type) { switch (_type) {
case (array): case (array): {
return "array"; return "array";
case (object): }
case (object): {
return "object"; return "object";
case (null): }
case (null): {
return "null"; return "null";
case (string): }
case (string): {
return "string"; return "string";
case (boolean): }
case (boolean): {
return "boolean"; return "boolean";
case (number): }
case (number): {
return "number"; return "number";
case (number_float): }
case (number_float): {
return "number"; return "number";
} }
}
} }
@ -683,7 +743,7 @@ JSON::parser::~parser() {
delete [] _buffer; 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 + "'"); throw std::runtime_error("parse error at position " + to_string(_pos) + ": " + msg + ", last read: '" + _current + "'");
} }
@ -917,9 +977,10 @@ JSON::iterator::iterator(JSON* j) : _object(j), _vi(nullptr), _oi(nullptr) {
_oi = new object_t::iterator(_object->_value.object->begin()); _oi = new object_t::iterator(_object->_value.object->begin());
break; break;
} }
default: default: {
break; break;
} }
}
} }
JSON::iterator::iterator(const JSON::iterator& o) : _object(o._object), _vi(nullptr), _oi(nullptr) { JSON::iterator::iterator(const JSON::iterator& o) : _object(o._object), _vi(nullptr), _oi(nullptr) {
@ -933,9 +994,10 @@ JSON::iterator::iterator(const JSON::iterator& o) : _object(o._object), _vi(null
_oi = new object_t::iterator(*(o._oi)); _oi = new object_t::iterator(*(o._oi));
break; break;
} }
default: default: {
break; break;
} }
}
} }
JSON::iterator::~iterator() { JSON::iterator::~iterator() {
@ -955,9 +1017,10 @@ JSON::iterator& JSON::iterator::operator=(const JSON::iterator& o) {
_oi = new object_t::iterator(*(o._oi)); _oi = new object_t::iterator(*(o._oi));
break; break;
} }
default: default: {
break; break;
} }
}
return *this; return *this;
} }
@ -1002,13 +1065,16 @@ JSON& JSON::iterator::operator*() const {
} }
switch (_object->_type) { switch (_object->_type) {
case (array): case (array): {
return **_vi; return **_vi;
case (object): }
case (object): {
return (*_oi)->second; return (*_oi)->second;
default: }
default: {
return *_object; return *_object;
} }
}
} }
JSON* JSON::iterator::operator->() const { JSON* JSON::iterator::operator->() const {
@ -1017,13 +1083,16 @@ JSON* JSON::iterator::operator->() const {
} }
switch (_object->_type) { switch (_object->_type) {
case (array): case (array): {
return &(**_vi); return &(**_vi);
case (object): }
case (object): {
return &((*_oi)->second); return &((*_oi)->second);
default: }
default: {
return _object; return _object;
} }
}
} }
std::string JSON::iterator::key() const { std::string JSON::iterator::key() const {
@ -1040,13 +1109,16 @@ JSON& JSON::iterator::value() const {
} }
switch (_object->_type) { switch (_object->_type) {
case (array): case (array): {
return **_vi; return **_vi;
case (object): }
case (object): {
return (*_oi)->second; return (*_oi)->second;
default: }
default: {
return *_object; return *_object;
} }
}
} }
@ -1079,9 +1151,10 @@ JSON::const_iterator::const_iterator(const JSON* j) : _object(j), _vi(nullptr),
_oi = new object_t::const_iterator(_object->_value.object->begin()); _oi = new object_t::const_iterator(_object->_value.object->begin());
break; break;
} }
default: default: {
break; break;
} }
}
} }
JSON::const_iterator::const_iterator(const JSON::const_iterator& o) : _object(o._object), _vi(nullptr), _oi(nullptr) { JSON::const_iterator::const_iterator(const JSON::const_iterator& o) : _object(o._object), _vi(nullptr), _oi(nullptr) {
@ -1095,9 +1168,10 @@ JSON::const_iterator::const_iterator(const JSON::const_iterator& o) : _object(o.
_oi = new object_t::const_iterator(*(o._oi)); _oi = new object_t::const_iterator(*(o._oi));
break; break;
} }
default: default: {
break; break;
} }
}
} }
JSON::const_iterator::const_iterator(const JSON::iterator& o) : _object(o._object), _vi(nullptr), _oi(nullptr) { JSON::const_iterator::const_iterator(const JSON::iterator& o) : _object(o._object), _vi(nullptr), _oi(nullptr) {
@ -1111,9 +1185,10 @@ JSON::const_iterator::const_iterator(const JSON::iterator& o) : _object(o._objec
_oi = new object_t::const_iterator(*(o._oi)); _oi = new object_t::const_iterator(*(o._oi));
break; break;
} }
default: default: {
break; break;
} }
}
} }
JSON::const_iterator::~const_iterator() { JSON::const_iterator::~const_iterator() {
@ -1133,9 +1208,10 @@ JSON::const_iterator& JSON::const_iterator::operator=(const JSON::const_iterator
_oi = new object_t::const_iterator(*(o._oi)); _oi = new object_t::const_iterator(*(o._oi));
break; break;
} }
default: default: {
break; break;
} }
}
return *this; return *this;
} }
@ -1180,13 +1256,16 @@ const JSON& JSON::const_iterator::operator*() const {
} }
switch (_object->_type) { switch (_object->_type) {
case (array): case (array): {
return **_vi; return **_vi;
case (object): }
case (object): {
return (*_oi)->second; return (*_oi)->second;
default: }
default: {
return *_object; return *_object;
} }
}
} }
const JSON* JSON::const_iterator::operator->() const { const JSON* JSON::const_iterator::operator->() const {
@ -1195,13 +1274,16 @@ const JSON* JSON::const_iterator::operator->() const {
} }
switch (_object->_type) { switch (_object->_type) {
case (array): case (array): {
return &(**_vi); return &(**_vi);
case (object): }
case (object): {
return &((*_oi)->second); return &((*_oi)->second);
default: }
default: {
return _object; return _object;
} }
}
} }
std::string JSON::const_iterator::key() const { std::string JSON::const_iterator::key() const {
@ -1218,11 +1300,14 @@ const JSON& JSON::const_iterator::value() const {
} }
switch (_object->_type) { switch (_object->_type) {
case (array): case (array): {
return **_vi; return **_vi;
case (object): }
case (object): {
return (*_oi)->second; return (*_oi)->second;
default: }
default: {
return *_object; return *_object;
} }
}
} }

View file

@ -5,17 +5,12 @@
#define __cplusplus11 #define __cplusplus11
#endif #endif
// allow us to use "nullptr" everywhere // STL containers
#include <cstddef>
#ifndef nullptr
#define nullptr NULL
#endif
#include <string> #include <string>
#include <vector> #include <vector>
#include <map> #include <map>
// additional C++11 header // additional C++11 headers
#ifdef __cplusplus11 #ifdef __cplusplus11
#include <mutex> #include <mutex>
#include <initializer_list> #include <initializer_list>
@ -27,8 +22,8 @@ class JSON {
class iterator; class iterator;
class const_iterator; class const_iterator;
private:
#ifdef __cplusplus11 #ifdef __cplusplus11
private:
/// mutex to guard payload /// mutex to guard payload
static std::mutex _token; static std::mutex _token;
#endif #endif
@ -55,19 +50,32 @@ class JSON {
/// a JSON value /// a JSON value
union value { union value {
/// array as pointer to array_t
array_t* array; array_t* array;
/// object as pointer to object_t
object_t* object; object_t* object;
/// string as pointer to string_t
string_t* string; string_t* string;
/// Boolean
boolean_t boolean; boolean_t boolean;
/// number (integer)
number_t number; number_t number;
/// number (float)
number_float_t number_float; number_float_t number_float;
/// default constructor
value() {} value() {}
/// constructor for arrays
value(array_t* array): array(array) {} value(array_t* array): array(array) {}
/// constructor for objects
value(object_t* object): object(object) {} value(object_t* object): object(object) {}
/// constructor for strings
value(string_t* string): string(string) {} value(string_t* string): string(string) {}
/// constructor for Booleans
value(boolean_t boolean) : boolean(boolean) {} value(boolean_t boolean) : boolean(boolean) {}
/// constructor for numbers (integer)
value(number_t number) : number(number) {} value(number_t number) : number(number) {}
/// constructor for numbers (float)
value(number_float_t number_float) : number_float(number_float) {} value(number_float_t number_float) : number_float(number_float) {}
}; };
@ -84,15 +92,15 @@ class JSON {
#endif #endif
public: public:
/// create an empty (null) object /// create a null object
JSON(); JSON();
/// create an empty object according to given type /// create an object according to given type
JSON(json_t); JSON(json_t);
/// create a string object from C++ string /// create a string object from a C++ string
JSON(const std::string&); JSON(const std::string&);
/// create a string object from C string /// create a string object from a C string
JSON(char*); JSON(char*);
/// create a string object from C string /// create a string object from a C string
JSON(const char*); JSON(const char*);
/// create a Boolean object /// create a Boolean object
JSON(const bool); JSON(const bool);
@ -135,13 +143,16 @@ class JSON {
operator double() const; operator double() const;
/// implicit conversion to Boolean (only for Booleans) /// implicit conversion to Boolean (only for Booleans)
operator bool() const; operator bool() const;
/// implicit conversion to JSON vector (not for objects)
operator std::vector<JSON>() const;
/// implicit conversion to JSON map (only for objects)
operator std::map<std::string, JSON>() const;
/// write to stream /// write to stream
friend std::ostream& operator<<(std::ostream& o, const JSON& j) { friend std::ostream& operator<<(std::ostream& o, const JSON& j) {
o << j.toString(); o << j.toString();
return o; return o;
} }
/// write to stream /// write to stream
friend std::ostream& operator>>(const JSON& j, std::ostream& o) { friend std::ostream& operator>>(const JSON& j, std::ostream& o) {
o << j.toString(); o << j.toString();
@ -153,7 +164,6 @@ class JSON {
parser(i).parse(j); parser(i).parse(j);
return i; return i;
} }
/// read from stream /// read from stream
friend std::istream& operator<<(JSON& j, std::istream& i) { friend std::istream& operator<<(JSON& j, std::istream& i) {
parser(i).parse(j); parser(i).parse(j);
@ -211,8 +221,11 @@ class JSON {
/// find an element in an object (returns end() iterator otherwise) /// find an element in an object (returns end() iterator otherwise)
iterator find(const std::string&); iterator find(const std::string&);
/// find an element in an object (returns end() iterator otherwise)
const_iterator find(const std::string&) const; const_iterator find(const std::string&) const;
/// find an element in an object (returns end() iterator otherwise)
iterator find(const char*); iterator find(const char*);
/// find an element in an object (returns end() iterator otherwise)
const_iterator find(const char*) const; const_iterator find(const char*) const;
/// direct access to the underlying payload /// direct access to the underlying payload
@ -222,6 +235,8 @@ class JSON {
/// lexicographically compares the values /// lexicographically compares the values
bool operator==(const JSON&) const; bool operator==(const JSON&) const;
/// lexicographically compares the values
bool operator!=(const JSON&) const;
private: private:
/// return the type as string /// return the type as string
@ -299,26 +314,43 @@ class JSON {
const_iterator cend() const; const_iterator cend() const;
private: private:
/// a helper class to parse a JSON object
class parser { class parser {
public: public:
/// a parser reading from a C string
parser(char*); parser(char*);
/// a parser reading from a C++ string
parser(std::string&); parser(std::string&);
/// a parser reading from an input stream
parser(std::istream&); parser(std::istream&);
/// destructor of the parser
~parser(); ~parser();
/// parse into a given JSON object
void parse(JSON&); void parse(JSON&);
private: private:
/// read the next character, stripping whitespace
bool next(); 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(); std::string parseString();
/// parse a Boolean "true"
void parseTrue(); void parseTrue();
/// parse a Boolean "false"
void parseFalse(); void parseFalse();
/// parse a null object
void parseNull(); void parseNull();
/// a helper function to expect a certain character
void expect(char); void expect(char);
/// the current character
char _current; char _current;
/// a buffer of the input
char* _buffer; char* _buffer;
/// the position inside the input buffer
size_t _pos; size_t _pos;
/// the length of the input buffer
size_t _length; size_t _length;
}; };
}; };