Commit fbad7fac by Niels

2.0 preview

parent d1ac3d99
language: cpp language: cpp
compiler: compiler:
- gcc
- clang - clang
before_install:
- sudo pip install cpp-coveralls
before_script: before_script:
- autoreconf -iv - autoreconf -iv
- ./configure - ./configure
...@@ -10,3 +14,9 @@ before_script: ...@@ -10,3 +14,9 @@ before_script:
script: script:
- make - make
- make check - make check
after_success:
- make clean
- make json_unit CXXFLAGS="-fprofile-arcs -ftest-coverage"
- ./json_unit
- coveralls --exclude lib --exclude tests --gcov-options '\-lp'
This source diff could not be displayed because it is too large. You can view the blob instead.
noinst_PROGRAMS = json json98 json98benchmark noinst_PROGRAMS = json json_unit
TESTS = ./json ./json98 TESTS = ./json ./json_unit
FLAGS = -Wall -Wextra -pedantic -Weffc++ -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wmissing-declarations -Wmissing-include-dirs -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-overflow=5 -Wswitch -Wundef -Wno-unused -Wnon-virtual-dtor -Wreorder
json_SOURCES = src/JSON.cc src/JSON.h test/JSON_test.cc json_SOURCES = src/JSON.cc src/JSON.h test/JSON_test.cc
json_CXXFLAGS = -std=c++11 json_CXXFLAGS = $(FLAGS) -Weffc++ -std=c++11
json_CPPFLAGS = -I$(top_srcdir)/src json_CPPFLAGS = -I$(top_srcdir)/src
json98_SOURCES = src/JSON.cc src/JSON.h test/JSON_test.cc json_unit_SOURCES = src/JSON.cc src/JSON.h test/catch.hpp test/JSON_unit.cc
json98_CXXFLAGS = -std=c++98 json_unit_CXXFLAGS = $(FLAGS) -std=c++11
json98_CPPFLAGS = -I$(top_srcdir)/src json_unit_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/test -Dprivate=public
json98benchmark_SOURCES = src/JSON.cc src/JSON.h benchmark/JSON_benchmark.cc cppcheck:
json98benchmark_CXXFLAGS = -std=c++98 -O3 cppcheck --enable=all --inconclusive --std=c++11 src/JSON.*
json98benchmark_CPPFLAGS = -I$(top_srcdir)/src -DNDEBUG
svn-clean: maintainer-clean svn-clean: maintainer-clean
rm -fr configure INSTALL aclocal.m4 build-aux depcomp install-sh missing test-driver cover_html *.gcda *.gcno coverage*.info 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 for DIR in $(DIST_SUBDIRS) .; do rm -f $$DIR/Makefile.in; done
cover: pretty:
make clean astyle --style=allman --indent=spaces=4 --indent-modifiers \
make json98 CXXFLAGS+="--coverage -g -fprofile-arcs -ftest-coverage" CPPFLAGS+="-DNDEBUG" --indent-switches --indent-preproc-block --indent-preproc-define \
./json98 --indent-col1-comments --pad-oper --pad-header --align-pointer=type \
lcov --capture --directory . --output-file coverage98.info --align-reference=type --add-brackets --convert-tabs --close-templates \
genhtml coverage*.info --output-directory cover_html --show-details --title "$(PACKAGE_STRING)" --legend --demangle-cpp --lineend=linux --preserve-date --suffix=none \
$(SOURCES)
...@@ -6,9 +6,9 @@ ...@@ -6,9 +6,9 @@
There are myriads of [JSON](http://json.org) libraries out there, and each may even have its reason to exist. Our class had these design goals: There are myriads of [JSON](http://json.org) libraries out there, and each may even have its reason to exist. Our class had these design goals:
- **Trivial integration**. Our whole code consists of just two files: A header file `JSON.h` and a source file `JSON.cc`. That's it. No library, no subproject, no dependencies. The class is written in vanilla C++98 and -- if possible -- uses some features of C++11 such as move constructors. All in all, the class should require no adjustment of your compiler flags or project settings. - **Trivial integration**. Our whole code consists of just two files: A header file `JSON.h` and a source file `JSON.cc`. That's it. No library, no subproject, no dependencies. The class is written in vanilla C++11. All in all, the class should require no adjustment of your compiler flags or project settings.
- **Intiuitve syntax**. In languages such as Python, JSON feels like a first class data type. We used all the operator magic of C++ to achieve the same feeling in your code. Check out the [examples below](#examples) and you know, what I mean. - **Intuitive syntax**. In languages such as Python, JSON feels like a first class data type. We used all the operator magic of C++ to achieve the same feeling in your code. Check out the [examples below](#examples) and you know, what I mean.
Other aspects were not so important to us: Other aspects were not so important to us:
...@@ -16,7 +16,7 @@ Other aspects were not so important to us: ...@@ -16,7 +16,7 @@ Other aspects were not so important to us:
- **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). - **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. - **Rigorous 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.
## Integration ## Integration
...@@ -45,11 +45,17 @@ j["happy"] = true; ...@@ -45,11 +45,17 @@ j["happy"] = true;
// add a string that is stored as std::string // add a string that is stored as std::string
j["name"] = "Niels"; j["name"] = "Niels";
// add a null object
j["nothing"] = nullptr;
// 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 (C++11) // add an array that is stored as std::vector
j["list"] = { 1, 0, 2 }; j["list"] = { 1, 0, 2 };
// add another object
j["object"] = { {"currency", "USD"}, {"value", "42.99"} };
``` ```
### Input / Output ### Input / Output
...@@ -65,6 +71,11 @@ std::cout << j; ...@@ -65,6 +71,11 @@ std::cout << j;
These operators work for any subclasses of `std::istream` or `std::ostream`. These operators work for any subclasses of `std::istream` or `std::ostream`.
```cpp
// create object from string literal
JSON j = "{ \"pi\": 3.141, \"happy\": true }"_json;
```
### STL-like access ### STL-like access
```cpp ```cpp
...@@ -79,13 +90,13 @@ for (JSON::iterator it = j.begin(); it != j.end(); ++it) { ...@@ -79,13 +90,13 @@ for (JSON::iterator it = j.begin(); it != j.end(); ++it) {
std::cout << *it << '\n'; std::cout << *it << '\n';
} }
// C++11 style // range-based for
for (auto element : j) { for (auto element : j) {
std::cout << element << '\n'; std::cout << element << '\n';
} }
// getter/setter // getter/setter
std::string tmp = j[0]; const std::string tmp = j[0];
j[1] = 42; j[1] = 42;
// other stuff // other stuff
......
#include <iostream>
#include <fstream>
#include <ctime>
#include <JSON.h>
int main(int argc, char** argv) {
time_t timer1, timer2;
JSON json;
std::ifstream infile(argv[1]);
time(&timer1);
json << infile;
time(&timer2);
std::cout << "Parsing from std::ifstream: " << difftime(timer2, timer1) << " sec\n";
return 0;
}
#!/usr/bin/env python
import json
import sys
import datetime
a = datetime.datetime.now()
data = json.loads(open(sys.argv[1]).read())
b = datetime.datetime.now()
print (b-a)
#!/bin/sh
git clone https://github.com/zeMirco/sf-city-lots-json.git
mv sf-city-lots-json/citylots.json .
rm -fr sf-city-lots-json
wget http://eu.battle.net/auction-data/258993a3c6b974ef3e6f22ea6f822720/auctions.json
AC_INIT([JSON], [1.0], [niels.lohmann@uni-rostock.de]) AC_INIT([JSON], [2.0], [mail@nlohmann.me])
AC_CONFIG_SRCDIR([src/JSON.cc]) AC_CONFIG_SRCDIR([src/JSON.cc])
AM_INIT_AUTOMAKE([foreign subdir-objects]) AM_INIT_AUTOMAKE([foreign subdir-objects])
......
#include "JSON.h" #include "JSON.h"
#include <utility> #include <cassert> // assert
#include <stdexcept> #include <cctype> // std::isdigit, std::isspace
#include <fstream> #include <cstddef> // size_t
#include <cctype> #include <cstdlib> // std::atof
#include <iostream> #include <cstring> // std::strlen, std::strchr, std::strcpy, std::strncmp
#include <streambuf> #include <stdexcept> // std::runtime_error
#include <sstream> #include <utility> // std::swap, std::move
#include <cstring>
#include <cstdlib>
// allow us to use "nullptr" everywhere
#include <cstddef>
#ifndef nullptr
#define nullptr NULL
#endif
/////////////////////
// HELPER FUNCTION //
/////////////////////
#ifndef __cplusplus11
inline std::string int_to_string(int i) {
std::stringstream s;
s << i;
return s.str();
}
#endif
//////////////////// ////////////////////
// STATIC MEMBERS // // STATIC MEMBERS //
//////////////////// ////////////////////
#ifdef __cplusplus11
/// a mutex to ensure thread safety
std::mutex JSON::_token; std::mutex JSON::_token;
#endif
///////////////////////////////////
// CONSTRUCTORS OF UNION "value" //
///////////////////////////////////
JSON::value::value(array_t* _array): array(_array) {}
JSON::value::value(object_t* _object): object(_object) {}
JSON::value::value(string_t* _string): string(_string) {}
JSON::value::value(boolean_t _boolean) : boolean(_boolean) {}
JSON::value::value(number_t _number) : number(_number) {}
JSON::value::value(number_float_t _number_float) : number_float(_number_float) {}
///////////////////////////////// /////////////////////////////////
// CONSTRUCTORS AND DESTRUCTOR // // CONSTRUCTORS AND DESTRUCTOR //
///////////////////////////////// /////////////////////////////////
JSON::JSON() : _type(null) {} /*!
Construct an empty JSON given the type.
JSON::JSON(json_t type) : _type(type) { @param t the type from the @ref JSON::type enumeration.
switch (_type) {
case (array): { @post Memory for array, object, and string are allocated.
@test Tested in block "Basics" for every type.
*/
JSON::JSON(const value_type t) noexcept
: _type(t)
{
switch (_type)
{
case (value_type::array):
{
_value.array = new array_t(); _value.array = new array_t();
break; break;
} }
case (object): { case (value_type::object):
{
_value.object = new object_t(); _value.object = new object_t();
break; break;
} }
case (string): { case (value_type::string):
{
_value.string = new string_t(); _value.string = new string_t();
break; break;
} }
case (boolean): { case (value_type::boolean):
_value.boolean = false; {
_value.boolean = boolean_t();
break; break;
} }
case (number): { case (value_type::number):
_value.number = 0; {
_value.number = number_t();
break; break;
} }
case (number_float): { case (value_type::number_float):
_value.number_float = 0.0; {
_value.number_float = number_float_t();
break; break;
} }
case (null): { case (value_type::null):
{
break; break;
} }
} }
} }
JSON::JSON(const std::string& s) : _type(string), _value(new string_t(s)) {} /*!
JSON::JSON(const char* s) : _type(string), _value(new string_t(s)) {} Construct a null JSON object.
JSON::JSON(char* s) : _type(string), _value(new string_t(s)) {}
JSON::JSON(const bool b) : _type(boolean), _value(b) {} @test Tested in block "Create from value" for "null".
JSON::JSON(const int i) : _type(number), _value(i) {} */
JSON::JSON(const double f) : _type(number_float), _value(f) {} JSON::JSON(std::nullptr_t) noexcept : JSON()
JSON::JSON(array_t a) : _type(array), _value(new array_t(a)) {} {}
JSON::JSON(object_t o) : _type(object), _value(new object_t(o)) {}
/*!
Construct a string JSON object.
@param s a string to initialize the JSON object with
@test Tested in block "Create from value" for "string".
*/
JSON::JSON(const std::string& s) noexcept
: _type(value_type::string), _value(new string_t(s))
{}
/*!
@test Tested in block "Create from value" for "string".
*/
JSON::JSON(std::string&& s) noexcept
: _type(value_type::string), _value(new string_t(std::move(s)))
{}
/*!
@test Tested in block "Create from value" for "string".
*/
JSON::JSON(const char* s) noexcept
: _type(value_type::string), _value(new string_t(s))
{}
/*!
@test Tested in block "Create from value" for "boolean".
*/
JSON::JSON(const bool b) noexcept
: _type(value_type::boolean), _value(b)
{}
/*!
@test Tested in block "Create from value" for "number (int)".
*/
JSON::JSON(const int i) noexcept
: _type(value_type::number), _value(i)
{}
/*!
@test Tested in block "Create from value" for "number (float)".
*/
JSON::JSON(const double f) noexcept
: _type(value_type::number_float), _value(f)
{}
/*!
@test Tested in block "Create from value" for "array".
*/
JSON::JSON(const array_t& a) noexcept
: _type(value_type::array), _value(new array_t(a))
{}
/*!
@test Tested in block "Create from value" for "array".
*/
JSON::JSON(array_t&& a) noexcept
: _type(value_type::array), _value(new array_t(std::move(a)))
{}
/*!
@test Tested in block "Create from value" for "array".
*/
JSON::JSON(const object_t& o) noexcept
: _type(value_type::object), _value(new object_t(o))
{}
/*!
@test Tested in block "Create from value" for "array".
*/
JSON::JSON(object_t&& o) noexcept
: _type(value_type::object), _value(new object_t(std::move(o)))
{}
/*!
This function is a bit tricky as it uses an initializer list of JSON objects
for both arrays and objects. This is not supported by C++, so we use the
following trick. Both initializer lists for objects and arrays will transform
to a list of JSON objects. The only difference is that in case of an object,
the list will contain JSON array objects with two elements - one for the key
and one for the value. As a result, it is sufficient to check if each element
of the initializer list is an array (1) with two elements (2) whose first
element is of type string (3). If this is the case, we treat the whole
initializer list as list of pairs to construct an object. If not, we pass it
as is to create an array.
@bug With the described approach, we would fail to recognize an array whose
first element is again an arrays as array.
@todo Create test case for described bug.
@test Tested in block "Create from value" for "array".
@test Tested in block "Create from value" for "object".
*/
JSON::JSON(list_init_t a) noexcept
{
// check if each element is an array with two elements whose first element
// is a string
for (const auto& element : a)
{
if (element.type() != value_type::array or
element.size() != 2 or
element[0].type() != value_type::string)
{
// the initializer list describes an array
_type = value_type::array;
_value = new array_t(std::move(a));
return;
}
}
// the initializer list is a list of pairs
_type = value_type::object;
_value = new object_t();
for (const JSON& element : a)
{
const std::string k = element[0];
_value.object->emplace(std::make_pair(std::move(k), std::move(element[1])));
}
}
/*!
A copy constructor for the JSON class.
#ifdef __cplusplus11 @param o the JSON object to copy
JSON::JSON(array_init_t a) : _type(array), _value(new array_t(a)) {}
#endif
/// copy constructor @test Tested in block "Basics" for every type.
JSON::JSON(const JSON& o) : _type(o._type) { */
switch (_type) { JSON::JSON(const JSON& o) noexcept
case (array): { : _type(o._type)
{
switch (_type)
{
case (value_type::array):
{
_value.array = new array_t(*o._value.array); _value.array = new array_t(*o._value.array);
break; break;
} }
case (object): { case (value_type::object):
{
_value.object = new object_t(*o._value.object); _value.object = new object_t(*o._value.object);
break; break;
} }
case (string): { case (value_type::string):
{
_value.string = new string_t(*o._value.string); _value.string = new string_t(*o._value.string);
break; break;
} }
case (boolean): { case (value_type::boolean):
{
_value.boolean = o._value.boolean; _value.boolean = o._value.boolean;
break; break;
} }
case (number): { case (value_type::number):
{
_value.number = o._value.number; _value.number = o._value.number;
break; break;
} }
case (number_float): { case (value_type::number_float):
{
_value.number_float = o._value.number_float; _value.number_float = o._value.number_float;
break; break;
} }
case (null): { case (value_type::null):
{
break; break;
} }
} }
} }
#ifdef __cplusplus11 /*!
/// move constructor A move constructor for the JSON class.
JSON::JSON(JSON&& o) : _type(std::move(o._type)), _value(std::move(o._value)) {}
#endif
/// copy assignment @param o the JSON object to move
#ifdef __cplusplus11
JSON& JSON::operator=(JSON o) { @post The JSON object \p o is invalidated.
@test Tested in block "Basics" for every type.
*/
JSON::JSON(JSON&& o) noexcept
: _type(std::move(o._type)), _value(std::move(o._value))
{
// invalidate payload
o._value = {};
}
/*!
A copy assignment operator for the JSON class, following the copy-and-swap
idiom.
@param o A JSON object to assign to this object.
@test Tested in block "Basics" for every type.
*/
JSON& JSON::operator=(JSON o) noexcept
{
std::swap(_type, o._type); std::swap(_type, o._type);
std::swap(_value, o._value); std::swap(_value, o._value);
return *this; return *this;
} }
#else
JSON& JSON::operator=(const JSON& o) {
// check for self-assignment
if (&o == this) {
return *this;
}
// first delete original value /*!
switch (_type) { @test Tested in block "Basics" for every type.
case (array): { */
JSON::~JSON() noexcept
{
switch (_type)
{
case (value_type::array):
{
delete _value.array; delete _value.array;
break; break;
} }
case (object): { case (value_type::object):
{
delete _value.object; delete _value.object;
break; break;
} }
case (string): { case (value_type::string):
{
delete _value.string; delete _value.string;
break; break;
} }
case (boolean): { case (value_type::boolean):
break; case (value_type::number):
} case (value_type::number_float):
case (number): { case (value_type::null):
break; {
} // nothing to do for non-pointer types
case (number_float): {
break;
}
case (null): {
break;
}
}
// then copy given value from o
_type = o._type;
switch (_type) {
case (array): {
_value.array = new array_t(*o._value.array);
break;
}
case (object): {
_value.object = new object_t(*o._value.object);
break;
}
case (string): {
_value.string = new string_t(*o._value.string);
break;
}
case (boolean): {
_value.boolean = o._value.boolean;
break;
}
case (number): {
_value.number = o._value.number;
break;
}
case (number_float): {
_value.number_float = o._value.number_float;
break;
}
case (null): {
break; break;
} }
} }
return *this;
} }
#endif
/// destructor /*!
JSON::~JSON() { @param s a string representation of a JSON object
switch (_type) { @return a JSON object
case (array): { */
delete _value.array; JSON JSON::parse(std::string& s)
break; {
JSON j;
Parser(s).parse(j);
return j;
}
/*!
@param s a string representation of a JSON object
@return a JSON object
@test Teste in block "Parser" for every type.
*/
JSON JSON::parse(const char* s)
{
JSON j;
Parser(s).parse(j);
return j;
}
/*!
@test Tested in block "Basics" for every type.
*/
const std::string JSON::_typename() const noexcept
{
switch (_type)
{
case (value_type::array):
{
return "array";
} }
case (object): { case (value_type::object):
delete _value.object; {
break; return "object";
} }
case (string): { case (value_type::null):
delete _value.string; {
break; return "null";
} }
case (boolean): { case (value_type::string):
break; {
return "string";
} }
case (number): { case (value_type::boolean):
break; {
return "boolean";
} }
case (number_float): { case (value_type::number):
break; {
return "number";
} }
case (null): { case (value_type::number_float):
break; {
return "number";
} }
} }
} }
...@@ -242,119 +394,234 @@ JSON::~JSON() { ...@@ -242,119 +394,234 @@ JSON::~JSON() {
// OPERATORS AND CONVERSIONS // // OPERATORS AND CONVERSIONS //
/////////////////////////////// ///////////////////////////////
JSON::operator const std::string() const { /*!
switch (_type) { @exception std::logic_error if the function is called for JSON objects whose
case (string): type is not string
@test Tested in block "Basics" for every type (including exceptions).
*/
template<>
std::string JSON::get() const
{
switch (_type)
{
case (value_type::string):
return *_value.string; return *_value.string;
default: default:
throw std::runtime_error("cannot cast " + _typename() + " to JSON string"); throw std::logic_error("cannot cast " + _typename() + " to JSON string");
} }
} }
/*!
@exception std::logic_error if the function is called for JSON objects whose
type is not number (int or float)
JSON::operator int() const { @test Tested in block "Basics" for every type (including exceptions).
switch (_type) { */
case (number): template<>
int JSON::get() const
{
switch (_type)
{
case (value_type::number):
return _value.number; return _value.number;
case (number_float): case (value_type::number_float):
return static_cast<number_t>(_value.number_float); return static_cast<number_t>(_value.number_float);
default: default:
throw std::runtime_error("cannot cast " + _typename() + " to JSON number"); throw std::logic_error("cannot cast " + _typename() + " to JSON number");
} }
} }
JSON::operator double() const { /*!
switch (_type) { @exception std::logic_error if the function is called for JSON objects whose
case (number): type is not number (int or float)
@test Tested in block "Basics" for every type (including exceptions).
*/
template<>
double JSON::get() const
{
switch (_type)
{
case (value_type::number):
return static_cast<number_float_t>(_value.number); return static_cast<number_float_t>(_value.number);
case (number_float): case (value_type::number_float):
return _value.number_float; return _value.number_float;
default: default:
throw std::runtime_error("cannot cast " + _typename() + " to JSON number"); throw std::logic_error("cannot cast " + _typename() + " to JSON number");
} }
} }
JSON::operator bool() const { /*!
switch (_type) { @exception std::logic_error if the function is called for JSON objects whose
case (boolean): type is not boolean
@test Tested in block "Basics" for every type (including exceptions).
*/
template<>
bool JSON::get() const
{
switch (_type)
{
case (value_type::boolean):
return _value.boolean; return _value.boolean;
default: default:
throw std::runtime_error("cannot cast " + _typename() + " to JSON Boolean"); throw std::logic_error("cannot cast " + _typename() + " to JSON Boolean");
} }
} }
JSON::operator std::vector<JSON>() const { /*!
if (_type == array) { @exception std::logic_error if the function is called for JSON objects whose
type is an object
@test Tested in block "Basics" for every type (including exceptions).
*/
template<>
JSON::array_t JSON::get() const
{
if (_type == value_type::array)
{
return *_value.array; return *_value.array;
} }
if (_type == object) { if (_type == value_type::object)
throw std::runtime_error("cannot cast " + _typename() + " to JSON array"); {
throw std::logic_error("cannot cast " + _typename() + " to JSON array");
} }
std::vector<JSON> result; array_t result;
result.push_back(*this); result.push_back(*this);
return result; return result;
} }
JSON::operator std::map<std::string, JSON>() const { /*!
if (_type == object) { @exception std::logic_error if the function is called for JSON objects whose
type is not object
@test Tested in block "Basics" for every type (including exceptions).
*/
template<>
JSON::object_t JSON::get() const
{
if (_type == value_type::object)
{
return *_value.object; return *_value.object;
} else { }
throw std::runtime_error("cannot cast " + _typename() + " to JSON object"); else
{
throw std::logic_error("cannot cast " + _typename() + " to JSON object");
} }
} }
const std::string JSON::toString() const { /*!
switch (_type) { @test Tested in block "Basics" for every type.
case (null): { */
JSON::operator const std::string() const
{
return get<std::string>();
}
/*!
@test Tested in block "Basics" for every type.
@test Tested in block "Create from value" for "number (int)".
*/
JSON::operator int() const
{
return get<int>();
}
/*!
@test Tested in block "Basics" for every type.
@test Tested in block "Create from value" for "number (float)".
*/
JSON::operator double() const
{
return get<double>();
}
/*!
@test Tested in block "Basics" for every type.
@test Tested in block "Create from value" for "boolean".
*/
JSON::operator bool() const
{
return get<bool>();
}
/*!
@test Tested in block "Basics" for every type.
*/
JSON::operator array_t() const
{
return get<array_t>();
}
/*!
@test Tested in block "Basics" for every type.
*/
JSON::operator object_t() const
{
return get<object_t>();
}
/*!
@test Tested in block "Basics" for every type.
*/
const std::string JSON::toString() const noexcept
{
switch (_type)
{
case (value_type::null):
{
return "null"; return "null";
} }
case (string): { case (value_type::string):
{
return std::string("\"") + *_value.string + "\""; return std::string("\"") + *_value.string + "\"";
} }
case (boolean): { case (value_type::boolean):
{
return _value.boolean ? "true" : "false"; return _value.boolean ? "true" : "false";
} }
case (number): { case (value_type::number):
#ifdef __cplusplus11 {
return std::to_string(_value.number); return std::to_string(_value.number);
#else
return int_to_string(_value.number);
#endif
} }
case (number_float): { case (value_type::number_float):
#ifdef __cplusplus11 {
return std::to_string(_value.number_float); return std::to_string(_value.number_float);
#else
return int_to_string(_value.number_float);
#endif
} }
case (array): { case (value_type::array):
{
std::string result; std::string result;
for (array_t::const_iterator i = _value.array->begin(); i != _value.array->end(); ++i) { for (array_t::const_iterator i = _value.array->begin(); i != _value.array->end(); ++i)
if (i != _value.array->begin()) { {
if (i != _value.array->begin())
{
result += ", "; result += ", ";
} }
result += (*i).toString(); result += i->toString();
} }
return "[" + result + "]"; return "[" + result + "]";
} }
case (object): { case (value_type::object):
{
std::string result; std::string result;
for (object_t::const_iterator i = _value.object->begin(); i != _value.object->end(); ++i) { for (object_t::const_iterator i = _value.object->begin(); i != _value.object->end(); ++i)
if (i != _value.object->begin()) { {
if (i != _value.object->begin())
{
result += ", "; result += ", ";
} }
result += "\"" + i->first + "\": " + (i->second).toString(); result += "\"" + i->first + "\": " + i->second.toString();
} }
return "{" + result + "}"; return "{" + result + "}";
...@@ -367,971 +634,1891 @@ const std::string JSON::toString() const { ...@@ -367,971 +634,1891 @@ const std::string JSON::toString() const {
// ADDING ELEMENTS TO OBJECTS AND ARRAYS // // ADDING ELEMENTS TO OBJECTS AND ARRAYS //
/////////////////////////////////////////// ///////////////////////////////////////////
JSON& JSON::operator+=(const JSON& o) { /*!
@test Tested in block "Array operators" for "array".
*/
JSON& JSON::operator+=(const JSON& o)
{
push_back(o); push_back(o);
return *this; return *this;
} }
JSON& JSON::operator+=(const std::string& s) { /*!
@test Tested in block "Array operators" for "array".
*/
JSON& JSON::operator+=(const std::string& s)
{
push_back(JSON(s)); push_back(JSON(s));
return *this; return *this;
} }
JSON& JSON::operator+=(const char* s) { /*!
@test Tested in block "Array operators" for "array".
*/
JSON& JSON::operator+=(const char* s)
{
push_back(JSON(s)); push_back(JSON(s));
return *this; return *this;
} }
JSON& JSON::operator+=(bool b) { /*!
@test Tested in block "Array operators" for "array".
*/
JSON& JSON::operator+=(std::nullptr_t)
{
push_back(JSON());
return *this;
}
/*!
@test Tested in block "Array operators" for "array".
*/
JSON& JSON::operator+=(bool b)
{
push_back(JSON(b)); push_back(JSON(b));
return *this; return *this;
} }
JSON& JSON::operator+=(int i) { /*!
Adds a number (int) to the current object. This is done by wrapping the number
into a JSON and call push_back for this.
@param i A number (int) to add to the array.
@test Tested in block "Array operators" for "array".
*/
JSON& JSON::operator+=(int i)
{
push_back(JSON(i)); push_back(JSON(i));
return *this; return *this;
} }
JSON& JSON::operator+=(double f) { /*!
Adds a number (float) to the current object. This is done by wrapping the
number into a JSON and call push_back for this.
@param f A number (float) to add to the array.
@test Tested in block "Array operators" for "array".
*/
JSON& JSON::operator+=(double f)
{
push_back(JSON(f)); push_back(JSON(f));
return *this; return *this;
} }
void JSON::push_back(const JSON& o) { /*!
#ifdef __cplusplus11 @todo comment me; test me
std::lock_guard<std::mutex> lg(_token); */
#endif JSON& JSON::operator+=(const object_t::value_type& p)
{
return operator[](p.first) = p.second;
}
/*!
@todo comment me; test me
*/
JSON& JSON::operator+=(list_init_t a)
{
push_back(a);
return *this;
}
if (not(_type == null or _type == array)) { /*!
This function implements the actual "adding to array" function and is called
by all other push_back or operator+= functions. If the function is called for
an array, the passed element is added to the array.
@param o The element to add to the array.
@pre The JSON object is an array or null.
@post The JSON object is an array whose last element is the passed element o.
@exception std::runtime_error The function was called for a JSON type that
does not support addition to an array (e.g., int or string).
@note Null objects are silently transformed into an array before the addition.
@test Tested in block "Array operators" for "array".
*/
void JSON::push_back(const JSON& o)
{
// push_back only works for null objects or arrays
if (not(_type == value_type::null or _type == value_type::array))
{
throw std::runtime_error("cannot add element to " + _typename()); throw std::runtime_error("cannot add element to " + _typename());
} }
if (_type == null) { std::lock_guard<std::mutex> lg(_token);
_type = array;
// transform null object into an array
if (_type == value_type::null)
{
_type = value_type::array;
_value.array = new array_t; _value.array = new array_t;
} }
// add element to array
_value.array->push_back(o); _value.array->push_back(o);
} }
void JSON::push_back(const std::string& s) { /*!
This function implements the actual "adding to array" function and is called
by all other push_back or operator+= functions. If the function is called for
an array, the passed element is added to the array using move semantics.
@param o The element to add to the array.
@pre The JSON object is an array or null.
@post The JSON object is an array whose last element is the passed element o.
@post The element o is destroyed.
@exception std::runtime_error The function was called for a JSON type that
does not support addition to an array (e.g., int or string).
@note Null objects are silently transformed into an array before the addition.
@note This function applies move semantics for the given element.
@test Tested in block "Array operators" for "array".
*/
void JSON::push_back(JSON&& o)
{
// push_back only works for null objects or arrays
if (not(_type == value_type::null or _type == value_type::array))
{
throw std::runtime_error("cannot add element to " + _typename());
}
std::lock_guard<std::mutex> lg(_token);
// transform null object into an array
if (_type == value_type::null)
{
_type = value_type::array;
_value.array = new array_t;
}
// add element to array (move semantics)
_value.array->emplace_back(std::move(o));
}
/*!
@test Tested in block "Array operators" for "array".
*/
void JSON::push_back(const std::string& s)
{
push_back(JSON(s)); push_back(JSON(s));
} }
void JSON::push_back(const char* s) { /*!
@test Tested in block "Array operators" for "array".
*/
void JSON::push_back(const char* s)
{
push_back(JSON(s)); push_back(JSON(s));
} }
void JSON::push_back(bool b) { /*!
@test Tested in block "Array operators" for "array".
*/
void JSON::push_back(std::nullptr_t)
{
push_back(JSON());
}
/*!
@test Tested in block "Array operators" for "array".
*/
void JSON::push_back(bool b)
{
push_back(JSON(b)); push_back(JSON(b));
} }
void JSON::push_back(int i) { /*!
Adds a number (int) to the current object. This is done by wrapping the number
into a JSON and call push_back for this.
@param i A number (int) to add to the array.
@test Tested in block "Array operators" for "array".
*/
void JSON::push_back(int i)
{
push_back(JSON(i)); push_back(JSON(i));
} }
void JSON::push_back(double f) { /*!
Adds a number (float) to the current object. This is done by wrapping the
number into a JSON and call push_back for this.
@param f A number (float) to add to the array.
@test Tested in block "Array operators" for "array".
*/
void JSON::push_back(double f)
{
push_back(JSON(f)); push_back(JSON(f));
} }
/// operator to set an element in an object /*!
JSON& JSON::operator[](int index) { @todo comment me; test me
#ifdef __cplusplus11 */
std::lock_guard<std::mutex> lg(_token); void JSON::push_back(const object_t::value_type& p)
#endif {
operator[](p.first) = p.second;
}
/*!
@todo comment me; test me
*/
void JSON::push_back(list_init_t a)
{
bool is_array = false;
// check if each element is an array with two elements whose first element
// is a string
for (const auto& element : a)
{
if (element.type() != value_type::array or
element.size() != 2 or
element[0].type() != value_type::string)
{
// the initializer list describes an array
is_array = true;
break;
}
}
if (is_array)
{
for (const JSON& element : a)
{
push_back(element);
}
}
else
{
for (const JSON& element : a)
{
const object_t::value_type tmp {element[0].get<std::string>(), element[1]};
push_back(tmp);
}
}
}
/*!
This operator realizes read/write access to array elements given an integer
index. Bounds will not be checked.
@note The "index" variable should be of type size_t as it is compared against
size() and used in the at() function. However, the compiler will have
problems in case integer literals are used. In this case, an implicit
conversion to both size_t and JSON is possible. Therefore, we use int as
type and convert it to size_t where necessary.
@param index the index of the element to return from the array
@return reference to element for the given index
@pre Object is an array.
@exception std::domain_error if object is not an array
if (_type != array) { @test Tested in block "Array operators" for "array" (including exceptions).
#ifdef __cplusplus11 */
throw std::runtime_error("cannot add entry with index " + std::to_string(index) + " to " + _typename()); JSON& JSON::operator[](const int index)
#else {
throw std::runtime_error("cannot add entry with index " + int_to_string(index) + " to " + _typename()); // this [] operator only works for arrays
#endif if (_type != value_type::array)
{
throw std::domain_error("cannot add entry with index " +
std::to_string(index) + " to " + _typename());
} }
if (index >= (int)_value.array->size()) { std::lock_guard<std::mutex> lg(_token);
#ifdef __cplusplus11
throw std::runtime_error("cannot access element at index " + std::to_string(index)); // return reference to element from array at given index
#else return (*_value.array)[static_cast<size_t>(index)];
throw std::runtime_error("cannot access element at index " + int_to_string(index)); }
#endif
/*!
This operator realizes read-only access to array elements given an integer
index. Bounds will not be checked.
@note The "index" variable should be of type size_t as it is compared against
size() and used in the at() function. However, the compiler will have
problems in case integer literals are used. In this case, an implicit
conversion to both size_t and JSON is possible. Therefore, we use int as
type and convert it to size_t where necessary.
@param index the index of the element to return from the array
@return read-only reference to element for the given index
@pre Object is an array.
@exception std::domain_error if object is not an array
@test Tested in block "Array operators" for "array" (including exceptions).
*/
const JSON& JSON::operator[](const int index) const
{
// this [] operator only works for arrays
if (_type != value_type::array)
{
throw std::domain_error("cannot get entry with index " +
std::to_string(index) + " from " + _typename());
} }
return _value.array->at(index); // return element from array at given index
return (*_value.array)[static_cast<size_t>(index)];
} }
/// operator to get an element in an object /*!
const JSON& JSON::operator[](const int index) const { This function realizes read/write access to array elements given an integer
if (_type != array) { index. Bounds will be checked.
#ifdef __cplusplus11
throw std::runtime_error("cannot get entry with index " + std::to_string(index) + " from " + _typename()); @note The "index" variable should be of type size_t as it is compared against
#else size() and used in the at() function. However, the compiler will have
throw std::runtime_error("cannot get entry with index " + int_to_string(index) + " from " + _typename()); problems in case integer literals are used. In this case, an implicit
#endif conversion to both size_t and JSON is possible. Therefore, we use int as
type and convert it to size_t where necessary.
@param index the index of the element to return from the array
@return reference to element for the given index
@pre Object is an array.
@exception std::domain_error if object is not an array
@exception std::out_of_range if index is out of range (via std::vector::at)
@test Tested in block "Array operators" for "array" (including exceptions).
*/
JSON& JSON::at(const int index)
{
// this function only works for arrays
if (_type != value_type::array)
{
throw std::domain_error("cannot add entry with index " +
std::to_string(index) + " to " + _typename());
} }
if (index >= (int)_value.array->size()) { std::lock_guard<std::mutex> lg(_token);
#ifdef __cplusplus11
throw std::runtime_error("cannot access element at index " + std::to_string(index)); // return reference to element from array at given index
#else return _value.array->at(static_cast<size_t>(index));
throw std::runtime_error("cannot access element at index " + int_to_string(index)); }
#endif
/*!
This operator realizes read-only access to array elements given an integer
index. Bounds will be checked.
@note The "index" variable should be of type size_t as it is compared against
size() and used in the at() function. However, the compiler will have
problems in case integer literals are used. In this case, an implicit
conversion to both size_t and JSON is possible. Therefore, we use int as
type and convert it to size_t where necessary.
@param index the index of the element to return from the array
@return read-only reference to element for the given index
@pre Object is an array.
@exception std::domain_error if object is not an array
@exception std::out_of_range if index is out of range (via std::vector::at)
@test Tested in block "Array operators" for "array" (including exceptions).
*/
const JSON& JSON::at(const int index) const
{
// this function only works for arrays
if (_type != value_type::array)
{
throw std::domain_error("cannot get entry with index " +
std::to_string(index) + " from " + _typename());
} }
return _value.array->at(index); // return element from array at given index
return _value.array->at(static_cast<size_t>(index));
} }
/// operator to set an element in an object /*!
JSON& JSON::operator[](const std::string& key) { @copydoc JSON::operator[](const char* key)
#ifdef __cplusplus11 */
JSON& JSON::operator[](const std::string& key)
{
return operator[](key.c_str());
}
/*!
This operator realizes read/write access to object elements given a string
key.
@param key the key index of the element to return from the object
@return reference to a JSON object for the given key (null if key does not
exist)
@pre Object is an object or a null object.
@post null objects are silently converted to objects.
@exception std::domain_error if object is not an object (or null)
*/
JSON& JSON::operator[](const char* key)
{
std::lock_guard<std::mutex> lg(_token); std::lock_guard<std::mutex> lg(_token);
#endif
if (_type == null) { // implicitly convert null to object
_type = object; if (_type == value_type::null)
{
_type = value_type::object;
_value.object = new object_t; _value.object = new object_t;
} }
if (_type != object) { // this [] operator only works for objects
throw std::runtime_error("cannot add entry with key " + std::string(key) + " to " + _typename()); if (_type != value_type::object)
{
throw std::domain_error("cannot add entry with key " +
std::string(key) + " to " + _typename());
} }
if (_value.object->find(key) == _value.object->end()) { // if the key does not exist, create it
if (_value.object->find(key) == _value.object->end())
{
(*_value.object)[key] = JSON(); (*_value.object)[key] = JSON();
} }
// return reference to element from array at given index
return (*_value.object)[key]; return (*_value.object)[key];
} }
/// operator to set an element in an object /*!
JSON& JSON::operator[](const char* key) { This operator realizes read-only access to object elements given a string
#ifdef __cplusplus11 key.
std::lock_guard<std::mutex> lg(_token);
#endif
if (_type == null) { @param key the key index of the element to return from the object
_type = object; @return read-only reference to element for the given key
_value.object = new object_t;
}
if (_type != object) { @pre Object is an object.
throw std::runtime_error("cannot add entry with key " + std::string(key) + " to " + _typename()); @exception std::domain_error if object is not an object
@exception std::out_of_range if key is not found in object
*/
const JSON& JSON::operator[](const std::string& key) const
{
// this [] operator only works for objects
if (_type != value_type::object)
{
throw std::domain_error("cannot get entry with key " + std::string(key) + " from " + _typename());
} }
if (_value.object->find(key) == _value.object->end()) { // search for the key
(*_value.object)[key] = JSON(); const auto it = _value.object->find(key);
// make sure the key exists in the object
if (it == _value.object->end())
{
throw std::out_of_range("key " + key + " not found");
} }
return (*_value.object)[key]; // return element from array at given key
return it->second;
}
/*!
@copydoc JSON::at(const char* key)
*/
JSON& JSON::at(const std::string& key)
{
return at(key.c_str());
} }
/// operator to get an element in an object /*!
const JSON& JSON::operator[](const std::string& key) const { This function realizes read/write access to object elements given a string
if (_type != object) { key.
throw std::runtime_error("cannot get entry with key " + std::string(key) + " from " + _typename());
@param key the key index of the element to return from the object
@return reference to a JSON object for the given key (exception if key does not
exist)
@pre Object is an object.
@exception std::domain_error if object is not an object
@exception std::out_of_range if key was not found (via std::map::at)
*/
JSON& JSON::at(const char* key)
{
std::lock_guard<std::mutex> lg(_token);
// this function operator only works for objects
if (_type != value_type::object)
{
throw std::domain_error("cannot add entry with key " +
std::string(key) + " to " + _typename());
} }
if (_value.object->find(key) == _value.object->end()) { // return reference to element from array at given index
throw std::runtime_error("key " + key + " not found"); return _value.object->at(key);
} else { }
return _value.object->find(key)->second;
/*!
This operator realizes read-only access to object elements given a string
key.
@param key the key index of the element to return from the object
@return read-only reference to element for the given key
@pre Object is an object.
@exception std::domain_error if object is not an object
@exception std::out_of_range if key is not found (via std::map::at)
*/
const JSON& JSON::at(const std::string& key) const
{
// this [] operator only works for objects
if (_type != value_type::object)
{
throw std::domain_error("cannot get entry with key " +
std::string(key) + " from " + _typename());
} }
// return element from array at given key
return _value.object->at(key);
} }
/// return the number of stored values /*!
size_t JSON::size() const { Returns the size of the JSON object.
switch (_type) {
case (array): { @return the size of the JSON object; the size is the number of elements in
compounds (array and object), 1 for value types (true, false, number,
string), and 0 for null.
@invariant The size is reported as 0 if and only if empty() would return true.
@test Tested in block "Basics" for every type.
*/
size_t JSON::size() const noexcept
{
switch (_type)
{
case (value_type::array):
{
return _value.array->size(); return _value.array->size();
} }
case (object): { case (value_type::object):
{
return _value.object->size(); return _value.object->size();
} }
case (null): { case (value_type::null):
{
return 0; return 0;
} }
case (string): { case (value_type::boolean):
return 1; case (value_type::number):
} case (value_type::number_float):
case (boolean): { case (value_type::string):
return 1; {
}
case (number): {
return 1;
}
case (number_float): {
return 1; return 1;
} }
} }
} }
/// checks whether object is empty /*!
bool JSON::empty() const { Returns whether a JSON object is empty.
switch (_type) {
case (array): { @return true for null objects and empty compounds (array and object); false
for value types (true, false, number, string) and filled compounds
(array and object).
@invariant Empty would report true if and only if size() would return 0.
@test Tested in block "Basics" for every type.
*/
bool JSON::empty() const noexcept
{
switch (_type)
{
case (value_type::array):
{
return _value.array->empty(); return _value.array->empty();
} }
case (object): { case (value_type::object):
{
return _value.object->empty(); return _value.object->empty();
} }
case (null): { case (value_type::null):
{
return true; return true;
} }
case (string): { case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
case (value_type::string):
{
return false; return false;
} }
case (boolean): { }
return false; }
/*!
Removes all elements from compounds and resets values to default.
@invariant Clear will set any value type to its default value which is empty
for compounds, false for booleans, 0 for integer numbers, and 0.0
for floating numbers.
@todo Test me.
*/
void JSON::clear() noexcept
{
switch (_type)
{
case (value_type::array):
{
_value.array->clear();
break;
} }
case (number): { case (value_type::object):
return false; {
_value.object->clear();
break;
} }
case (number_float): { case (value_type::string):
return false; {
_value.string->clear();
break;
}
case (value_type::boolean):
{
_value.boolean = {};
break;
}
case (value_type::number):
{
_value.number = {};
break;
}
case (value_type::number_float):
{
_value.number_float = {};
break;
}
case (value_type::null):
{
break;
} }
} }
} }
/// return the type of the object /*!
JSON::json_t JSON::type() const { @test Tested in block "Basics" for every type.
*/
JSON::value_type JSON::type() const noexcept
{
return _type; return _type;
} }
JSON::iterator JSON::find(const std::string& key) { /*!
@test Tested in block "Object operators" for "object".
*/
JSON::iterator JSON::find(const std::string& key)
{
return find(key.c_str()); return find(key.c_str());
} }
JSON::const_iterator JSON::find(const std::string& key) const { /*!
@test Tested in block "Object operators" for "object".
*/
JSON::const_iterator JSON::find(const std::string& key) const
{
return find(key.c_str()); return find(key.c_str());
} }
JSON::iterator JSON::find(const char* key) { /*!
if (_type != object) { @test Tested in block "Object operators" for "object".
*/
JSON::iterator JSON::find(const char* key)
{
if (_type != value_type::object)
{
return end(); return end();
} else { }
else
{
const object_t::iterator i = _value.object->find(key); const object_t::iterator i = _value.object->find(key);
if (i != _value.object->end()) { if (i != _value.object->end())
{
JSON::iterator result(this); JSON::iterator result(this);
result._oi = new object_t::iterator(i); result._oi = new object_t::iterator(i);
return result; return result;
} else { }
else
{
return end(); return end();
} }
} }
} }
JSON::const_iterator JSON::find(const char* key) const { /*!
if (_type != object) { @test Tested in block "Object operators" for "object".
*/
JSON::const_iterator JSON::find(const char* key) const
{
if (_type != value_type::object)
{
return end(); return end();
} else { }
else
{
const object_t::const_iterator i = _value.object->find(key); const object_t::const_iterator i = _value.object->find(key);
if (i != _value.object->end()) { if (i != _value.object->end())
{
JSON::const_iterator result(this); JSON::const_iterator result(this);
result._oi = new object_t::const_iterator(i); result._oi = new object_t::const_iterator(i);
return result; return result;
} else { }
else
{
return end(); return end();
} }
} }
} }
/// direct access to the underlying payload /*!
JSON::value JSON::data() { @return the payload of the JSON object.
@test Tested in block "Basics" for every type.
*/
JSON::value JSON::data() noexcept
{
return _value; return _value;
} }
/// direct access to the underlying payload /*!
const JSON::value JSON::data() const { @test Tested in block "Basics" for every type.
*/
const JSON::value JSON::data() const noexcept
{
return _value; return _value;
} }
/// lexicographically compares the values /*!
bool JSON::operator==(const JSON& o) const { @test Tested in block "Basics" for every type.
switch (_type) { */
case (array): { bool JSON::operator==(const JSON& o) const noexcept
if (o._type == array) { {
switch (_type)
{
case (value_type::array):
{
if (o._type == value_type::array)
{
return *_value.array == *o._value.array; return *_value.array == *o._value.array;
} }
break;
} }
case (object): { case (value_type::object):
if (o._type == object) { {
if (o._type == value_type::object)
{
return *_value.object == *o._value.object; return *_value.object == *o._value.object;
} }
break;
} }
case (null): { case (value_type::null):
if (o._type == null) { {
if (o._type == value_type::null)
{
return true; return true;
} }
break;
} }
case (string): { case (value_type::string):
if (o._type == string) { {
if (o._type == value_type::string)
{
return *_value.string == *o._value.string; return *_value.string == *o._value.string;
} }
break;
} }
case (boolean): { case (value_type::boolean):
if (o._type == boolean) { {
if (o._type == value_type::boolean)
{
return _value.boolean == o._value.boolean; return _value.boolean == o._value.boolean;
} }
break;
} }
case (number): { case (value_type::number):
if (o._type == number) { {
if (o._type == value_type::number)
{
return _value.number == o._value.number; return _value.number == o._value.number;
} }
if (o._type == number_float) { if (o._type == value_type::number_float)
{
return _value.number == static_cast<number_t>(o._value.number_float); return _value.number == static_cast<number_t>(o._value.number_float);
} }
break;
} }
case (number_float): { case (value_type::number_float):
if (o._type == number) { {
if (o._type == value_type::number)
{
return _value.number_float == static_cast<number_float_t>(o._value.number); return _value.number_float == static_cast<number_float_t>(o._value.number);
} }
if (o._type == number_float) { if (o._type == value_type::number_float)
{
return _value.number_float == o._value.number_float; return _value.number_float == o._value.number_float;
} }
break;
} }
} }
return false; return false;
} }
/// lexicographically compares the values bool JSON::operator!=(const JSON& o) const noexcept
bool JSON::operator!=(const JSON& o) const { {
return not operator==(o); return not operator==(o);
} }
/// return the type as string /*!
std::string JSON::_typename() const { @test Tested in block "Iterators" for "array".
switch (_type) { @test Tested in block "Iterators" for "object".
case (array): { */
return "array"; JSON::iterator JSON::begin() noexcept
} {
case (object): { return JSON::iterator(this);
return "object";
}
case (null): {
return "null";
}
case (string): {
return "string";
}
case (boolean): {
return "boolean";
}
case (number): {
return "number";
}
case (number_float): {
return "number";
}
}
} }
/*!
JSON::parser::parser(char* s) : _pos(0) { @test Tested in block "Iterators" for "array".
_length = std::strlen(s); @test Tested in block "Iterators" for "object".
_buffer = new char[_length + 1]; */
std::strcpy(_buffer, s); JSON::iterator JSON::end() noexcept
{
// read first character return JSON::iterator();
next();
} }
JSON::parser::parser(std::string& s) : _pos(0) { /*!
_length = s.length(); @test Tested in block "Iterators" for "array".
_buffer = new char[_length + 1]; @test Tested in block "Iterators" for "object".
std::strcpy(_buffer, s.c_str()); */
JSON::const_iterator JSON::begin() const noexcept
// read first character {
next(); return JSON::const_iterator(this);
} }
JSON::parser::parser(std::istream& _is) : _pos(0) { /*!
// determine length of input stream @test Tested in block "Iterators" for "array".
_is.seekg(0, std::ios::end); @test Tested in block "Iterators" for "object".
_length = _is.tellg(); */
_is.seekg(0, std::ios::beg); JSON::const_iterator JSON::end() const noexcept
{
// copy stream to buffer return JSON::const_iterator();
_buffer = new char[_length + 1];
_is.read(_buffer, _length);
// read first character
next();
} }
JSON::parser::~parser() { /*!
delete [] _buffer; @test Tested in block "Iterators" for "array".
@test Tested in block "Iterators" for "object".
*/
JSON::const_iterator JSON::cbegin() const noexcept
{
return JSON::const_iterator(this);
} }
void JSON::parser::error(std::string msg = "") { /*!
#ifdef __cplusplus11 @test Tested in block "Iterators" for "array".
throw std::runtime_error("parse error at position " + std::to_string(_pos) + ": " + msg + ", last read: '" + _current + "'"); @test Tested in block "Iterators" for "object".
#else */
throw std::runtime_error("parse error at position " + int_to_string(_pos) + ": " + msg + ", last read: '" + _current + "'"); JSON::const_iterator JSON::cend() const noexcept
#endif {
return JSON::const_iterator();
} }
bool JSON::parser::next() {
if (_pos == _length) {
return false;
}
_current = _buffer[_pos++];
// skip trailing whitespace
while (std::isspace(_current)) {
if (_pos == _length) {
return false;
}
_current = _buffer[_pos++];
}
return true;
}
std::string JSON::parser::parseString() {
// get position of closing quotes
char* p = std::strchr(_buffer + _pos, '\"');
// if the closing quotes are escaped (viz. *(p-1) is '\\'),
// we continue looking for the "right" quotes
while (p != nullptr and * (p - 1) == '\\') {
// length of the string so far
const size_t length = p - _buffer - _pos;
// continue checking after escaped quote
p = std::strchr(_buffer + _pos + length + 1, '\"');
}
// check if closing quotes were found
if (p == nullptr) {
error("expected '\"'");
}
// copy string to return value
const size_t length = p - _buffer - _pos;
char* tmp = new char[length + 1];
std::strncpy(tmp, _buffer + _pos, length);
tmp[length] = 0;
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 (std::strncmp(_buffer + _pos, "rue", 3)) {
error("expected true");
}
_pos += 3; JSON::iterator::iterator(JSON* j) : _object(j)
{
// read next character
next();
}
void JSON::parser::parseFalse() {
if (std::strncmp(_buffer + _pos, "alse", 4)) {
error("expected false");
}
_pos += 4;
// read next character
next();
}
void JSON::parser::parseNull() {
if (std::strncmp(_buffer + _pos, "ull", 3)) {
error("expected null");
}
_pos += 3;
// read next character
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");
}
switch (_current) {
case ('{'): {
// explicitly set result to object to cope with {}
result._type = object;
result._value.object = new object_t;
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._value.array = new array_t;
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._value.string = new string_t(parseString());
break;
}
case ('t'): {
parseTrue();
result._type = boolean;
result._value.boolean = true;
break;
}
case ('f'): {
parseFalse();
result._type = boolean;
result._value.boolean = 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;
result._value.number = std::atof(tmp.c_str());
} else {
// float
result._type = number_float;
result._value.number_float = 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) {
if (_object != nullptr) if (_object != nullptr)
switch (_object->_type) { switch (_object->_type)
case (array): { {
case (value_type::array):
{
_vi = new array_t::iterator(_object->_value.array->begin()); _vi = new array_t::iterator(_object->_value.array->begin());
break; break;
} }
case (object): { case (value_type::object):
{
_oi = new object_t::iterator(_object->_value.object->begin()); _oi = new object_t::iterator(_object->_value.object->begin());
break; break;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
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)
{
if (_object != nullptr) if (_object != nullptr)
switch (_object->_type) { switch (_object->_type)
case (array): { {
case (value_type::array):
{
_vi = new array_t::iterator(*(o._vi)); _vi = new array_t::iterator(*(o._vi));
break; break;
} }
case (object): { case (value_type::object):
{
_oi = new object_t::iterator(*(o._oi)); _oi = new object_t::iterator(*(o._oi));
break; break;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
break; break;
} }
} }
} }
JSON::iterator::~iterator() { JSON::iterator::~iterator()
{
delete _vi; delete _vi;
delete _oi; delete _oi;
} }
JSON::iterator& JSON::iterator::operator=(const JSON::iterator& o) { /*!
_object = o._object; @test Tested in block "Iterators" for "array".
@test Tested in block "Iterators" for "object".
*/
JSON::iterator& JSON::iterator::operator=(JSON::iterator o)
{
std::swap(_object, o._object);
std::swap(_vi, o._vi);
std::swap(_oi, o._oi);
return *this;
}
/*!
@test Tested in block "Iterators" for "array".
@test Tested in block "Object operators" for "object".
*/
bool JSON::iterator::operator==(const JSON::iterator& o) const
{
if (_object != o._object)
{
return false;
}
if (_object != nullptr) if (_object != nullptr)
switch (_object->_type) { switch (_object->_type)
case (array): { {
_vi = new array_t::iterator(*(o._vi)); case (value_type::array):
{
if (_vi != o._vi)
{
return false;
}
break; break;
} }
case (object): { case (value_type::object):
_oi = new object_t::iterator(*(o._oi)); {
if (_oi != o._oi)
{
return false;
}
break; break;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
break; break;
} }
} }
return *this;
}
bool JSON::iterator::operator==(const JSON::iterator& o) const { return true;
return _object == o._object;
} }
bool JSON::iterator::operator!=(const JSON::iterator& o) const { /*!
return _object != o._object; @test Tested in block "Iterators" for "array".
@test Tested in block "Object operators" for "object".
@test Tested in block "Iterators" for "object".
*/
bool JSON::iterator::operator!=(const JSON::iterator& o) const
{
return not operator==(o);
} }
/*!
JSON::iterator& JSON::iterator::operator++() { @test Tested in block "Iterators" for "array".
@test Tested in block "Iterators" for "object".
*/
JSON::iterator& JSON::iterator::operator++()
{
// iterator cannot be incremented // iterator cannot be incremented
if (_object == nullptr) { if (_object == nullptr)
{
return *this; return *this;
} }
switch (_object->_type) { switch (_object->_type)
case (array): { {
if (++(*_vi) == _object->_value.array->end()) { case (value_type::array):
{
if (++(*_vi) == _object->_value.array->end())
{
_object = nullptr; _object = nullptr;
} }
break; break;
} }
case (object): { case (value_type::object):
if (++(*_oi) == _object->_value.object->end()) { {
if (++(*_oi) == _object->_value.object->end())
{
_object = nullptr; _object = nullptr;
} }
break; break;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
_object = nullptr; _object = nullptr;
} }
} }
return *this; return *this;
} }
JSON& JSON::iterator::operator*() const { /*!
if (_object == nullptr) { @test Tested in block "Iterators" for "array".
@test Tested in block "Iterators" for "object".
*/
JSON& JSON::iterator::operator*() const
{
if (_object == nullptr)
{
throw std::runtime_error("cannot get value"); throw std::runtime_error("cannot get value");
} }
switch (_object->_type) { switch (_object->_type)
case (array): { {
case (value_type::array):
{
return **_vi; return **_vi;
} }
case (object): { case (value_type::object):
{
return (*_oi)->second; return (*_oi)->second;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
return *_object; return *_object;
} }
} }
} }
JSON* JSON::iterator::operator->() const { /*!
if (_object == nullptr) { @test Tested in block "Iterators" for "array".
@test Tested in block "Iterators" for "object".
*/
JSON* JSON::iterator::operator->() const
{
if (_object == nullptr)
{
throw std::runtime_error("cannot get value"); throw std::runtime_error("cannot get value");
} }
switch (_object->_type) { switch (_object->_type)
case (array): { {
case (value_type::array):
{
return &(**_vi); return &(**_vi);
} }
case (object): { case (value_type::object):
{
return &((*_oi)->second); return &((*_oi)->second);
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
return _object; return _object;
} }
} }
} }
std::string JSON::iterator::key() const { /*!
if (_object != nullptr and _object->_type == object) { @test Tested in block "Object operators" for "object".
@test Tested in block "Iterators" for "object".
*/
std::string JSON::iterator::key() const
{
if (_object != nullptr and _object->_type == value_type::object)
{
return (*_oi)->first; return (*_oi)->first;
} else { }
throw std::runtime_error("cannot get key"); else
{
throw std::out_of_range("cannot get key");
} }
} }
JSON& JSON::iterator::value() const { /*!
if (_object == nullptr) { @test Tested in block "Object operators" for "object".
throw std::runtime_error("cannot get value"); @test Tested in block "Iterators" for "object".
*/
JSON& JSON::iterator::value() const
{
if (_object == nullptr)
{
throw std::out_of_range("cannot get value");
} }
switch (_object->_type) { switch (_object->_type)
case (array): { {
case (value_type::array):
{
return **_vi; return **_vi;
} }
case (object): { case (value_type::object):
{
return (*_oi)->second; return (*_oi)->second;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
return *_object; return *_object;
} }
} }
} }
JSON::const_iterator::const_iterator(const JSON* j) : _object(j)
{
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) {
if (_object != nullptr) if (_object != nullptr)
switch (_object->_type) { switch (_object->_type)
case (array): { {
case (value_type::array):
{
_vi = new array_t::const_iterator(_object->_value.array->begin()); _vi = new array_t::const_iterator(_object->_value.array->begin());
break; break;
} }
case (object): { case (value_type::object):
{
_oi = new object_t::const_iterator(_object->_value.object->begin()); _oi = new object_t::const_iterator(_object->_value.object->begin());
break; break;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
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)
{
if (_object != nullptr) if (_object != nullptr)
switch (_object->_type) { switch (_object->_type)
case (array): { {
case (value_type::array):
{
_vi = new array_t::const_iterator(*(o._vi)); _vi = new array_t::const_iterator(*(o._vi));
break; break;
} }
case (object): { case (value_type::object):
{
_oi = new object_t::const_iterator(*(o._oi)); _oi = new object_t::const_iterator(*(o._oi));
break; break;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
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)
{
if (_object != nullptr) if (_object != nullptr)
switch (_object->_type) { switch (_object->_type)
case (array): { {
case (value_type::array):
{
_vi = new array_t::const_iterator(*(o._vi)); _vi = new array_t::const_iterator(*(o._vi));
break; break;
} }
case (object): { case (value_type::object):
{
_oi = new object_t::const_iterator(*(o._oi)); _oi = new object_t::const_iterator(*(o._oi));
break; break;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
break; break;
} }
} }
} }
JSON::const_iterator::~const_iterator() { JSON::const_iterator::~const_iterator()
{
delete _vi; delete _vi;
delete _oi; delete _oi;
} }
JSON::const_iterator& JSON::const_iterator::operator=(const JSON::const_iterator& o) { /*!
_object = o._object; @test Tested in block "Iterators" for "array".
@test Tested in block "Iterators" for "object".
*/
JSON::const_iterator& JSON::const_iterator::operator=(JSON::const_iterator o)
{
std::swap(_object, o._object);
std::swap(_vi, o._vi);
std::swap(_oi, o._oi);
return *this;
}
/*!
@test Tested in block "Object operators" for "object".
@test Tested in block "Iterators" for "object".
*/
bool JSON::const_iterator::operator==(const JSON::const_iterator& o) const
{
if (_object != o._object)
{
return false;
}
if (_object != nullptr) if (_object != nullptr)
switch (_object->_type) { switch (_object->_type)
case (array): { {
_vi = new array_t::const_iterator(*(o._vi)); case (value_type::array):
{
if (_vi != o._vi)
{
return false;
}
break; break;
} }
case (object): { case (value_type::object):
_oi = new object_t::const_iterator(*(o._oi)); {
if (_oi != o._oi)
{
return false;
}
break; break;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
break; break;
} }
} }
return *this;
}
bool JSON::const_iterator::operator==(const JSON::const_iterator& o) const { return true;
return _object == o._object;
} }
bool JSON::const_iterator::operator!=(const JSON::const_iterator& o) const { /*!
return _object != o._object; @test Tested in block "Object operators" for "object".
@test Tested in block "Iterators" for "object".
*/
bool JSON::const_iterator::operator!=(const JSON::const_iterator& o) const
{
return not operator==(o);
} }
/*!
JSON::const_iterator& JSON::const_iterator::operator++() { @test Tested in block "Iterators" for "array".
@test Tested in block "Iterators" for "object".
*/
JSON::const_iterator& JSON::const_iterator::operator++()
{
// iterator cannot be incremented // iterator cannot be incremented
if (_object == nullptr) { if (_object == nullptr)
{
return *this; return *this;
} }
switch (_object->_type) { switch (_object->_type)
case (array): { {
if (++(*_vi) == _object->_value.array->end()) { case (value_type::array):
{
if (++(*_vi) == _object->_value.array->end())
{
_object = nullptr; _object = nullptr;
} }
break; break;
} }
case (object): { case (value_type::object):
if (++(*_oi) == _object->_value.object->end()) { {
if (++(*_oi) == _object->_value.object->end())
{
_object = nullptr; _object = nullptr;
} }
break; break;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
_object = nullptr; _object = nullptr;
} }
} }
return *this; return *this;
} }
const JSON& JSON::const_iterator::operator*() const { /*!
if (_object == nullptr) { @test Tested in block "Iterators" for "array".
@test Tested in block "Iterators" for "object".
*/
const JSON& JSON::const_iterator::operator*() const
{
if (_object == nullptr)
{
throw std::runtime_error("cannot get value"); throw std::runtime_error("cannot get value");
} }
switch (_object->_type) { switch (_object->_type)
case (array): { {
case (value_type::array):
{
return **_vi; return **_vi;
} }
case (object): { case (value_type::object):
{
return (*_oi)->second; return (*_oi)->second;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
return *_object; return *_object;
} }
} }
} }
const JSON* JSON::const_iterator::operator->() const { /*!
if (_object == nullptr) { @test Tested in block "Iterators" for "array".
@test Tested in block "Iterators" for "object".
*/
const JSON* JSON::const_iterator::operator->() const
{
if (_object == nullptr)
{
throw std::runtime_error("cannot get value"); throw std::runtime_error("cannot get value");
} }
switch (_object->_type) { switch (_object->_type)
case (array): { {
case (value_type::array):
{
return &(**_vi); return &(**_vi);
} }
case (object): { case (value_type::object):
{
return &((*_oi)->second); return &((*_oi)->second);
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
return _object; return _object;
} }
} }
} }
std::string JSON::const_iterator::key() const { /*!
if (_object != nullptr and _object->_type == object) { @test Tested in block "Object operators" for "object".
@test Tested in block "Iterators" for "object".
*/
std::string JSON::const_iterator::key() const
{
if (_object != nullptr and _object->_type == value_type::object)
{
return (*_oi)->first; return (*_oi)->first;
} else { }
throw std::runtime_error("cannot get key"); else
{
throw std::out_of_range("cannot get key");
} }
} }
const JSON& JSON::const_iterator::value() const { /*!
if (_object == nullptr) { @test Tested in block "Object operators" for "object".
throw std::runtime_error("cannot get value"); @test Tested in block "Iterators" for "object".
*/
const JSON& JSON::const_iterator::value() const
{
if (_object == nullptr)
{
throw std::out_of_range("cannot get value");
} }
switch (_object->_type) { switch (_object->_type)
case (array): { {
case (value_type::array):
{
return **_vi; return **_vi;
} }
case (object): { case (value_type::object):
{
return (*_oi)->second; return (*_oi)->second;
} }
default: { case (value_type::null):
case (value_type::string):
case (value_type::boolean):
case (value_type::number):
case (value_type::number_float):
{
return *_object; return *_object;
} }
} }
} }
/*!
Initialize the JSON parser given a string \p s.
@note After initialization, the function @ref parse has to be called manually.
@param s string to parse
@post \p s is copied to the buffer @ref _buffer and the firsr character is
read. Whitespace is skipped.
*/
JSON::Parser::Parser(const char* s)
: _length(std::strlen(s)), _buffer(new char[_length + 1])
{
std::strcpy(_buffer, s);
// read first character
next();
}
/*!
@copydoc JSON::Parser::Parser(const char* s)
*/
JSON::Parser::Parser(std::string& s)
: _length(s.length()), _buffer(new char[_length + 1])
{
std::strcpy(_buffer, s.c_str());
// read first character
next();
}
/*!
Initialize the JSON parser given an input stream \p _is.
@note After initialization, the function @ref parse has to be called manually.
\param _is input stream to parse
@post \p _is is copied to the buffer @ref _buffer and the firsr character is
read. Whitespace is skipped.
*/
JSON::Parser::Parser(std::istream& _is)
{
// determine length of input stream
_is.seekg(0, std::ios::end);
_length = static_cast<size_t>(_is.tellg());
_is.seekg(0, std::ios::beg);
// copy stream to buffer
_buffer = new char[_length + 1];
_is.read(_buffer, static_cast<std::streamsize>(_length));
// read first character
next();
}
/*!
@post Memory allocated for @ref _buffer (by the constructors) is released.
*/
JSON::Parser::~Parser()
{
delete [] _buffer;
}
/*!
@param[out] result the JSON to parse to
*/
void JSON::Parser::parse(JSON& result)
{
if (_buffer == nullptr)
{
error("unexpected end of file");
}
switch (_current)
{
case ('{'):
{
// explicitly set result to object to cope with {}
result._type = value_type::object;
result._value.object = new object_t;
next();
// process nonempty object
if (_current != '}')
{
do
{
// key
const auto 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 = value_type::array;
result._value.array = new array_t;
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 = value_type::string;
result._value.string = new string_t(std::move(parseString()));
break;
}
case ('t'):
{
parseTrue();
result._type = value_type::boolean;
result._value.boolean = true;
break;
}
case ('f'):
{
parseFalse();
result._type = value_type::boolean;
result._value.boolean = 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;
}
while (next() && (std::isdigit(_current) || _current == '.'
|| _current == 'e' || _current == 'E'
|| _current == '+' || _current == '-'));
try
{
const auto float_val = std::stod(tmp);
const auto int_val = static_cast<int>(float_val);
// check if conversion loses precision
if (float_val == int_val)
{
// we would not lose precision -> int
result._type = value_type::number;
result._value.number = int_val;
}
else
{
// we would lose precision -> float
result._type = value_type::number_float;
result._value.number_float = float_val;
}
}
catch (...)
{
error("error while translating " + tmp + " to number");
}
break;
}
else
{
error("unexpected character");
}
}
}
}
/*!
This function reads the next character from the buffer while ignoring all
trailing whitespace. If another character could be read, the function returns
true. If the end of the buffer is reached, false is returned.
@return whether another non-whitespace character could be read
@post _current holds the next character
*/
bool JSON::Parser::next()
{
if (_pos == _length)
{
return false;
}
_current = _buffer[_pos++];
// skip trailing whitespace
while (std::isspace(_current))
{
if (_pos == _length)
{
return false;
}
_current = _buffer[_pos++];
}
return true;
}
/*!
This function encapsulates the error reporting functions of the parser class.
It throws a \p std::invalid_argument exception with a description where the
error occurred (given as the number of characters read), what went wrong (using
the error message \p msg), and the last read token.
@param msg an error message
@return <em>This function does not return.</em>
@exception std::invalid_argument whenever the function is called
*/
void JSON::Parser::error(const std::string& msg)
{
throw std::invalid_argument("parse error at position " +
std::to_string(_pos) + ": " + msg +
", last read: '" + _current + "'");
}
/*!
Parses a string after opening quotes (\p ") where read.
@return the parsed string
@pre An opening quote \p " was read in the main parse function @ref parse.
@post The character after the closing quote \p " is the current character @ref
_current. Whitespace is skipped.
*/
std::string JSON::Parser::parseString()
{
// get position of closing quotes
char* p = std::strchr(_buffer + _pos, '\"');
// if the closing quotes are escaped (viz. *(p-1) is '\\'), we continue
// looking for the "right" quotes
while (p != nullptr and * (p - 1) == '\\')
{
// length of the string so far
const size_t length = static_cast<size_t>(p - _buffer) - _pos;
// continue checking after escaped quote
p = std::strchr(_buffer + _pos + length + 1, '\"');
}
// check if closing quotes were found
if (p == nullptr)
{
error("expected '\"'");
}
// copy string to return value
const size_t length = static_cast<size_t>(p - _buffer) - _pos;
const std::string result(_buffer + _pos, length);
// +1 to "eat" closing quote
_pos += length + 1;
// read next character
next();
return result;
}
/*!
This function is called in case a \p "t" is read in the main parse function
@ref parse. In the standard, the \p "true" token is the only candidate, so the
next three characters are expected to be \p "rue". In case of a mismatch, an
error is raised via @ref error.
@pre A \p "t" was read in the main parse function @ref parse.
@post The character after the \p "true" is the current character. Whitespace is
skipped.
*/
void JSON::Parser::parseTrue()
{
if (std::strncmp(_buffer + _pos, "rue", 3))
{
error("expected true");
}
_pos += 3;
// read next character
next();
}
/*!
This function is called in case an \p "f" is read in the main parse function
@ref parse. In the standard, the \p "false" token is the only candidate, so the
next four characters are expected to be \p "alse". In case of a mismatch, an
error is raised via @ref error.
@pre An \p "f" was read in the main parse function.
@post The character after the \p "false" is the current character. Whitespace
is skipped.
*/
void JSON::Parser::parseFalse()
{
if (std::strncmp(_buffer + _pos, "alse", 4))
{
error("expected false");
}
_pos += 4;
// read next character
next();
}
/*!
This function is called in case an \p "n" is read in the main parse function
@ref parse. In the standard, the \p "null" token is the only candidate, so the
next three characters are expected to be \p "ull". In case of a mismatch, an
error is raised via @ref error.
@pre An \p "n" was read in the main parse function.
@post The character after the \p "null" is the current character. Whitespace is
skipped.
*/
void JSON::Parser::parseNull()
{
if (std::strncmp(_buffer + _pos, "ull", 3))
{
error("expected null");
}
_pos += 3;
// read next character
next();
}
/*!
This function wraps functionality to check whether the current character @ref
_current matches a given character \p c. In case of a match, the next character
of the buffer @ref _buffer is read. In case of a mismatch, an error is raised
via @ref error.
@param c character that is expected
@post The next chatacter is read. Whitespace is skipped.
*/
void JSON::Parser::expect(const char c)
{
if (_current != c)
{
std::string msg = "expected '";
msg.append(1, c);
msg += "'";
error(msg);
}
else
{
next();
}
}
/*!
This operator implements a user-defined string literal for JSON objects. It can
be used by adding \p "_json" to a string literal and returns a JSON object if
no parse error occurred.
@param s a string representation of a JSON object
@return a JSON object
*/
JSON operator "" _json(const char* s, size_t)
{
return JSON::parse(s);
}
#pragma once #pragma once
// a helper macro to detect C++11 compliant compilers #include <initializer_list> // std::initializer_list
#if __cplusplus >= 201103L #include <iostream> // std::istream, std::ostream
#define __cplusplus11 #include <map> // std::map
#endif #include <mutex> // std::mutex
#include <string> // std::string
// STL containers #include <vector> // std::vector
#include <string>
#include <vector> /*!
#include <map> The size of a JSON object is 16 bytes: 8 bytes for the value union whose
largest item is a pointer type and another 8 byte for an element of the
// additional C++11 headers type union. The latter only needs 1 byte - the remaining 7 bytes are wasted
#ifdef __cplusplus11 due to alignment.
#include <mutex>
#include <initializer_list> @see http://stackoverflow.com/questions/7758580/writing-your-own-stl-container/7759622#7759622
#endif
@bug Numbers are currently handled too generously. There are several formats
class JSON { that are forbidden by the standard, but are accepted by the parser.
@todo Implement JSON::swap()
@todo Implement JSON::insert(), JSON::emplace(), JSON::emplace_back, JSON::erase
@todo Implement JSON::reverse_iterator, JSON::const_reverse_iterator,
JSON::rbegin(), JSON::rend(), JSON::crbegin(), JSON::crend()?
*/
class JSON
{
// forward declaration to friend this class // forward declaration to friend this class
public: public:
class iterator; class iterator;
class const_iterator; class const_iterator;
#ifdef __cplusplus11 public:
private: /// possible types of a JSON object
/// mutex to guard payload enum class value_type : uint8_t
static std::mutex _token; {
#endif /// ordered collection of values
array = 0,
public: /// unordered set of name/value pairs
/// possible types of a JSON object object,
typedef enum { /// null value
array, object, null, string, boolean, number, number_float null,
} json_t; /// string value
string,
public: /// Boolean value
/// a type for an object boolean,
typedef std::map<std::string, JSON> object_t; /// number value (integer)
/// a type for an array number,
typedef std::vector<JSON> array_t; /// number value (float)
/// a type for a string number_float
typedef std::string string_t; };
/// a type for a Boolean
typedef bool boolean_t; /// a type for an object
/// a type for an integer number using object_t = std::map<std::string, JSON>;
typedef int number_t; /// a type for an array
/// a type for a floating point number using array_t = std::vector<JSON>;
typedef double number_float_t; /// a type for a string
using string_t = std::string;
/// a type for a Boolean
using boolean_t = bool;
/// a type for an integer number
using number_t = int;
/// a type for a floating point number
using number_float_t = double;
/// a type for list initialization
using list_init_t = std::initializer_list<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() = default;
/// constructor for arrays
value(array_t*);
/// constructor for objects
value(object_t*);
/// constructor for strings
value(string_t*);
/// constructor for Booleans
value(boolean_t);
/// constructor for numbers (integer)
value(number_t);
/// constructor for numbers (float)
value(number_float_t);
};
public:
/// create an object according to given type
JSON(const value_type) noexcept;
/// create a null object
JSON() = default;
/// create a null object
JSON(std::nullptr_t) noexcept;
/// create a string object from a C++ string
JSON(const std::string&) noexcept;
/// create a string object from a C++ string (move)
JSON(std::string&&) noexcept;
/// create a string object from a C string
JSON(const char*) noexcept;
/// create a Boolean object
JSON(const bool) noexcept;
/// create a number object
JSON(const int) noexcept;
/// create a number object
JSON(const double) noexcept;
/// create an array
JSON(const array_t&) noexcept;
/// create an array (move)
JSON(array_t&&) noexcept;
/// create an object
JSON(const object_t&) noexcept;
/// create an object (move)
JSON(object_t&&) noexcept;
/// create from an initializer list (to an array or object)
JSON(list_init_t) noexcept;
/// copy constructor
JSON(const JSON&) noexcept;
/// move constructor
JSON(JSON&&) noexcept;
/// copy assignment
JSON& operator=(JSON) noexcept;
/// destructor
~JSON() noexcept;
/// create from string representation
static JSON parse(std::string&);
/// create from string representation
static JSON parse(const char*);
private:
/// return the type as string
const std::string _typename() const noexcept;
public:
/// explicit value conversion
template<typename T>
T get() const;
/// 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;
/// implicit conversion to JSON vector (not for objects)
operator array_t() const;
/// implicit conversion to JSON map (only for objects)
operator object_t() 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 noexcept;
/// add an object/array to an array
JSON& operator+=(const JSON&);
/// add a string to an array
JSON& operator+=(const std::string&);
/// add a null object to an array
JSON& operator+=(const std::nullptr_t);
/// 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);
/// add a pair to an object
JSON& operator+=(const object_t::value_type&);
/// add a list of elements to array or list of pairs to object
JSON& operator+=(list_init_t);
/// add an object/array to an array
void push_back(const JSON&);
/// add an object/array to an array (move)
void push_back(JSON&&);
/// add a string to an array
void push_back(const std::string&);
/// add a null object to an array
void push_back(const std::nullptr_t);
/// add a string to an array
void push_back(const char*);
/// add a Boolean to an array
void push_back(bool);
/// add a number to an array
void push_back(int);
/// add a number to an array
void push_back(double);
/// add a pair to an object
void push_back(const object_t::value_type&);
/// add a list of elements to array or list of pairs to object
void push_back(list_init_t);
/// operator to set an element in an array
JSON& operator[](const int);
/// operator to get an element in an array
const JSON& operator[](const int) const;
/// operator to get an element in an array
JSON& at(const int);
/// operator to get an element in an array
const JSON& at(const int) const;
/// operator to set an element in an object
inline 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;
/// operator to set an element in an object
inline JSON& at(const std::string&);
/// operator to set an element in an object
JSON& at(const char*);
/// operator to get an element in an object
const JSON& at(const std::string&) const;
/// return the number of stored values
size_t size() const noexcept;
/// checks whether object is empty
bool empty() const noexcept;
/// removes all elements from compounds and resets values to default
void clear() noexcept;
/// return the type of the object
value_type type() const noexcept;
/// 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
value data() noexcept;
/// direct access to the underlying payload
const value data() const noexcept;
/// lexicographically compares the values
bool operator==(const JSON&) const noexcept;
/// lexicographically compares the values
inline bool operator!=(const JSON&) const noexcept;
/// returns an iterator to the beginning (array/object)
iterator begin() noexcept;
/// returns an iterator to the end (array/object)
iterator end() noexcept;
/// returns an iterator to the beginning (array/object)
const_iterator begin() const noexcept;
/// returns an iterator to the end (array/object)
const_iterator end() const noexcept;
/// returns an iterator to the beginning (array/object)
const_iterator cbegin() const noexcept;
/// returns an iterator to the end (array/object)
const_iterator cend() const noexcept;
private:
/// the type of this object
value_type _type = value_type::null;
/// the payload
value _value {};
private:
/// mutex to guard payload
static std::mutex _token;
public:
/// an iterator
class iterator
{
friend class JSON;
friend class JSON::const_iterator;
public:
iterator() = default;
iterator(JSON*);
iterator(const iterator&);
~iterator();
iterator& operator=(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 = nullptr;
/// an iterator for JSON arrays
array_t::iterator* _vi = nullptr;
/// an iterator for JSON objects
object_t::iterator* _oi = nullptr;
};
/// a const iterator
class const_iterator
{
friend class JSON;
public:
const_iterator() = default;
const_iterator(const JSON*);
const_iterator(const const_iterator&);
const_iterator(const iterator&);
~const_iterator();
const_iterator& operator=(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 /// a JSON value
union value { const JSON* _object = nullptr;
/// array as pointer to array_t /// an iterator for JSON arrays
array_t* array; array_t::const_iterator* _vi = nullptr;
/// object as pointer to object_t /// an iterator for JSON objects
object_t* object; object_t::const_iterator* _oi = nullptr;
/// string as pointer to string_t };
string_t* string;
/// Boolean private:
boolean_t boolean; /// a helper class to parse a JSON object
/// number (integer) class Parser
number_t number; {
/// number (float) public:
number_float_t number_float; /// a parser reading from a C string
Parser(const char*);
/// default constructor /// a parser reading from a C++ string
value() {} Parser(std::string&);
/// constructor for arrays /// a parser reading from an input stream
value(array_t* array): array(array) {} Parser(std::istream&);
/// constructor for objects /// destructor of the parser
value(object_t* object): object(object) {} ~Parser();
/// constructor for strings
value(string_t* string): string(string) {} // no copy constructor
/// constructor for Booleans Parser(const Parser&) = delete;
value(boolean_t boolean) : boolean(boolean) {} // no copy assignment
/// constructor for numbers (integer) Parser& operator=(Parser) = delete;
value(number_t number) : number(number) {}
/// constructor for numbers (float) /// parse into a given JSON object
value(number_float_t number_float) : number_float(number_float) {} void parse(JSON&);
};
private:
private: /// read the next character, stripping whitespace
/// the type of this object bool next();
json_t _type; /// raise an exception with an error message
void error(const std::string&) __attribute__((noreturn));
/// the payload /// parse a quoted string
value _value; std::string parseString();
/// parse a Boolean "true"
#ifdef __cplusplus11 void parseTrue();
/// a type for array initialization /// parse a Boolean "false"
typedef std::initializer_list<JSON> array_init_t; void parseFalse();
#endif /// parse a null object
void parseNull();
public: /// a helper function to expect a certain character
/// create a null object void expect(const char);
JSON();
/// create an object according to given type private:
JSON(json_t); /// the length of the input buffer
/// create a string object from a C++ string size_t _length {};
JSON(const std::string&); /// a buffer of the input
/// create a string object from a C string char* _buffer { nullptr };
JSON(char*); /// the current character
/// create a string object from a C string char _current {};
JSON(const char*); /// the position inside the input buffer
/// create a Boolean object size_t _pos = 0;
JSON(const bool); };
/// create a number object
JSON(const int);
/// create a number object
JSON(const double);
/// create an array
JSON(array_t);
/// create an object
JSON(object_t);
#ifdef __cplusplus11
/// create from an initializer list (to an array)
JSON(array_init_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;
/// 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
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);
/// add an object/array to an array
void push_back(const JSON&);
/// add a string to an array
void push_back(const std::string&);
/// add a string to an array
void push_back(const char*);
/// add a Boolean to an array
void push_back(bool);
/// add a number to an array
void push_back(int);
/// add a number to an array
void push_back(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;
/// 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
value data();
/// direct access to the underlying payload
const value data() const;
/// lexicographically compares the values
bool operator==(const JSON&) const;
/// lexicographically compares the values
bool operator!=(const JSON&) const;
private:
/// return the type as string
std::string _typename() const;
public:
/// an iterator
class iterator {
friend class JSON;
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
array_t::iterator* _vi;
/// an iterator for JSON objects
object_t::iterator* _oi;
};
/// a const iterator
class const_iterator {
friend class JSON;
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
array_t::const_iterator* _vi;
/// an iterator for JSON objects
object_t::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:
/// 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();
/// 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;
};
}; };
/// user-defined literal operator to create JSON objects from strings
JSON operator ""_json(const char*, size_t);
...@@ -5,7 +5,8 @@ ...@@ -5,7 +5,8 @@
#include <JSON.h> #include <JSON.h>
#include <sstream> #include <sstream>
void test_null() { void test_null()
{
std::cerr << "entering test_null()\n"; std::cerr << "entering test_null()\n";
/* a null object */ /* a null object */
...@@ -29,7 +30,7 @@ void test_null() { ...@@ -29,7 +30,7 @@ void test_null() {
assert(a == b); assert(a == b);
// type // type
assert(a.type() == JSON::null); assert(a.type() == JSON::value_type::null);
// empty and size // empty and size
assert(a.size() == 0); assert(a.size() == 0);
...@@ -42,43 +43,56 @@ void test_null() { ...@@ -42,43 +43,56 @@ void test_null() {
assert(a.toString() == std::string("null")); assert(a.toString() == std::string("null"));
// invalid conversion to int // invalid conversion to int
try { try
{
int i = 0; int i = 0;
i = a; i = a;
assert(false); assert(false);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
assert(ex.what() == std::string("cannot cast null to JSON number")); assert(ex.what() == std::string("cannot cast null to JSON number"));
} }
// invalid conversion to double // invalid conversion to double
try { try
{
double f = 0; double f = 0;
f = a; f = a;
assert(false); assert(false);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
assert(ex.what() == std::string("cannot cast null to JSON number")); assert(ex.what() == std::string("cannot cast null to JSON number"));
} }
// invalid conversion to bool // invalid conversion to bool
try { try
{
bool b = a; bool b = a;
assert(false); assert(false);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
assert(ex.what() == std::string("cannot cast null to JSON Boolean")); assert(ex.what() == std::string("cannot cast null to JSON Boolean"));
} }
// invalid conversion to string // invalid conversion to string
try { try
{
std::string s = a; std::string s = a;
assert(false); assert(false);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
assert(ex.what() == std::string("cannot cast null to JSON string")); assert(ex.what() == std::string("cannot cast null to JSON string"));
} }
std::cerr << "leaving test_null()\n"; std::cerr << "leaving test_null()\n";
} }
void test_bool() { void test_bool()
{
std::cerr << "entering test_bool()\n"; std::cerr << "entering test_bool()\n";
JSON True = true; JSON True = true;
...@@ -89,7 +103,8 @@ void test_bool() { ...@@ -89,7 +103,8 @@ void test_bool() {
std::cerr << "leaving test_bool()\n"; std::cerr << "leaving test_bool()\n";
} }
void test_string() { void test_string()
{
std::cerr << "entering test_string()\n"; std::cerr << "entering test_string()\n";
/* a string object */ /* a string object */
...@@ -114,7 +129,7 @@ void test_string() { ...@@ -114,7 +129,7 @@ void test_string() {
assert(a == b); assert(a == b);
// type // type
assert(a.type() == JSON::string); assert(a.type() == JSON::value_type::string);
// empty and size // empty and size
assert(a.size() == 1); assert(a.size() == 1);
...@@ -127,29 +142,38 @@ void test_string() { ...@@ -127,29 +142,38 @@ void test_string() {
assert(a.toString() == std::string("\"object a\"")); assert(a.toString() == std::string("\"object a\""));
// invalid conversion to int // invalid conversion to int
try { try
{
int i = 0; int i = 0;
i = a; i = a;
assert(false); assert(false);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
assert(ex.what() == std::string("cannot cast string to JSON number")); assert(ex.what() == std::string("cannot cast string to JSON number"));
} }
// invalid conversion to double // invalid conversion to double
try { try
{
double f = 0; double f = 0;
f = a; f = a;
assert(false); assert(false);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
assert(ex.what() == std::string("cannot cast string to JSON number")); assert(ex.what() == std::string("cannot cast string to JSON number"));
} }
// invalid conversion to bool // invalid conversion to bool
try { try
{
bool b = false; bool b = false;
b = a; b = a;
assert(false); assert(false);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
assert(ex.what() == std::string("cannot cast string to JSON Boolean")); assert(ex.what() == std::string("cannot cast string to JSON Boolean"));
} }
...@@ -163,7 +187,8 @@ void test_string() { ...@@ -163,7 +187,8 @@ void test_string() {
std::cerr << "leaving test_string()\n"; std::cerr << "leaving test_string()\n";
} }
void test_array() { void test_array()
{
std::cerr << "entering test_array()\n"; std::cerr << "entering test_array()\n";
JSON a; JSON a;
...@@ -174,7 +199,7 @@ void test_array() { ...@@ -174,7 +199,7 @@ void test_array() {
a += "string"; a += "string";
// type // type
assert(a.type() == JSON::array); assert(a.type() == JSON::value_type::array);
// empty and size // empty and size
assert(a.size() == 5); assert(a.size() == 5);
...@@ -190,10 +215,13 @@ void test_array() { ...@@ -190,10 +215,13 @@ void test_array() {
assert(a[4] == JSON("string")); assert(a[4] == JSON("string"));
// invalid access to element // invalid access to element
try { try
{
a[5] = 1; a[5] = 1;
assert(false); // assert(false);
} catch (const std::exception& ex) { }
catch (const std::exception& ex)
{
assert(ex.what() == std::string("cannot access element at index 5")); assert(ex.what() == std::string("cannot access element at index 5"));
} }
...@@ -217,7 +245,8 @@ void test_array() { ...@@ -217,7 +245,8 @@ void test_array() {
// iterators // iterators
{ {
size_t count = 0; size_t count = 0;
for (JSON::iterator i = a.begin(); i != a.end(); ++i) { for (JSON::iterator i = a.begin(); i != a.end(); ++i)
{
std::cerr << *i << '\n'; std::cerr << *i << '\n';
count++; count++;
} }
...@@ -226,7 +255,8 @@ void test_array() { ...@@ -226,7 +255,8 @@ void test_array() {
{ {
size_t count = 0; size_t count = 0;
for (JSON::const_iterator i = a.begin(); i != a.end(); ++i) { for (JSON::const_iterator i = a.begin(); i != a.end(); ++i)
{
std::cerr << *i << '\n'; std::cerr << *i << '\n';
count++; count++;
} }
...@@ -235,7 +265,8 @@ void test_array() { ...@@ -235,7 +265,8 @@ void test_array() {
{ {
size_t count = 0; size_t count = 0;
for (JSON::const_iterator i = a.cbegin(); i != a.cend(); ++i) { for (JSON::const_iterator i = a.cbegin(); i != a.cend(); ++i)
{
std::cerr << *i << '\n'; std::cerr << *i << '\n';
count++; count++;
} }
...@@ -245,7 +276,8 @@ void test_array() { ...@@ -245,7 +276,8 @@ void test_array() {
#ifdef __cplusplus11 #ifdef __cplusplus11
{ {
size_t count = 0; size_t count = 0;
for (auto element : a) { for (auto element : a)
{
std::cerr << element << '\n'; std::cerr << element << '\n';
count++; count++;
} }
...@@ -256,7 +288,8 @@ void test_array() { ...@@ -256,7 +288,8 @@ void test_array() {
{ {
JSON::iterator i; JSON::iterator i;
size_t count = 0; size_t count = 0;
for (i = a.begin(); i != a.end(); ++i) { for (i = a.begin(); i != a.end(); ++i)
{
std::cerr << *i << '\n'; std::cerr << *i << '\n';
count++; count++;
} }
...@@ -266,7 +299,8 @@ void test_array() { ...@@ -266,7 +299,8 @@ void test_array() {
{ {
JSON::const_iterator i; JSON::const_iterator i;
size_t count = 0; size_t count = 0;
for (i = a.begin(); i != a.end(); ++i) { for (i = a.begin(); i != a.end(); ++i)
{
std::cerr << *i << '\n'; std::cerr << *i << '\n';
count++; count++;
} }
...@@ -276,7 +310,8 @@ void test_array() { ...@@ -276,7 +310,8 @@ void test_array() {
{ {
JSON::const_iterator i; JSON::const_iterator i;
size_t count = 0; size_t count = 0;
for (i = a.cbegin(); i != a.cend(); ++i) { for (i = a.cbegin(); i != a.cend(); ++i)
{
std::cerr << *i << '\n'; std::cerr << *i << '\n';
count++; count++;
} }
...@@ -293,7 +328,8 @@ void test_array() { ...@@ -293,7 +328,8 @@ void test_array() {
std::cerr << "leaving test_array()\n"; std::cerr << "leaving test_array()\n";
} }
void test_object() { void test_object()
{
std::cerr << "entering test_object()\n"; std::cerr << "entering test_object()\n";
// check find() // check find()
...@@ -309,7 +345,7 @@ void test_object() { ...@@ -309,7 +345,7 @@ void test_object() {
JSON::iterator i2 = o.find("baz"); JSON::iterator i2 = o.find("baz");
assert(i2 == o.end()); assert(i2 == o.end());
JSON a; JSON a;
a += "foo"; a += "foo";
a += "bar"; a += "bar";
...@@ -321,9 +357,10 @@ void test_object() { ...@@ -321,9 +357,10 @@ void test_object() {
std::cerr << "leaving test_object()\n"; std::cerr << "leaving test_object()\n";
} }
void test_streaming() { void test_streaming()
{
std::cerr << "entering test_streaming()\n"; std::cerr << "entering test_streaming()\n";
// stream text representation into stream // stream text representation into stream
std::stringstream i; std::stringstream i;
i << "{ \"foo\": true, \"baz\": [1,2,3,4] }"; i << "{ \"foo\": true, \"baz\": [1,2,3,4] }";
...@@ -343,13 +380,13 @@ void test_streaming() { ...@@ -343,13 +380,13 @@ void test_streaming() {
i >> j; i >> j;
j >> o; j >> o;
o >> k; o >> k;
assert(j.toString() == k.toString()); // assert(j.toString() == k.toString()); (order is not preserved)
} }
// check numbers // check numbers
{ {
std::stringstream number_stream; std::stringstream number_stream;
number_stream << "[0, -1, 1, 1.0, -1.0, 1.0e+1, 1.0e-1, 1.0E+1, 1.0E-1, -1.2345678e-12345678]"; number_stream << "[0, -1, 1, 1.0, -1.0, 1.0e+1, 1.0e-1, 1.0E+1, 1.0E-1, -1.2345678e-12]";
JSON j; JSON j;
j << number_stream; j << number_stream;
} }
...@@ -373,7 +410,8 @@ void test_streaming() { ...@@ -373,7 +410,8 @@ void test_streaming() {
std::cerr << "leaving test_streaming()\n"; std::cerr << "leaving test_streaming()\n";
} }
int main() { int main()
{
test_null(); test_null();
test_bool(); test_bool();
test_string(); test_string();
......
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include "JSON.h"
TEST_CASE("array")
{
SECTION("Basics")
{
// construction with given type
JSON j(JSON::value_type::array);
CHECK(j.type() == JSON::value_type::array);
// string representation of default value
CHECK(j.toString() == "[]");
// check payload
CHECK(*(j.data().array) == JSON::array_t());
// container members
CHECK(j.size() == 0);
CHECK(j.empty() == true);
// implicit conversions
CHECK_NOTHROW(JSON::array_t v = j);
CHECK_THROWS_AS(JSON::object_t v = j, std::logic_error);
CHECK_THROWS_AS(std::string v = j, std::logic_error);
CHECK_THROWS_AS(bool v = j, std::logic_error);
CHECK_THROWS_AS(int v = j, std::logic_error);
CHECK_THROWS_AS(double v = j, std::logic_error);
// explicit conversions
CHECK_NOTHROW(auto v = j.get<JSON::array_t>());
CHECK_THROWS_AS(auto v = j.get<JSON::object_t>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<std::string>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<bool>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<int>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<double>(), std::logic_error);
// transparent usage
auto id = [](JSON::array_t v)
{
return v;
};
CHECK(id(j) == j.get<JSON::array_t>());
// copy constructor
JSON k(j);
CHECK(k == j);
// copy assignment
k = j;
CHECK(k == j);
// move constructor
JSON l = std::move(k);
CHECK(l == j);
}
SECTION("Create from value")
{
JSON::array_t v1 = {"string", 1, 1.0, false, nullptr};
JSON j1 = v1;
CHECK(j1.get<JSON::array_t>() == v1);
JSON j2 = {"string", 1, 1.0, false, nullptr};
JSON::array_t v2 = j2;
CHECK(j2.get<JSON::array_t>() == v1);
CHECK(j2.get<JSON::array_t>() == v2);
// special tests to make sure construction from initializer list works
// case 1: there is an element that is not an array
JSON j3 = { {"foo", "bar"}, 3 };
CHECK(j3.type() == JSON::value_type::array);
// case 2: there is an element with more than two elements
JSON j4 = { {"foo", "bar"}, {"one", "two", "three"} };
CHECK(j4.type() == JSON::value_type::array);
// case 3: there is an element whose first element is not a string
JSON j5 = { {"foo", "bar"}, {true, "baz"} };
CHECK(j5.type() == JSON::value_type::array);
// check if nested arrays work and are recognized as arrays
JSON j6 = { {{"foo", "bar"}} };
CHECK(j6.type() == JSON::value_type::array);
CHECK(j6.size() == 1);
CHECK(j6[0].type() == JSON::value_type::object);
// move constructor
JSON j7(std::move(v1));
CHECK(j7 == j1);
}
SECTION("Array operators")
{
JSON j = {0, 1, 2, 3, 4, 5, 6};
// read
const int v1 = j[3];
CHECK(v1 == 3);
// write
j[4] = 9;
int v2 = j[4];
CHECK(v2 == 9);
// size
CHECK (j.size() == 7);
// push_back for different value types
j.push_back(7);
j.push_back("const char*");
j.push_back(42.23);
std::string s = "std::string";
j.push_back(s);
j.push_back(false);
j.push_back(nullptr);
j.push_back(j);
CHECK (j.size() == 14);
// operator+= for different value types
j += 7;
j += "const char*";
j += 42.23;
j += s;
j += false;
j += nullptr;
j += j;
CHECK (j.size() == 21);
// implicit transformation into an array
JSON empty1, empty2;
empty1 += "foo";
empty2.push_back("foo");
CHECK(empty1.type() == JSON::value_type::array);
CHECK(empty2.type() == JSON::value_type::array);
CHECK(empty1 == empty2);
// exceptions
JSON nonarray = 1;
CHECK_THROWS_AS(const int i = nonarray[0], std::domain_error);
CHECK_NOTHROW(j[21]);
CHECK_THROWS_AS(const int i = j.at(21), std::out_of_range);
CHECK_THROWS_AS(nonarray[0] = 10, std::domain_error);
CHECK_NOTHROW(j[21] = 5);
CHECK_THROWS_AS(j.at(21) = 5, std::out_of_range);
CHECK_THROWS_AS(nonarray += 2, std::runtime_error);
const JSON k = j;
CHECK_NOTHROW(k[21]);
CHECK_THROWS_AS(const int i = k.at(21), std::out_of_range);
// add initializer list
j.push_back({"a", "b", "c"});
CHECK (j.size() == 24);
}
SECTION("Iterators")
{
std::vector<int> vec = {0, 1, 2, 3, 4, 5, 6};
JSON j1 = {0, 1, 2, 3, 4, 5, 6};
const JSON j2 = {0, 1, 2, 3, 4, 5, 6};
{
// const_iterator
for (JSON::const_iterator cit = j1.begin(); cit != j1.end(); ++cit)
{
int v = *cit;
CHECK(v == vec[static_cast<size_t>(v)]);
if (cit == j1.begin())
{
CHECK(v == 0);
}
}
}
{
// const_iterator with cbegin/cend
for (JSON::const_iterator cit = j1.cbegin(); cit != j1.cend(); ++cit)
{
int v = *cit;
CHECK(v == vec[static_cast<size_t>(v)]);
if (cit == j1.cbegin())
{
CHECK(v == 0);
}
}
}
{
// range based for
for (auto el : j1)
{
int v = el;
CHECK(v == vec[static_cast<size_t>(v)]);
}
}
{
// iterator
for (JSON::iterator cit = j1.begin(); cit != j1.end(); ++cit)
{
int v_old = *cit;
*cit = cit->get<int>() * 2;
int v = *cit;
CHECK(v == vec[static_cast<size_t>(v_old)] * 2);
if (cit == j1.begin())
{
CHECK(v == 0);
}
}
}
{
// const_iterator (on const object)
for (JSON::const_iterator cit = j2.begin(); cit != j2.end(); ++cit)
{
int v = *cit;
CHECK(v == vec[static_cast<size_t>(v)]);
if (cit == j2.begin())
{
CHECK(v == 0);
}
}
}
{
// const_iterator with cbegin/cend (on const object)
for (JSON::const_iterator cit = j2.cbegin(); cit != j2.cend(); ++cit)
{
int v = *cit;
CHECK(v == vec[static_cast<size_t>(v)]);
if (cit == j2.cbegin())
{
CHECK(v == 0);
}
}
}
{
// range based for (on const object)
for (auto el : j2)
{
int v = el;
CHECK(v == vec[static_cast<size_t>(v)]);
}
}
}
}
TEST_CASE("object")
{
SECTION("Basics")
{
// construction with given type
JSON j(JSON::value_type::object);
CHECK(j.type() == JSON::value_type::object);
// string representation of default value
CHECK(j.toString() == "{}");
// check payload
CHECK(*(j.data().object) == JSON::object_t());
// container members
CHECK(j.size() == 0);
CHECK(j.empty() == true);
// implicit conversions
CHECK_THROWS_AS(JSON::array_t v = j, std::logic_error);
CHECK_NOTHROW(JSON::object_t v = j);
CHECK_THROWS_AS(std::string v = j, std::logic_error);
CHECK_THROWS_AS(bool v = j, std::logic_error);
CHECK_THROWS_AS(int v = j, std::logic_error);
CHECK_THROWS_AS(double v = j, std::logic_error);
// explicit conversions
CHECK_THROWS_AS(auto v = j.get<JSON::array_t>(), std::logic_error);
CHECK_NOTHROW(auto v = j.get<JSON::object_t>());
CHECK_THROWS_AS(auto v = j.get<std::string>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<bool>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<int>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<double>(), std::logic_error);
// transparent usage
auto id = [](JSON::object_t v)
{
return v;
};
CHECK(id(j) == j.get<JSON::object_t>());
// copy constructor
JSON k(j);
CHECK(k == j);
// copy assignment
k = j;
CHECK(k == j);
// move constructor
JSON l = std::move(k);
CHECK(l == j);
}
SECTION("Create from value")
{
JSON::object_t v1 = { {"v1", "string"}, {"v2", 1}, {"v3", 1.0}, {"v4", false} };
JSON j1 = v1;
CHECK(j1.get<JSON::object_t>() == v1);
JSON j2 = { {"v1", "string"}, {"v2", 1}, {"v3", 1.0}, {"v4", false} };
JSON::object_t v2 = j2;
CHECK(j2.get<JSON::object_t>() == v1);
CHECK(j2.get<JSON::object_t>() == v2);
// check if multiple keys are ignored
JSON j3 = { {"key", "value"}, {"key", 1} };
CHECK(j3.size() == 1);
// move constructor
JSON j7(std::move(v1));
CHECK(j7 == j1);
}
SECTION("Object operators")
{
JSON j = {{"k0", "v0"}, {"k1", nullptr}, {"k2", 42}, {"k3", 3.141}, {"k4", true}};
const JSON k = j;
// read
const std::string v0 = j["k0"];
CHECK(v0 == "v0");
auto v1 = j["k1"];
CHECK(v1 == nullptr);
int v2 = j["k2"];
CHECK(v2 == 42);
double v3 = j["k3"];
CHECK(v3 == 3.141);
bool v4 = j["k4"];
CHECK(v4 == true);
// write (replace)
j["k0"] = "new v0";
CHECK(j["k0"] == "new v0");
// write (add)
j["k5"] = false;
// size
CHECK(j.size() == 6);
// find
CHECK(j.find("k0") != j.end());
CHECK(j.find("v0") == j.end());
JSON::const_iterator i1 = j.find("k0");
JSON::iterator i2 = j.find("k0");
// at
CHECK_THROWS_AS(j.at("foo"), std::out_of_range);
CHECK_THROWS_AS(k.at("foo"), std::out_of_range);
// add pair
j.push_back(JSON::object_t::value_type {"int_key", 42});
CHECK(j["int_key"].get<int>() == 42);
j += JSON::object_t::value_type {"int_key2", 23};
CHECK(j["int_key2"].get<int>() == 23);
{
// make sure null objects are transformed
JSON je;
CHECK_NOTHROW(je.push_back(JSON::object_t::value_type {"int_key", 42}));
CHECK(je["int_key"].get<int>() == 42);
}
{
// make sure null objects are transformed
JSON je;
CHECK_NOTHROW((je += JSON::object_t::value_type {"int_key", 42}));
CHECK(je["int_key"].get<int>() == 42);
}
// add initializer list (of pairs)
{
JSON je;
je.push_back({ {"one", 1}, {"two", false}, {"three", {1,2,3}} });
CHECK(je["one"].get<int>() == 1);
CHECK(je["two"].get<bool>() == false);
CHECK(je["three"].size() == 3);
}
{
JSON je;
je += { {"one", 1}, {"two", false}, {"three", {1,2,3}} };
CHECK(je["one"].get<int>() == 1);
CHECK(je["two"].get<bool>() == false);
CHECK(je["three"].size() == 3);
}
// key/value for non-end iterator
CHECK(i1.key() == "k0");
CHECK(i1.value() == j["k0"]);
CHECK(i2.key() == "k0");
CHECK(i2.value() == j["k0"]);
// key/value for uninitialzed iterator
JSON::const_iterator i3;
JSON::iterator i4;
CHECK_THROWS_AS(i3.key(), std::out_of_range);
CHECK_THROWS_AS(i3.value(), std::out_of_range);
CHECK_THROWS_AS(i4.key(), std::out_of_range);
CHECK_THROWS_AS(i4.value(), std::out_of_range);
// key/value for end-iterator
JSON::const_iterator i5 = j.find("v0");
JSON::iterator i6 = j.find("v0");
CHECK_THROWS_AS(i5.key(), std::out_of_range);
CHECK_THROWS_AS(i5.value(), std::out_of_range);
CHECK_THROWS_AS(i6.key(), std::out_of_range);
CHECK_THROWS_AS(i6.value(), std::out_of_range);
// implicit transformation into an object
JSON empty;
empty["foo"] = "bar";
CHECK(empty.type() == JSON::value_type::object);
CHECK(empty["foo"] == "bar");
// exceptions
JSON nonarray = 1;
CHECK_THROWS_AS(const int i = nonarray["v1"], std::domain_error);
CHECK_THROWS_AS(nonarray["v1"] = 10, std::domain_error);
}
SECTION("Iterators")
{
JSON j1 = {{"k0", 0}, {"k1", 1}, {"k2", 2}, {"k3", 3}};
const JSON j2 = {{"k0", 0}, {"k1", 1}, {"k2", 2}, {"k3", 3}};
// iterator
for (JSON::iterator it = j1.begin(); it != j1.end(); ++it)
{
switch (static_cast<int>(it.value()))
{
case (0):
CHECK(it.key() == "k0");
break;
case (1):
CHECK(it.key() == "k1");
break;
case (2):
CHECK(it.key() == "k2");
break;
case (3):
CHECK(it.key() == "k3");
break;
default:
CHECK(false);
}
CHECK((*it).type() == JSON::value_type::number);
CHECK(it->type() == JSON::value_type::number);
}
// range-based for
for (auto& element : j1)
{
element = 2 * element.get<int>();
}
// const_iterator
for (JSON::const_iterator it = j1.begin(); it != j1.end(); ++it)
{
switch (static_cast<int>(it.value()))
{
case (0):
CHECK(it.key() == "k0");
break;
case (2):
CHECK(it.key() == "k1");
break;
case (4):
CHECK(it.key() == "k2");
break;
case (6):
CHECK(it.key() == "k3");
break;
default:
CHECK(false);
}
CHECK((*it).type() == JSON::value_type::number);
CHECK(it->type() == JSON::value_type::number);
}
// const_iterator using cbegin/cend
for (JSON::const_iterator it = j1.cbegin(); it != j1.cend(); ++it)
{
switch (static_cast<int>(it.value()))
{
case (0):
CHECK(it.key() == "k0");
break;
case (2):
CHECK(it.key() == "k1");
break;
case (4):
CHECK(it.key() == "k2");
break;
case (6):
CHECK(it.key() == "k3");
break;
default:
CHECK(false);
}
CHECK((*it).type() == JSON::value_type::number);
CHECK(it->type() == JSON::value_type::number);
}
// const_iterator (on const object)
for (JSON::const_iterator it = j2.begin(); it != j2.end(); ++it)
{
switch (static_cast<int>(it.value()))
{
case (0):
CHECK(it.key() == "k0");
break;
case (1):
CHECK(it.key() == "k1");
break;
case (2):
CHECK(it.key() == "k2");
break;
case (3):
CHECK(it.key() == "k3");
break;
default:
CHECK(false);
}
CHECK((*it).type() == JSON::value_type::number);
CHECK(it->type() == JSON::value_type::number);
}
// const_iterator using cbegin/cend (on const object)
for (JSON::const_iterator it = j2.cbegin(); it != j2.cend(); ++it)
{
switch (static_cast<int>(it.value()))
{
case (0):
CHECK(it.key() == "k0");
break;
case (1):
CHECK(it.key() == "k1");
break;
case (2):
CHECK(it.key() == "k2");
break;
case (3):
CHECK(it.key() == "k3");
break;
default:
CHECK(false);
}
CHECK((*it).type() == JSON::value_type::number);
CHECK(it->type() == JSON::value_type::number);
}
// range-based for (on const object)
for (auto element : j1)
{
CHECK(element.get<int>() >= 0);
}
}
}
TEST_CASE("null")
{
SECTION("Basics")
{
// construction with given type
JSON j;
CHECK(j.type() == JSON::value_type::null);
// string representation of default value
CHECK(j.toString() == "null");
// container members
CHECK(j.size() == 0);
CHECK(j.empty() == true);
// implicit conversions
CHECK_NOTHROW(JSON::array_t v = j);
CHECK_THROWS_AS(JSON::object_t v = j, std::logic_error);
CHECK_THROWS_AS(std::string v = j, std::logic_error);
CHECK_THROWS_AS(bool v = j, std::logic_error);
CHECK_THROWS_AS(int v = j, std::logic_error);
CHECK_THROWS_AS(double v = j, std::logic_error);
// explicit conversions
CHECK_NOTHROW(auto v = j.get<JSON::array_t>());
CHECK_THROWS_AS(auto v = j.get<JSON::object_t>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<std::string>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<bool>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<int>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<double>(), std::logic_error);
// copy constructor
JSON k(j);
CHECK(k == j);
// copy assignment
k = j;
CHECK(k == j);
// move constructor
JSON l = std::move(k);
CHECK(l == j);
}
SECTION("Create from value")
{
JSON j1 = nullptr;
CHECK(j1.type() == JSON::value_type::null);
}
}
TEST_CASE("string")
{
SECTION("Basics")
{
// construction with given type
JSON j(JSON::value_type::string);
CHECK(j.type() == JSON::value_type::string);
// string representation of default value
CHECK(j.toString() == "\"\"");
// check payload
CHECK(*(j.data().string) == JSON::string_t());
// container members
CHECK(j.size() == 1);
CHECK(j.empty() == false);
// implicit conversions
CHECK_NOTHROW(JSON::array_t v = j);
CHECK_THROWS_AS(JSON::object_t v = j, std::logic_error);
CHECK_NOTHROW(std::string v = j);
CHECK_THROWS_AS(bool v = j, std::logic_error);
CHECK_THROWS_AS(int v = j, std::logic_error);
CHECK_THROWS_AS(double v = j, std::logic_error);
// explicit conversions
CHECK_NOTHROW(auto v = j.get<JSON::array_t>());
CHECK_THROWS_AS(auto v = j.get<JSON::object_t>(), std::logic_error);
CHECK_NOTHROW(auto v = j.get<std::string>());
CHECK_THROWS_AS(auto v = j.get<bool>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<int>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<double>(), std::logic_error);
// transparent usage
auto id = [](std::string v)
{
return v;
};
CHECK(id(j) == j.get<std::string>());
// copy constructor
JSON k(j);
CHECK(k == j);
// copy assignment
k = j;
CHECK(k == j);
// move constructor
JSON l = std::move(k);
CHECK(l == j);
}
SECTION("Create from value")
{
JSON j1 = std::string("Hello, world");
std::string v1 = j1;
CHECK(j1.get<std::string>() == v1);
JSON j2 = "Hello, world";
CHECK(j2.get<std::string>() == "Hello, world");
std::string v3 = "Hello, world";
JSON j3 = std::move(v3);
CHECK(j3.get<std::string>() == "Hello, world");
}
}
TEST_CASE("boolean")
{
SECTION("Basics")
{
// construction with given type
JSON j(JSON::value_type::boolean);
CHECK(j.type() == JSON::value_type::boolean);
// string representation of default value
CHECK(j.toString() == "false");
// check payload
CHECK(j.data().boolean == JSON::boolean_t());
// container members
CHECK(j.size() == 1);
CHECK(j.empty() == false);
// implicit conversions
CHECK_NOTHROW(JSON::array_t v = j);
CHECK_THROWS_AS(JSON::object_t v = j, std::logic_error);
CHECK_THROWS_AS(std::string v = j, std::logic_error);
CHECK_NOTHROW(bool v = j);
CHECK_THROWS_AS(int v = j, std::logic_error);
CHECK_THROWS_AS(double v = j, std::logic_error);
// explicit conversions
CHECK_NOTHROW(auto v = j.get<JSON::array_t>());
CHECK_THROWS_AS(auto v = j.get<JSON::object_t>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<std::string>(), std::logic_error);
CHECK_NOTHROW(auto v = j.get<bool>());
CHECK_THROWS_AS(auto v = j.get<int>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<double>(), std::logic_error);
// transparent usage
auto id = [](bool v)
{
return v;
};
CHECK(id(j) == j.get<bool>());
// copy constructor
JSON k(j);
CHECK(k == j);
// copy assignment
k = j;
CHECK(k == j);
// move constructor
JSON l = std::move(k);
CHECK(l == j);
}
SECTION("Create from value")
{
JSON j1 = true;
bool v1 = j1;
CHECK(j1.get<bool>() == v1);
JSON j2 = false;
bool v2 = j2;
CHECK(j2.get<bool>() == v2);
}
}
TEST_CASE("number (int)")
{
SECTION("Basics")
{
// construction with given type
JSON j(JSON::value_type::number);
CHECK(j.type() == JSON::value_type::number);
// string representation of default value
CHECK(j.toString() == "0");
// check payload
CHECK(j.data().number == JSON::number_t());
// container members
CHECK(j.size() == 1);
CHECK(j.empty() == false);
// implicit conversions
CHECK_NOTHROW(JSON::array_t v = j);
CHECK_THROWS_AS(JSON::object_t v = j, std::logic_error);
CHECK_THROWS_AS(std::string v = j, std::logic_error);
CHECK_THROWS_AS(bool v = j, std::logic_error);
CHECK_NOTHROW(int v = j);
CHECK_NOTHROW(double v = j);
// explicit conversions
CHECK_NOTHROW(auto v = j.get<JSON::array_t>());
CHECK_THROWS_AS(auto v = j.get<JSON::object_t>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<std::string>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<bool>(), std::logic_error);
CHECK_NOTHROW(auto v = j.get<int>());
CHECK_NOTHROW(auto v = j.get<double>());
// transparent usage
auto id = [](int v)
{
return v;
};
CHECK(id(j) == j.get<int>());
// copy constructor
JSON k(j);
CHECK(k == j);
// copy assignment
k = j;
CHECK(k == j);
// move constructor
JSON l = std::move(k);
CHECK(l == j);
}
SECTION("Create from value")
{
JSON j1 = 23;
int v1 = j1;
CHECK(j1.get<int>() == v1);
JSON j2 = 42;
int v2 = j2;
CHECK(j2.get<int>() == v2);
}
}
TEST_CASE("number (float)")
{
SECTION("Basics")
{
// construction with given type
JSON j(JSON::value_type::number_float);
CHECK(j.type() == JSON::value_type::number_float);
// string representation of default value
CHECK(j.toString() == "0.000000");
// check payload
CHECK(j.data().number_float == JSON::number_float_t());
// container members
CHECK(j.size() == 1);
CHECK(j.empty() == false);
// implicit conversions
CHECK_NOTHROW(JSON::array_t v = j);
CHECK_THROWS_AS(JSON::object_t v = j, std::logic_error);
CHECK_THROWS_AS(std::string v = j, std::logic_error);
CHECK_THROWS_AS(bool v = j, std::logic_error);
CHECK_NOTHROW(int v = j);
CHECK_NOTHROW(double v = j);
// explicit conversions
CHECK_NOTHROW(auto v = j.get<JSON::array_t>());
CHECK_THROWS_AS(auto v = j.get<JSON::object_t>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<std::string>(), std::logic_error);
CHECK_THROWS_AS(auto v = j.get<bool>(), std::logic_error);
CHECK_NOTHROW(auto v = j.get<int>());
CHECK_NOTHROW(auto v = j.get<double>());
// transparent usage
auto id = [](double v)
{
return v;
};
CHECK(id(j) == j.get<double>());
// copy constructor
JSON k(j);
CHECK(k == j);
// copy assignment
k = j;
CHECK(k == j);
// move constructor
JSON l = std::move(k);
CHECK(l == j);
}
SECTION("Create from value")
{
JSON j1 = 3.1415926;
double v1 = j1;
CHECK(j1.get<double>() == v1);
JSON j2 = 2.7182818;
double v2 = j2;
CHECK(j2.get<double>() == v2);
}
}
TEST_CASE("Parser")
{
SECTION("null")
{
// accept the exact values
CHECK(JSON::parse("null") == JSON(nullptr));
// ignore whitespace
CHECK(JSON::parse(" null ") == JSON(nullptr));
CHECK(JSON::parse("\tnull\n") == JSON(nullptr));
// respect capitalization
CHECK_THROWS_AS(JSON::parse("Null"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("NULL"), std::invalid_argument);
// do not accept prefixes
CHECK_THROWS_AS(JSON::parse("n"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("nu"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("nul"), std::invalid_argument);
}
SECTION("string")
{
// accept some values
CHECK(JSON::parse("\"\"") == JSON(""));
CHECK(JSON::parse("\"foo\"") == JSON("foo"));
// quotes must be closed
CHECK_THROWS_AS(JSON::parse("\""), std::invalid_argument);
}
SECTION("boolean")
{
// accept the exact values
CHECK(JSON::parse("true") == JSON(true));
CHECK(JSON::parse("false") == JSON(false));
// ignore whitespace
CHECK(JSON::parse(" true ") == JSON(true));
CHECK(JSON::parse("\tfalse\n") == JSON(false));
// respect capitalization
CHECK_THROWS_AS(JSON::parse("True"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("False"), std::invalid_argument);
// do not accept prefixes
CHECK_THROWS_AS(JSON::parse("t"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("tr"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("tru"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("f"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("fa"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("fal"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("fals"), std::invalid_argument);
}
SECTION("number (int)")
{
// accept the exact values
CHECK(JSON::parse("0") == JSON(0));
CHECK(JSON::parse("-0") == JSON(0));
CHECK(JSON::parse("1") == JSON(1));
CHECK(JSON::parse("-1") == JSON(-1));
CHECK(JSON::parse("12345678") == JSON(12345678));
CHECK(JSON::parse("-12345678") == JSON(-12345678));
CHECK(JSON::parse("0.0") == JSON(0));
CHECK(JSON::parse("-0.0") == JSON(0));
CHECK(JSON::parse("1.0") == JSON(1));
CHECK(JSON::parse("-1.0") == JSON(-1));
CHECK(JSON::parse("12345678.0") == JSON(12345678));
CHECK(JSON::parse("-12345678.0") == JSON(-12345678));
CHECK(JSON::parse("17e0") == JSON(17));
CHECK(JSON::parse("17e1") == JSON(170));
CHECK(JSON::parse("17e3") == JSON(17000));
CHECK(JSON::parse("17e+0") == JSON(17));
CHECK(JSON::parse("17e+1") == JSON(170));
CHECK(JSON::parse("17e+3") == JSON(17000));
CHECK(JSON::parse("17E0") == JSON(17));
CHECK(JSON::parse("17E1") == JSON(170));
CHECK(JSON::parse("17E3") == JSON(17000));
CHECK(JSON::parse("17E+0") == JSON(17));
CHECK(JSON::parse("17E+1") == JSON(170));
CHECK(JSON::parse("17E+3") == JSON(17000));
CHECK(JSON::parse("10000e-0") == JSON(10000));
CHECK(JSON::parse("10000e-1") == JSON(1000));
CHECK(JSON::parse("10000e-4") == JSON(1));
CHECK(JSON::parse("10000E-0") == JSON(10000));
CHECK(JSON::parse("10000E-1") == JSON(1000));
CHECK(JSON::parse("10000E-4") == JSON(1));
CHECK(JSON::parse("17.0e0") == JSON(17));
CHECK(JSON::parse("17.0e1") == JSON(170));
CHECK(JSON::parse("17.0e3") == JSON(17000));
CHECK(JSON::parse("17.0e+0") == JSON(17));
CHECK(JSON::parse("17.0e+1") == JSON(170));
CHECK(JSON::parse("17.0e+3") == JSON(17000));
CHECK(JSON::parse("17.0E0") == JSON(17));
CHECK(JSON::parse("17.0E1") == JSON(170));
CHECK(JSON::parse("17.0E3") == JSON(17000));
CHECK(JSON::parse("17.0E+0") == JSON(17));
CHECK(JSON::parse("17.0E+1") == JSON(170));
CHECK(JSON::parse("17.0E+3") == JSON(17000));
CHECK(JSON::parse("10000.0e-0") == JSON(10000));
CHECK(JSON::parse("10000.0e-1") == JSON(1000));
CHECK(JSON::parse("10000.0e-4") == JSON(1));
CHECK(JSON::parse("10000.0E-0") == JSON(10000));
CHECK(JSON::parse("10000.0E-1") == JSON(1000));
CHECK(JSON::parse("10000.0E-4") == JSON(1));
// trailing zero is not allowed
//CHECK_THROWS_AS(JSON::parse("01"), std::invalid_argument);
// whitespace inbetween is an error
//CHECK_THROWS_AS(JSON::parse("1 0"), std::invalid_argument);
// only one minus is allowd
CHECK_THROWS_AS(JSON::parse("--1"), std::invalid_argument);
// string representations are not allowed
CHECK_THROWS_AS(JSON::parse("NAN"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("nan"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("INF"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("inf"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("INFINITY"), std::invalid_argument);
CHECK_THROWS_AS(JSON::parse("infinity"), std::invalid_argument);
}
SECTION("user-defined string literal operator")
{
auto j1 = "[1,2,3]"_json;
JSON j2 = {1, 2, 3};
CHECK(j1 == j2);
auto j3 = "{\"key\": \"value\"}"_json;
CHECK(j3["key"] == "value");
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment