From 4502e7e51c0569419c26e75fbdd5748170603e54 Mon Sep 17 00:00:00 2001 From: Niels Date: Thu, 4 Jul 2013 10:49:03 +0200 Subject: [PATCH] - initial commit --- Makefile.am | 16 + configure.ac | 11 + src/JSON.cc | 1063 +++++++++++++++++++++++++++++++++++++++++++++ src/JSON.h | 267 ++++++++++++ test/JSON_test.cc | 236 ++++++++++ 5 files changed, 1593 insertions(+) create mode 100644 Makefile.am create mode 100644 configure.ac create mode 100644 src/JSON.cc create mode 100644 src/JSON.h create mode 100644 test/JSON_test.cc diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..fe70bf5b --- /dev/null +++ b/Makefile.am @@ -0,0 +1,16 @@ +noinst_PROGRAMS = json json98 +TESTS = ./json ./json98 + +#CXXFLAGS = -O3 -flto + +json_SOURCES = src/JSON.cc src/JSON.h test/JSON_test.cc +json_CXXFLAGS = -std=c++11 +json_CPPFLAGS = -I$(top_srcdir)/src + +json98_SOURCES = src/JSON.cc src/JSON.h test/JSON_test.cc +json98_CXXFLAGS = -std=c++98 +json98_CPPFLAGS = -I$(top_srcdir)/src + +svn-clean: maintainer-clean + rm -fr configure INSTALL aclocal.m4 build-aux depcomp install-sh missing test-driver + for DIR in $(DIST_SUBDIRS) .; do rm -f $$DIR/Makefile.in; done diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..f416fd7b --- /dev/null +++ b/configure.ac @@ -0,0 +1,11 @@ +AC_PREREQ([2.69]) +AC_INIT([JSON], [1.0], [BUG-REPORT-ADDRESS]) +AC_CONFIG_SRCDIR([src/JSON.cc]) + +AM_INIT_AUTOMAKE([foreign]) +AM_SILENT_RULES([yes]) + +AC_PROG_CXX + +AC_CONFIG_FILES(Makefile) +AC_OUTPUT diff --git a/src/JSON.cc b/src/JSON.cc new file mode 100644 index 00000000..4800a1e6 --- /dev/null +++ b/src/JSON.cc @@ -0,0 +1,1063 @@ +#include "JSON.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef __cplusplus11 +#include +#include +#endif + +#ifdef __cplusplus11 +using std::to_string; +#else + +inline std::string to_string(double f) { + std::stringstream s; + s << f; + return s.str(); +} +#endif + +/****************** + * STATIC MEMBERS * + ******************/ + +#ifdef __cplusplus11 +/// a mutex to ensure thread safety +std::mutex JSON::_token; +#endif + +/******************************* + * CONSTRUCTORS AND DESTRUCTOR * + *******************************/ + +JSON::JSON() : _type(null), _payload(nullptr) {} +JSON::JSON(const std::string& s) : _type(string), _payload(new std::string(s)) {} +JSON::JSON(const char* s) : _type(string), _payload(new std::string(s)) {} +JSON::JSON(char* s) : _type(string), _payload(new std::string(s)) {} +JSON::JSON(const bool b) : _type(boolean), _payload(new bool(b)) {} +JSON::JSON(const int i) : _type(number_int), _payload(new int(i)) {} +JSON::JSON(const double f) : _type(number_float), _payload(new double(f)) {} + +#ifdef __cplusplus11 +JSON::JSON(array_t a) : _type(array), _payload(new std::vector(a)) {} + +JSON::JSON(object_t o) : _type(object), _payload(new std::map) { + (*this)[std::get<0>(o)] = std::get<1>(o); +} +#endif + +/// copy constructor +JSON::JSON(const JSON& o) : _type(o._type) { + switch (_type) { + case (array): + _payload = new std::vector(*static_cast*>(o._payload)); + break; + case (object): + _payload = new std::map(*static_cast*>(o._payload)); + break; + case (string): + _payload = new std::string(*static_cast(o._payload)); + break; + case (boolean): + _payload = new bool(*static_cast(o._payload)); + break; + case (number_int): + _payload = new int(*static_cast(o._payload)); + break; + case (number_float): + _payload = new double(*static_cast(o._payload)); + break; + case (null): + break; + } +} + +#ifdef __cplusplus11 +/// move constructor +JSON::JSON(JSON&& o) : _type(std::move(o._type)), _payload(std::move(o._payload)) {} +#endif + +/// copy assignment +#ifdef __cplusplus11 +JSON& JSON::operator=(JSON o) { + std::swap(_type, o._type); + std::swap(_payload, o._payload); + return *this; +} +#else +JSON& JSON::operator=(const JSON& o) { + _type = o._type; + switch (_type) { + case (array): + _payload = new std::vector(*static_cast*>(o._payload)); + break; + case (object): + _payload = new std::map(*static_cast*>(o._payload)); + break; + case (string): + _payload = new std::string(*static_cast(o._payload)); + break; + case (boolean): + _payload = new bool(*static_cast(o._payload)); + break; + case (number_int): + _payload = new int(*static_cast(o._payload)); + break; + case (number_float): + _payload = new double(*static_cast(o._payload)); + break; + case (null): + break; + } + + return *this; +} +#endif + +/// destructor +JSON::~JSON() { + switch (_type) { + case (array): + delete static_cast*>(_payload); + break; + case (object): + delete static_cast*>(_payload); + break; + case (string): + delete static_cast(_payload); + break; + case (boolean): + delete static_cast(_payload); + break; + case (number_int): + delete static_cast(_payload); + break; + case (number_float): + delete static_cast(_payload); + break; + case (null): + break; + } +} + + +/***************************** + * OPERATORS AND CONVERSIONS * + *****************************/ + +JSON::operator const std::string() const { + switch (_type) { + case (string): + return *static_cast(_payload); + default: + throw std::runtime_error("cannot cast " + _typename() + " to JSON string"); + } +} + + +JSON::operator int() const { + switch (_type) { + case (number_int): + return *static_cast(_payload); + case (number_float): + return static_cast(*static_cast(_payload)); + default: + throw std::runtime_error("cannot cast " + _typename() + " to JSON number"); + } +} + +JSON::operator double() const { + switch (_type) { + case (number_int): + return static_cast(*static_cast(_payload)); + case (number_float): + return *static_cast(_payload); + default: + throw std::runtime_error("cannot cast " + _typename() + " to JSON number"); + } +} + +JSON::operator bool() const { + switch (_type) { + case (boolean): + return *static_cast(_payload); + default: + throw std::runtime_error("cannot cast " + _typename() + " to JSON Boolean"); + } +} + +const std::string JSON::toString() const { + switch (_type) { + case (null): { + return "null"; + } + + case (string): { + return std::string("\"") + *static_cast(_payload) + "\""; + } + + case (boolean): { + return *static_cast(_payload) ? "true" : "false"; + } + + case (number_int): { + return to_string(*static_cast(_payload)); + } + + case (number_float): { + return to_string(*static_cast(_payload)); + } + + case (array): { + std::string result; + + const std::vector* array = static_cast*>(_payload); + for (std::vector::const_iterator i = array->begin(); i != array->end(); ++i) { + if (i != array->begin()) { + result += ", "; + } + result += (*i).toString(); + } + + return "[" + result + "]"; + } + + case (object): { + std::string result; + + const std::map* object = static_cast*>(_payload); + for (std::map::const_iterator i = object->begin(); i != object->end(); ++i) { + if (i != object->begin()) { + result += ", "; + } + result += "\"" + i->first + "\": " + (i->second).toString(); + } + + return "{" + result + "}"; + } + } +} + + +/***************************************** + * ADDING ELEMENTS TO OBJECTS AND ARRAYS * + *****************************************/ + +JSON& JSON::operator+=(const JSON& o) { +#ifdef __cplusplus11 + std::lock_guard lg(_token); +#endif + + if (not(_type == null or _type == array)) { + throw std::runtime_error("cannot add element to " + _typename()); + } + + if (_type == null) { + _type = array; + _payload = new std::vector; + } + + static_cast*>(_payload)->push_back(o); + + return *this; +} + +JSON& JSON::operator+=(const std::string& s) { + JSON tmp(s); + operator+=(tmp); + return *this; +} + +JSON& JSON::operator+=(const char* s) { + JSON tmp(s); + operator+=(tmp); + return *this; +} + +JSON& JSON::operator+=(bool b) { + JSON tmp(b); + operator+=(tmp); + return *this; +} + +JSON& JSON::operator+=(int i) { + JSON tmp(i); + operator+=(tmp); + return *this; +} + +JSON& JSON::operator+=(double f) { + JSON tmp(f); + operator+=(tmp); + return *this; +} + +/// operator to set an element in an object +JSON& JSON::operator[](int index) { +#ifdef __cplusplus11 + std::lock_guard lg(_token); +#endif + + if (_type != array) { + throw std::runtime_error("cannot add entry with index " + to_string(index) + " to " + _typename()); + } + + std::vector* array = static_cast*>(_payload); + + if (index >= array->size()) { + throw std::runtime_error("cannot access element at index " + to_string(index)); + } + + return array->at(index); +} + +/// operator to get an element in an object +const JSON& JSON::operator[](const int index) const { + if (_type != array) { + throw std::runtime_error("cannot get entry with index " + to_string(index) + " from " + _typename()); + } + + std::vector* array = static_cast*>(_payload); + + if (index >= array->size()) { + throw std::runtime_error("cannot access element at index " + to_string(index)); + } + + return array->at(index); +} + +/// operator to set an element in an object +JSON& JSON::operator[](const std::string& key) { +#ifdef __cplusplus11 + std::lock_guard lg(_token); +#endif + + if (_type == null) { + _type = object; + _payload = new std::map; + } + + if (_type != object) { + throw std::runtime_error("cannot add entry with key " + std::string(key) + " to " + _typename()); + } + + std::map* object = static_cast*>(_payload); + if (object->find(key) == object->end()) { + (*object)[key] = JSON(); + } + + return (*object)[key]; +} + +/// operator to set an element in an object +JSON& JSON::operator[](const char* key) { +#ifdef __cplusplus11 + std::lock_guard lg(_token); +#endif + + if (_type == null) { + _type = object; + _payload = new std::map; + } + + if (_type != object) { + throw std::runtime_error("cannot add entry with key " + std::string(key) + " to " + _typename()); + } + + std::map* object = static_cast*>(_payload); + if (object->find(key) == object->end()) { + (*object)[key] = JSON(); + } + + return (*object)[key]; +} + +/// operator to get an element in an object +const JSON& JSON::operator[](const std::string& key) const { + if (_type != object) { + throw std::runtime_error("cannot get entry with key " + std::string(key) + " from " + _typename()); + } + + const std::map* object = static_cast*>(_payload); + if (object->find(key) == object->end()) { + throw std::runtime_error("key " + key + " not found"); + } else { + return object->find(key)->second; + } +} + +/// return the number of stored values +size_t JSON::size() const { + switch (_type) { + case (array): + return static_cast*>(_payload)->size(); + case (object): + return static_cast*>(_payload)->size(); + case (null): + return 0; + case (string): + return 1; + case (boolean): + return 1; + case (number_int): + return 1; + case (number_float): + return 1; + } +} + +/// checks whether object is empty +bool JSON::empty() const { + switch (_type) { + case (array): + return static_cast*>(_payload)->empty(); + case (object): + return static_cast*>(_payload)->empty(); + case (null): + return true; + case (string): + return false; + case (boolean): + return false; + case (number_int): + return false; + case (number_float): + return false; + } +} + +/// return the type of the object +JSON::json_t JSON::type() const { + return _type; +} + +/// direct access to the underlying payload +void* JSON::data() { + return _payload; +} + +/// direct access to the underlying payload +const void* JSON::data() const { + return _payload; +} + +/// lexicographically compares the values +bool JSON::operator==(const JSON& o) const { + switch (_type) { + case (array): { + if (o._type == array) { + std::vector* a = static_cast*>(_payload); + std::vector* b = static_cast*>(o._payload); + return *a == *b; + } + } + case (object): { + if (o._type == object) { + std::map* a = static_cast*>(_payload); + std::map* b = static_cast*>(o._payload); + return *a == *b; + } + } + case (null): { + if (o._type == null) { + return true; + } + } + case (string): { + if (o._type == string) { + const std::string a = *this; + const std::string b = o; + return a == b; + } + } + case (boolean): { + if (o._type == boolean) { + bool a = *this; + bool b = o; + return a == b; + } + } + case (number_int): { + if (o._type == number_int or o._type == number_float) { + int a = *this; + int b = o; + return a == b; + } + } + case (number_float): { + if (o._type == number_int or o._type == number_float) { + double a = *this; + double b = o; + return a == b; + } + } + } + + return false; +} + + + +/// return the type as string +std::string JSON::_typename() const { + switch (_type) { + case (array): + return "array"; + case (object): + return "object"; + case (null): + return "null"; + case (string): + return "string"; + case (boolean): + return "boolean"; + case (number_int): + return "number"; + case (number_float): + return "number"; + } +} + + +JSON::parser::parser(char* s) : _pos(0) { + _buffer = new char[strlen(s) + 1]; + strcpy(_buffer, s); + + // read first character + next(); +} + +JSON::parser::parser(std::string& s) : _pos(0) { + _buffer = new char[s.length() + 1]; + strcpy(_buffer, s.c_str()); + + // read first character + next(); +} + +JSON::parser::parser(std::istream& _is) : _pos(0) { + // determine length of input stream + _is.seekg(0, std::ios::end); + std::streampos length = _is.tellg(); + _is.seekg(0, std::ios::beg); + + // copy stream to buffer + _buffer = new char[length]; + _is.read(_buffer, length); + + // read first character + next(); +} + +JSON::parser::~parser() { + delete [] _buffer; +} + +void JSON::parser::error(std::string msg) { + throw std::runtime_error("parse error at position " + to_string(_pos) + ": " + msg + ", last read: '" + _current + "'"); +} + +bool JSON::parser::next() { + _current = _buffer[_pos++]; + + if (_buffer == nullptr) { + return false; + } + + // skip trailing whitespace + while (std::isspace(_current)) { + _current = _buffer[_pos++]; + } + + return true; +} + +/// \todo: escaped strings +std::string JSON::parser::parseString() { + // get position of closing quote + const char* p = strchr(_buffer + _pos, '\"'); + + // check if quotes were found + if (!p) { + error("expected '\"'"); + } + + // copy string to return value + const size_t length = p - _buffer - _pos; + char* tmp = new char[length + 1]; + strncpy(tmp, _buffer + _pos, length); + std::string result(tmp); + delete [] tmp; + + // +1 to eat closing quote + _pos += length + 1; + + // read next character + next(); + + return result; +} + +void JSON::parser::parseTrue() { + if (strncmp(_buffer + _pos, "rue", 3)) { + error("expected true"); + } + + _pos += 3; + + // read next character + next(); +} + +void JSON::parser::parseFalse() { + if (strncmp(_buffer + _pos, "alse", 4)) { + error("expected false"); + } + + _pos += 4; + + // read next character + next(); +} + +void JSON::parser::parseNull() { + if (strncmp(_buffer + _pos, "ull", 3)) { + error("expected null"); + } + + _pos += 3; + + // read next character (optional?) + next(); +} + +void JSON::parser::expect(char c) { + if (_current != c) { + std::string msg = "expected '"; + msg.append(1, c); + msg += "'"; + error(msg.c_str()); + } else { + next(); + } +} + +void JSON::parser::parse(JSON& result) { + if (!_buffer) { + error("unexpected end of file"); + } + + //JSON result; + + switch (_current) { + case ('{'): { + // explicitly set result to object to cope with {} + result._type = object; + result._payload = new std::map; + + next(); + + // process nonempty object + if (_current != '}') { + do { + // key + const std::string key = parseString(); + + // colon + expect(':'); + + // value + parse(result[key]); + } while (_current == ',' && next()); + } + + // closing brace + expect('}'); + + break; + } + + case ('['): { + // explicitly set result to array to cope with [] + result._type = array; + result._payload = new std::vector; + + next(); + + // process nonempty array + if (_current != ']') { + size_t element_count = 0; + do { + // add a dummy value and continue parsing at its position + result += JSON(); + parse(result[element_count++]); + } while (_current == ',' && next()); + } + + // closing bracket + expect(']'); + + break; + } + + case ('\"'): { + result._type = string; + result._payload = new std::string(parseString()); + break; + } + + case ('t'): { + parseTrue(); + result._type = boolean; + result._payload = new bool(true); + break; + } + + case ('f'): { + parseFalse(); + result._type = boolean; + result._payload = new bool(false); + break; + } + + case ('n'): { + parseNull(); + // nothing to do with result: is null by default + break; + } + + default: { + if (std::isdigit(_current) || _current == '-') { + // collect number in tmp string + std::string tmp; + do { + tmp += _current; + next(); + } while (std::isdigit(_current) || _current == '.' || _current == 'e' || _current == 'E' || _current == '+' || _current == '-'); + + if (tmp.find(".") == std::string::npos) { + // integer (we use atof, because it can cope with e) + result._type = number_int; + result._payload = new int(std::atof(tmp.c_str())); + } else { + // float + result._type = number_float; + result._payload = new double(std::atof(tmp.c_str())); + } + break; + } else { + error("unexpected character"); + } + } + } +} + + + +// http://stackoverflow.com/questions/7758580/writing-your-own-stl-container/7759622#7759622 + +JSON::iterator JSON::begin() { + return JSON::iterator(this); +} +JSON::iterator JSON::end() { + return JSON::iterator(); +} + +JSON::iterator::iterator() : _object(nullptr), _vi(nullptr), _oi(nullptr) {} + +JSON::iterator::iterator(JSON* j) : _object(j), _vi(nullptr), _oi(nullptr) { + switch (_object->_type) { + case (array): { + _vi = new std::vector::iterator(static_cast*>(_object->_payload)->begin()); + break; + } + case (object): { + _oi = new std::map::iterator(static_cast*>(_object->_payload)->begin()); + break; + } + default: + break; + } +} + +JSON::iterator::iterator(const JSON::iterator& o) : _object(o._object), _vi(nullptr), _oi(nullptr) { + switch (_object->_type) { + case (array): { + _vi = new std::vector::iterator(static_cast*>(_object->_payload)->begin()); + break; + } + case (object): { + _oi = new std::map::iterator(static_cast*>(_object->_payload)->begin()); + break; + } + default: + break; + } +} + +JSON::iterator::~iterator() { + delete _vi; + delete _oi; +} + +JSON::iterator& JSON::iterator::operator=(const JSON::iterator& o) { + _object = o._object; + return *this; +} + +bool JSON::iterator::operator==(const JSON::iterator& o) const { + return _object == o._object; +} + +bool JSON::iterator::operator!=(const JSON::iterator& o) const { + return _object != o._object; +} + + +JSON::iterator& JSON::iterator::operator++() { + // iterator cannot be incremented + if (_object == nullptr) { + return *this; + } + + switch (_object->_type) { + case (array): { + if (++(*_vi) == static_cast*>(_object->_payload)->end()) { + _object = nullptr; + } + break; + } + case (object): { + if (++(*_oi) == static_cast*>(_object->_payload)->end()) { + _object = nullptr; + } + break; + } + default: { + _object = nullptr; + } + } + return *this; +} + +JSON& JSON::iterator::operator*() const { + if (_object == nullptr) { + throw std::runtime_error("cannot get value"); + } + + switch (_object->_type) { + case (array): + return **_vi; + case (object): + return (*_oi)->second; + default: + return *_object; + } +} + +JSON* JSON::iterator::operator->() const { + if (_object == nullptr) { + throw std::runtime_error("cannot get value"); + } + + switch (_object->_type) { + case (array): + return &(**_vi); + case (object): + return &((*_oi)->second); + default: + return _object; + } +} + +std::string JSON::iterator::key() const { + if (_object != nullptr and _object->_type == object) { + return (*_oi)->first; + } else { + throw std::runtime_error("cannot get key"); + } +} + +JSON& JSON::iterator::value() const { + if (_object == nullptr) { + throw std::runtime_error("cannot get value"); + } + + switch (_object->_type) { + case (array): + return **_vi; + case (object): + return (*_oi)->second; + default: + return *_object; + } +} + + + + +JSON::const_iterator JSON::begin() const { + return JSON::const_iterator(this); +} +JSON::const_iterator JSON::end() const { + return JSON::const_iterator(); +} + +JSON::const_iterator JSON::cbegin() const { + return JSON::const_iterator(this); +} +JSON::const_iterator JSON::cend() const { + return JSON::const_iterator(); +} + +JSON::const_iterator::const_iterator() : _object(nullptr), _vi(nullptr), _oi(nullptr) {} + +JSON::const_iterator::const_iterator(const JSON* j) : _object(j), _vi(nullptr), _oi(nullptr) { + switch (_object->_type) { + case (array): { + _vi = new std::vector::const_iterator(static_cast*>(_object->_payload)->begin()); + break; + } + case (object): { + _oi = new std::map::const_iterator(static_cast*>(_object->_payload)->begin()); + break; + } + default: + break; + } +} + +JSON::const_iterator::const_iterator(const JSON::const_iterator& o) : _object(o._object), _vi(nullptr), _oi(nullptr) { + switch (_object->_type) { + case (array): { + _vi = new std::vector::const_iterator(static_cast*>(_object->_payload)->begin()); + break; + } + case (object): { + _oi = new std::map::const_iterator(static_cast*>(_object->_payload)->begin()); + break; + } + default: + break; + } +} + +JSON::const_iterator::const_iterator(const JSON::iterator& o) : _object(o._object), _vi(nullptr), _oi(nullptr) { + switch (_object->_type) { + case (array): { + _vi = new std::vector::const_iterator(static_cast*>(_object->_payload)->begin()); + break; + } + case (object): { + _oi = new std::map::const_iterator(static_cast*>(_object->_payload)->begin()); + break; + } + default: + break; + } +} + +JSON::const_iterator::~const_iterator() { + delete _vi; + delete _oi; +} + +JSON::const_iterator& JSON::const_iterator::operator=(const JSON::const_iterator& o) { + _object = o._object; + return *this; +} + +bool JSON::const_iterator::operator==(const JSON::const_iterator& o) const { + return _object == o._object; +} + +bool JSON::const_iterator::operator!=(const JSON::const_iterator& o) const { + return _object != o._object; +} + + +JSON::const_iterator& JSON::const_iterator::operator++() { + // iterator cannot be incremented + if (_object == nullptr) { + return *this; + } + + switch (_object->_type) { + case (array): { + if (++(*_vi) == static_cast*>(_object->_payload)->end()) { + _object = nullptr; + } + break; + } + case (object): { + if (++(*_oi) == static_cast*>(_object->_payload)->end()) { + _object = nullptr; + } + break; + } + default: { + _object = nullptr; + } + } + return *this; +} + +const JSON& JSON::const_iterator::operator*() const { + if (_object == nullptr) { + throw std::runtime_error("cannot get value"); + } + + switch (_object->_type) { + case (array): + return **_vi; + case (object): + return (*_oi)->second; + default: + return *_object; + } +} + +const JSON* JSON::const_iterator::operator->() const { + if (_object == nullptr) { + throw std::runtime_error("cannot get value"); + } + + switch (_object->_type) { + case (array): + return &(**_vi); + case (object): + return &((*_oi)->second); + default: + return _object; + } +} + +std::string JSON::const_iterator::key() const { + if (_object != nullptr and _object->_type == object) { + return (*_oi)->first; + } else { + throw std::runtime_error("cannot get key"); + } +} + +const JSON& JSON::const_iterator::value() const { + if (_object == nullptr) { + throw std::runtime_error("cannot get value"); + } + + switch (_object->_type) { + case (array): + return **_vi; + case (object): + return (*_oi)->second; + default: + return *_object; + } +} diff --git a/src/JSON.h b/src/JSON.h new file mode 100644 index 00000000..d5bb223e --- /dev/null +++ b/src/JSON.h @@ -0,0 +1,267 @@ +#pragma once + +// a helper macro to detect C++11 compliant compilers +#if __cplusplus >= 201103L +#define __cplusplus11 +#endif + +// allow us to use "nullptr" everywhere +#ifndef nullptr +#define nullptr NULL +#endif + +#include +#include +#include + +// additional C++11 header +#ifdef __cplusplus11 +#include +#include +#endif + +class JSON { + private: +#ifdef __cplusplus11 + /// mutex to guard payload + static std::mutex _token; +#endif + + public: + /// possible types of a JSON object + typedef enum { + array, object, null, string, boolean, number_int, number_float + } json_t; + + private: + /// the type of this object + json_t _type; + + /// the payload + void* _payload; + + public: +#ifdef __cplusplus11 + /// a type for objects + typedef std::tuple object_t; + /// a type for arrays + typedef std::initializer_list array_t; +#endif + + public: + /// create an empty (null) object + JSON(); + /// create a string object from C++ string + JSON(const std::string&); + /// create a string object from C string + JSON(char*); + /// create a string object from C string + JSON(const char*); + /// create a Boolean object + JSON(const bool); + /// create a number object + JSON(const int); + /// create a number object + JSON(const double); +#ifdef __cplusplus11 + /// create from an initializer list (to an array) + JSON(array_t); + /// create from a mapping (to an object) + JSON(object_t); +#endif + + /// copy constructor + JSON(const JSON&); + +#ifdef __cplusplus11 + /// move constructor + JSON(JSON&&); +#endif + + /// copy assignment +#ifdef __cplusplus11 + JSON& operator=(JSON); +#else + JSON& operator=(const JSON&); +#endif + + /// destructor + ~JSON(); + + /// implicit conversion to string representation + operator const std::string() const; + /// implicit conversion to integer (only for numbers) + operator int() const; + /// implicit conversion to double (only for numbers) + operator double() const; + /// implicit conversion to Boolean (only for Booleans) + operator bool() 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(); + return o; + } + + /// read from stream + friend std::istream& operator>>(std::istream& i, JSON& j) { + parser(i).parse(j); + return i; + } + + /// read from stream + friend std::istream& operator<<(JSON& j, std::istream& i) { + parser(i).parse(j); + return i; + } + + /// explicit conversion to string representation (C++ style) + const std::string toString() const; + + /// add an object/array to an array + JSON& operator+=(const JSON&); + /// add a string to an array + JSON& operator+=(const std::string&); + /// add a string to an array + JSON& operator+=(const char*); + /// add a Boolean to an array + JSON& operator+=(bool); + /// add a number to an array + JSON& operator+=(int); + /// add a number to an array + JSON& operator+=(double); + + /// operator to set an element in an array + JSON& operator[](int); + /// operator to get an element in an array + const JSON& operator[](const int) const; + + /// operator to set an element in an object + JSON& operator[](const std::string&); + /// operator to set an element in an object + JSON& operator[](const char*); + /// operator to get an element in an object + const JSON& operator[](const std::string&) const; + + /// return the number of stored values + size_t size() const; + /// checks whether object is empty + bool empty() const; + + /// return the type of the object + json_t type() const; + + /// direct access to the underlying payload + void* data(); + /// direct access to the underlying payload + const void* data() const; + + /// lexicographically compares the values + bool operator==(const JSON&) const; + + private: + /// return the type as string + std::string _typename() const; + + // forward declaration to friend this class + public: + class const_iterator; + + public: + /// an iterator + class iterator { + friend class JSON::const_iterator; + public: + iterator(); + iterator(JSON*); + iterator(const iterator&); + ~iterator(); + + iterator& operator=(const iterator&); + bool operator==(const iterator&) const; + bool operator!=(const iterator&) const; + iterator& operator++(); + JSON& operator*() const; + JSON* operator->() const; + + /// getter for the key (in case of objects) + std::string key() const; + /// getter for the value + JSON& value() const; + + private: + /// a JSON value + JSON* _object; + /// an iterator for JSON arrays + std::vector::iterator* _vi; + /// an iterator for JSON objects + std::map::iterator* _oi; + }; + + /// a const iterator + class const_iterator { + public: + const_iterator(); + const_iterator(const JSON*); + const_iterator(const const_iterator&); + const_iterator(const iterator&); + ~const_iterator(); + + const_iterator& operator=(const const_iterator&); + bool operator==(const const_iterator&) const; + bool operator!=(const const_iterator&) const; + const_iterator& operator++(); + const JSON& operator*() const; + const JSON* operator->() const; + + /// getter for the key (in case of objects) + std::string key() const; + /// getter for the value + const JSON& value() const; + + private: + /// a JSON value + const JSON* _object; + /// an iterator for JSON arrays + std::vector::const_iterator* _vi; + /// an iterator for JSON objects + std::map::const_iterator* _oi; + }; + + public: + iterator begin(); + iterator end(); + const_iterator begin() const; + const_iterator end() const; + const_iterator cbegin() const; + const_iterator cend() const; + + private: + class parser { + public: + parser(char*); + parser(std::string&); + parser(std::istream&); + ~parser(); + void parse(JSON&); + + private: + bool next(); + void error(std::string = ""); + std::string parseString(); + void parseTrue(); + void parseFalse(); + void parseNull(); + void expect(char); + + char _current; + char* _buffer; + size_t _pos; + }; +}; diff --git a/test/JSON_test.cc b/test/JSON_test.cc new file mode 100644 index 00000000..683a6f5d --- /dev/null +++ b/test/JSON_test.cc @@ -0,0 +1,236 @@ +#include +#include +#include +#include +#include +#include + +void test_null() { + /* a null object */ + + // construct + JSON a, b; + + // copy assign + b = JSON(); + + // copy construct + JSON c(a); + + // copy construct + JSON d = a; + + // assign operator + JSON e = JSON(); + + // compare + assert(a == b); + + // type + assert(a.type() == JSON::null); + + // empty and size + assert(a.size() == 0); + assert(a.empty() == true); + + // output + std::cout << a << '\n'; + + // string represetations + assert(a.toString() == std::string("null")); + + // invalid conversion to int + try { + int i = 0; + i = a; + assert(false); + } catch (const std::exception& ex) { + assert(ex.what() == std::string("cannot cast null to JSON number")); + } + + // invalid conversion to double + try { + double f = 0; + f = a; + assert(false); + } catch (const std::exception& ex) { + assert(ex.what() == std::string("cannot cast null to JSON number")); + } + + // invalid conversion to bool + try { + bool b = a; + assert(false); + } catch (const std::exception& ex) { + assert(ex.what() == std::string("cannot cast null to JSON Boolean")); + } + + // invalid conversion to string + try { + std::string s = a; + assert(false); + } catch (const std::exception& ex) { + assert(ex.what() == std::string("cannot cast null to JSON string")); + } +} + +void test_string() { + /* a string object */ + + // construct + JSON a = "object a"; + JSON b; + + // copy assign + b = JSON("object a"); + + // copy construct + JSON c(a); + + // copy construct + JSON d = a; + + // assign operator + JSON e = JSON(""); + + // compare + assert(a == b); + + // type + assert(a.type() == JSON::string); + + // empty and size + assert(a.size() == 1); + assert(a.empty() == false); + + // output + std::cout << a << '\n'; + + // string represetations + assert(a.toString() == std::string("\"object a\"")); + + // invalid conversion to int + try { + int i = 0; + i = a; + assert(false); + } catch (const std::exception& ex) { + assert(ex.what() == std::string("cannot cast string to JSON number")); + } + + // invalid conversion to double + try { + double f = 0; + f = a; + assert(false); + } catch (const std::exception& ex) { + assert(ex.what() == std::string("cannot cast string to JSON number")); + } + + // invalid conversion to bool + try { + bool b = false; + b = a; + assert(false); + } catch (const std::exception& ex) { + assert(ex.what() == std::string("cannot cast string to JSON Boolean")); + } +} + +void test_array() { + JSON a; + a += JSON(); + a += 1; + a += 1.0; + a += true; + a += "string"; + + // type + assert(a.type() == JSON::array); + + // empty and size + assert(a.size() == 5); + assert(a.empty() == false); + + // output + std::cout << a << '\n'; + + // check for elements + assert(a[1] == JSON(1)); + assert(a[2] == JSON(1.0)); + assert(a[3] == JSON(true)); + assert(a[4] == JSON("string")); + + // invalid access to element + try { + a[5] = 1; + assert(false); + } catch (const std::exception& ex) { + assert(ex.what() == std::string("cannot access element at index 5")); + } + + // get elements + { + int i = a[1]; + double d = a[2]; + bool b = a[3]; + std::string s = a[4]; + } + + // set elements + a[1] = 2; + +#ifdef __cplusplus11 + // construction from initializer list + JSON b = {JSON(), 2, 1.0, true, "string"}; + assert(a == b); +#endif + + // iterators + for (JSON::iterator i = a.begin(); i != a.end(); ++i) { + std::cerr << *i << '\n'; + } + + for (JSON::const_iterator i = a.cbegin(); i != a.cend(); ++i) { + std::cerr << *i << '\n'; + } + +#ifdef __cplusplus11 + for (auto element : a) { + std::cerr << element << '\n'; + } +#endif +} + +void test_streaming() { + // stream text representation into stream + std::stringstream i; + i << "{ \"foo\": true, \"baz\": [1,2,3,4] }"; + + // create JSON from stream + { + JSON j, k; + i >> j; + k << i; + assert(j.toString() == k.toString()); + } + + // roundtrip + { + std::stringstream o; + JSON j, k; + i >> j; + j >> o; + o >> k; + assert(j.toString() == k.toString()); + } +} + +int main() { + test_null(); + test_string(); + test_array(); + test_streaming(); + + return 0; +}