From 52f399d55f3f02a9c0f87544e9f6a20949540cfa Mon Sep 17 00:00:00 2001 From: Niels Date: Fri, 9 Jan 2015 18:16:27 +0100 Subject: [PATCH] created a header-only version (see issue #16) --- Makefile.am | 16 +- README.md | 4 +- configure.ac | 1 + header_only/json.h | 2645 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2662 insertions(+), 4 deletions(-) create mode 100644 header_only/json.h diff --git a/Makefile.am b/Makefile.am index 9310afa65..ce5efd17b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,12 +1,17 @@ +.PHONY: header_only + +# the order is important: header before other sources +CORE_SOURCES = src/json.h src/json.cc + noinst_PROGRAMS = json_unit json_parser 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_unit_SOURCES = src/json.cc src/json.h test/catch.hpp test/json_unit.cc +json_unit_SOURCES = $(CORE_SOURCES) test/catch.hpp test/json_unit.cc json_unit_CXXFLAGS = $(FLAGS) -std=c++11 json_unit_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/test -Dprivate=public -json_parser_SOURCES = src/json.cc src/json.h benchmark/parse.cc +json_parser_SOURCES = $(CORE_SOURCES) benchmark/parse.cc json_parser_CXXFLAGS = -O3 -std=c++11 -flto json_parser_CPPFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/benchmark @@ -26,4 +31,9 @@ pretty: $(SOURCES) parser: - make CXXFLAGS="" json_parser \ No newline at end of file + make CXXFLAGS="" json_parser + +header_only/json.h: $(CORE_SOURCES) + cat $(CORE_SOURCES) | $(SED) 's/#include "json.h"//' > header_only/json.h + +BUILT_SOURCES = header_only/json.h diff --git a/README.md b/README.md index 34b53ce44..f39f0b512 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Other aspects were not so important to us: ## Integration -All you need to do is add +The two required source files are in the `src` directory. All you need to do is add ```cpp #include "json.h" @@ -37,6 +37,8 @@ using json = nlohmann::json; to the files you want to use JSON objects. Furthermore, you need to compile the file `json.cc` and link it to your binaries. Do not forget to set the necessary switches to enable C++11 (e.g., `-std=c++11` for GCC and Clang). +If you want a single header file, use the `json.h` file from the `header_only` directory. + ## Examples Here are some examples to give you an idea how to use the class. diff --git a/configure.ac b/configure.ac index 18a30c6c0..a41c85dfd 100644 --- a/configure.ac +++ b/configure.ac @@ -5,6 +5,7 @@ AM_INIT_AUTOMAKE([foreign subdir-objects]) AM_SILENT_RULES([yes]) AC_PROG_CXX +AC_PROG_SED AC_CONFIG_FILES(Makefile) AC_OUTPUT diff --git a/header_only/json.h b/header_only/json.h new file mode 100644 index 000000000..74db9e0bb --- /dev/null +++ b/header_only/json.h @@ -0,0 +1,2645 @@ +/*! +@file +@copyright The code is licensed under the MIT License + , + Copyright (c) 2013-2014 Niels Lohmann. + +@author Niels Lohmann + +@see https://github.com/nlohmann/json +*/ + +#pragma once + +#include // std::initializer_list +#include // std::istream, std::ostream +#include // std::map +#include // std::string +#include // std::vector +#include // std::iterator + +namespace nlohmann +{ + +/*! +@brief JSON for Modern C++ + +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 +type union. The latter only needs 1 byte - the remaining 7 bytes are wasted +due to alignment. + +@see http://stackoverflow.com/questions/7758580/writing-your-own-stl-container/7759622#7759622 + +@bug Numbers are currently handled too generously. There are several formats + 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 + public: + class iterator; + class const_iterator; + + public: + /// possible types of a JSON object + enum class value_type : uint8_t + { + /// ordered collection of values + array = 0, + /// unordered set of name/value pairs + object, + /// null value + null, + /// string value + string, + /// Boolean value + boolean, + /// number value (integer) + number, + /// number value (float) + number_float + }; + + /// a type for an object + using object_t = std::map; + /// a type for an array + using array_t = std::vector; + /// 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; + + /// 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); + /// 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&); + /// create a string object from a C++ string (move) + json(std::string&&); + /// create a string object from a C string + json(const char*); + /// 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&); + /// create an array (move) + json(array_t&&); + /// create an object + json(const object_t&); + /// create an object (move) + json(object_t&&); + /// create from an initializer list (to an array or object) + json(list_init_t); + + /// copy constructor + json(const json&); + /// move constructor + json(json&&) noexcept; + + /// copy assignment + json& operator=(json) noexcept; + + /// destructor + ~json() noexcept; + + /// create from string representation + static json parse(const std::string&); + /// create from string representation + static json parse(const char*); + + private: + /// return the type as string + std::string type_name() const noexcept; + + /// dump the object (with pretty printer) + std::string dump(const bool, const unsigned int, unsigned int = 0) const noexcept; + + public: + /// explicit value conversion + template + 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; + + /// serialize to stream + friend std::ostream& operator<<(std::ostream& o, const json& j) + { + o << j.dump(); + return o; + } + /// serialize to stream + friend std::ostream& operator>>(const json& j, std::ostream& o) + { + o << j.dump(); + return o; + } + + /// deserialize from stream + friend std::istream& operator>>(std::istream& i, json& j) + { + j = parser(i).parse(); + return i; + } + /// deserialize from stream + friend std::istream& operator<<(json& j, std::istream& i) + { + j = parser(i).parse(); + return i; + } + + /// explicit serialization + std::string dump(int = -1) 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 + 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 + 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; + /// operator to get an element in an object + const json& at(const char*) const; + + /// return the number of stored values + std::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; + + /// lexicographically compares the values + bool operator==(const json&) const noexcept; + /// lexicographically compares the values + 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_ {}; + + public: + /// an iterator + class iterator : public std::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 : public std::iterator + { + friend class json; + + public: + const_iterator() = default; + const_iterator(const json*); + const_iterator(const const_iterator&); + const_iterator(const json::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 + const json* object_ = nullptr; + /// an iterator for JSON arrays + array_t::const_iterator* vi_ = nullptr; + /// an iterator for JSON objects + object_t::const_iterator* oi_ = nullptr; + }; + + private: + /// a helper class to parse a JSON object + class parser + { + public: + /// a parser reading from a C string + parser(const char*); + /// a parser reading from a C++ string + parser(const std::string&); + /// a parser reading from an input stream + parser(std::istream&); + /// destructor of the parser + ~parser() = default; + + // no copy constructor + parser(const parser&) = delete; + // no copy assignment + parser& operator=(parser) = delete; + + /// parse and return a JSON object + json parse(); + + private: + /// read the next character, stripping whitespace + bool next(); + /// raise an exception with an error message + inline void error(const std::string&) __attribute__((noreturn)); + /// parse a quoted string + inline std::string parseString(); + /// parse a Boolean "true" + inline void parseTrue(); + /// parse a Boolean "false" + inline void parseFalse(); + /// parse a null object + inline void parseNull(); + /// a helper function to expect a certain character + inline void expect(const char); + + private: + /// a buffer of the input + std::string buffer_ {}; + /// the current character + char current_ {}; + /// the position inside the input buffer + std::size_t pos_ = 0; + }; +}; + +} + +/// user-defined literal operator to create JSON objects from strings +nlohmann::json operator "" _json(const char*, std::size_t); +/*! +@file +@copyright The code is licensed under the MIT License + , + Copyright (c) 2013-2014 Niels Lohmann. + +@author Niels Lohmann + +@see https://github.com/nlohmann/json +*/ + + + +#include // std::isdigit, std::isspace +#include // std::size_t +#include // std::runtime_error +#include // std::swap, std::move + +namespace nlohmann +{ + +/////////////////////////////////// +// 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 // +///////////////////////////////// + +/*! +Construct an empty JSON given the type. + +@param t the type from the @ref json::type enumeration. + +@post Memory for array, object, and string are allocated. +*/ +json::json(const value_type t) + : type_(t) +{ + switch (type_) + { + case (value_type::array): + { + value_.array = new array_t(); + break; + } + case (value_type::object): + { + value_.object = new object_t(); + break; + } + case (value_type::string): + { + value_.string = new string_t(); + break; + } + case (value_type::boolean): + { + value_.boolean = boolean_t(); + break; + } + case (value_type::number): + { + value_.number = number_t(); + break; + } + case (value_type::number_float): + { + value_.number_float = number_float_t(); + break; + } + default: + { + break; + } + } +} + +/*! +Construct a null JSON object. +*/ +json::json(std::nullptr_t) noexcept : json() +{} + +/*! +Construct a string JSON object. + +@param s a string to initialize the JSON object with +*/ +json::json(const std::string& s) + : type_(value_type::string), value_(new string_t(s)) +{} + +json::json(std::string&& s) + : type_(value_type::string), value_(new string_t(std::move(s))) +{} + +json::json(const char* s) + : type_(value_type::string), value_(new string_t(s)) +{} + +json::json(const bool b) noexcept + : type_(value_type::boolean), value_(b) +{} + +json::json(const int i) noexcept + : type_(value_type::number), value_(i) +{} + +json::json(const double f) noexcept + : type_(value_type::number_float), value_(f) +{} + +json::json(const array_t& a) + : type_(value_type::array), value_(new array_t(a)) +{} + +json::json(array_t&& a) + : type_(value_type::array), value_(new array_t(std::move(a))) +{} + +json::json(const object_t& o) + : type_(value_type::object), value_(new object_t(o)) +{} + +json::json(object_t&& o) + : 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. +*/ +json::json(list_init_t a) +{ + // 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(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. + +@param o the JSON object to copy +*/ +json::json(const json& o) + : type_(o.type_) +{ + switch (type_) + { + case (value_type::array): + { + value_.array = new array_t(*o.value_.array); + break; + } + case (value_type::object): + { + value_.object = new object_t(*o.value_.object); + break; + } + case (value_type::string): + { + value_.string = new string_t(*o.value_.string); + break; + } + case (value_type::boolean): + { + value_.boolean = o.value_.boolean; + break; + } + case (value_type::number): + { + value_.number = o.value_.number; + break; + } + case (value_type::number_float): + { + value_.number_float = o.value_.number_float; + break; + } + default: + { + break; + } + } +} + +/*! +A move constructor for the JSON class. + +@param o the JSON object to move + +@post The JSON object \p o is invalidated. +*/ +json::json(json&& o) noexcept + : type_(std::move(o.type_)), value_(std::move(o.value_)) +{ + // invalidate payload + o.type_ = value_type::null; + 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. +*/ +json& json::operator=(json o) noexcept +{ + std::swap(type_, o.type_); + std::swap(value_, o.value_); + return *this; +} + +json::~json() noexcept +{ + switch (type_) + { + case (value_type::array): + { + delete value_.array; + break; + } + case (value_type::object): + { + delete value_.object; + break; + } + case (value_type::string): + { + delete value_.string; + break; + } + default: + { + // nothing to do for non-pointer types + break; + } + } +} + +/*! +@param s a string representation of a JSON object +@return a JSON object +*/ +json json::parse(const std::string& s) +{ + return parser(s).parse(); +} + +/*! +@param s a string representation of a JSON object +@return a JSON object +*/ +json json::parse(const char* s) +{ + return parser(s).parse(); +} + + +std::string json::type_name() const noexcept +{ + switch (type_) + { + case (value_type::array): + { + return "array"; + } + case (value_type::object): + { + return "object"; + } + case (value_type::null): + { + return "null"; + } + case (value_type::string): + { + return "string"; + } + case (value_type::boolean): + { + return "boolean"; + } + default: + { + return "number"; + } + } +} + + +/////////////////////////////// +// OPERATORS AND CONVERSIONS // +/////////////////////////////// + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is not string +*/ +template<> +std::string json::get() const +{ + switch (type_) + { + case (value_type::string): + return *value_.string; + default: + throw std::logic_error("cannot cast " + type_name() + " to JSON string"); + } +} + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is not number (int or float) +*/ +template<> +int json::get() const +{ + switch (type_) + { + case (value_type::number): + return value_.number; + case (value_type::number_float): + return static_cast(value_.number_float); + default: + throw std::logic_error("cannot cast " + type_name() + " to JSON number"); + } +} + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is not number (int or float) +*/ +template<> +double json::get() const +{ + switch (type_) + { + case (value_type::number): + return static_cast(value_.number); + case (value_type::number_float): + return value_.number_float; + default: + throw std::logic_error("cannot cast " + type_name() + " to JSON number"); + } +} + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is not boolean +*/ +template<> +bool json::get() const +{ + switch (type_) + { + case (value_type::boolean): + return value_.boolean; + default: + throw std::logic_error("cannot cast " + type_name() + " to JSON Boolean"); + } +} + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is an object +*/ +template<> +json::array_t json::get() const +{ + if (type_ == value_type::array) + { + return *value_.array; + } + if (type_ == value_type::object) + { + throw std::logic_error("cannot cast " + type_name() + " to JSON array"); + } + + array_t result; + result.push_back(*this); + return result; +} + +/*! +@exception std::logic_error if the function is called for JSON objects whose + type is not object +*/ +template<> +json::object_t json::get() const +{ + if (type_ == value_type::object) + { + return *value_.object; + } + else + { + throw std::logic_error("cannot cast " + type_name() + " to JSON object"); + } +} + +json::operator const std::string() const +{ + return get(); +} + +json::operator int() const +{ + return get(); +} + +json::operator double() const +{ + return get(); +} + +json::operator bool() const +{ + return get(); +} + +json::operator array_t() const +{ + return get(); +} + +json::operator object_t() const +{ + return get(); +} + +/*! +Internal implementation of the serialization function. + +\param prettyPrint whether the output shall be pretty-printed +\param indentStep the indent level +\param currentIndent the current indent level (only used internally) +*/ +std::string json::dump(const bool prettyPrint, const unsigned int indentStep, + unsigned int currentIndent) const noexcept +{ + // helper function to return whitespace as indentation + const auto indent = [prettyPrint, ¤tIndent]() + { + return prettyPrint ? std::string(currentIndent, ' ') : std::string(); + }; + + switch (type_) + { + case (value_type::string): + { + return std::string("\"") + *value_.string + "\""; + } + + case (value_type::boolean): + { + return value_.boolean ? "true" : "false"; + } + + case (value_type::number): + { + return std::to_string(value_.number); + } + + case (value_type::number_float): + { + return std::to_string(value_.number_float); + } + + case (value_type::array): + { + if (value_.array->empty()) + { + return "[]"; + } + + std::string result = "["; + + // increase indentation + if (prettyPrint) + { + currentIndent += indentStep; + result += "\n"; + } + + for (array_t::const_iterator i = value_.array->begin(); i != value_.array->end(); ++i) + { + if (i != value_.array->begin()) + { + result += prettyPrint ? ",\n" : ", "; + } + result += indent() + i->dump(prettyPrint, indentStep, currentIndent); + } + + // decrease indentation + if (prettyPrint) + { + currentIndent -= indentStep; + result += "\n"; + } + + return result + indent() + "]"; + } + + case (value_type::object): + { + if (value_.object->empty()) + { + return "{}"; + } + + std::string result = "{"; + + // increase indentation + if (prettyPrint) + { + currentIndent += indentStep; + result += "\n"; + } + + for (object_t::const_iterator i = value_.object->begin(); i != value_.object->end(); ++i) + { + if (i != value_.object->begin()) + { + result += prettyPrint ? ",\n" : ", "; + } + result += indent() + "\"" + i->first + "\": " + i->second.dump(prettyPrint, indentStep, + currentIndent); + } + + // decrease indentation + if (prettyPrint) + { + currentIndent -= indentStep; + result += "\n"; + } + + return result + indent() + "}"; + } + + // actually only value_type::null - but making the compiler happy + default: + { + return "null"; + } + } +} + +/*! +Serialization function for JSON objects. The function tries to mimick Python's +\p json.dumps() function, and currently supports its \p indent parameter. + +\param indent if indent is nonnegative, then array elements and object members + will be pretty-printed with that indent level. An indent level + of 0 will only insert newlines. -1 (the default) selects the + most compact representation + +\see https://docs.python.org/2/library/json.html#json.dump +*/ +std::string json::dump(int indent) const noexcept +{ + if (indent >= 0) + { + return dump(true, static_cast(indent)); + } + else + { + return dump(false, 0); + } +} + + +/////////////////////////////////////////// +// ADDING ELEMENTS TO OBJECTS AND ARRAYS // +/////////////////////////////////////////// + +json& json::operator+=(const json& o) +{ + push_back(o); + return *this; +} + +json& json::operator+=(const std::string& s) +{ + push_back(json(s)); + return *this; +} + +json& json::operator+=(const char* s) +{ + push_back(json(s)); + return *this; +} + +json& json::operator+=(std::nullptr_t) +{ + push_back(json()); + return *this; +} + +json& json::operator+=(bool b) +{ + push_back(json(b)); + return *this; +} + +/*! +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. +*/ +json& json::operator+=(int i) +{ + push_back(json(i)); + return *this; +} + +/*! +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. +*/ +json& json::operator+=(double f) +{ + push_back(json(f)); + return *this; +} + +/*! +@todo comment me +*/ +json& json::operator+=(const object_t::value_type& p) +{ + return operator[](p.first) = p.second; +} + +/*! +@todo comment me +*/ +json& json::operator+=(list_init_t a) +{ + push_back(a); + return *this; +} + +/*! +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. +*/ +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 " + type_name()); + } + + // transform null object into an array + if (type_ == value_type::null) + { + type_ = value_type::array; + value_.array = new array_t; + } + + // add element to array + value_.array->push_back(o); +} + +/*! +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. +*/ +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 " + type_name()); + } + + // 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)); + // invalidate object + o.type_ = value_type::null; +} + +void json::push_back(const std::string& s) +{ + push_back(json(s)); +} + +void json::push_back(const char* s) +{ + push_back(json(s)); +} + +void json::push_back(std::nullptr_t) +{ + push_back(json()); +} + +void json::push_back(bool b) +{ + push_back(json(b)); +} + +/*! +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. +*/ +void json::push_back(int i) +{ + push_back(json(i)); +} + +/*! +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. +*/ +void json::push_back(double f) +{ + push_back(json(f)); +} + +/*! +@todo comment me +*/ +void json::push_back(const object_t::value_type& p) +{ + operator[](p.first) = p.second; +} + +/*! +@todo comment 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(), 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 +*/ +json& json::operator[](const int index) +{ + // this [] operator only works for arrays + if (type_ != value_type::array) + { + throw std::domain_error("cannot add entry with index " + + std::to_string(index) + " to " + type_name()); + } + + // return reference to element from array at given index + return (*value_.array)[static_cast(index)]; +} + +/*! +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 +*/ +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 " + type_name()); + } + + // return element from array at given index + return (*value_.array)[static_cast(index)]; +} + +/*! +This function realizes read/write 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 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) +*/ +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 " + type_name()); + } + + // return reference to element from array at given index + return value_.array->at(static_cast(index)); +} + +/*! +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) +*/ +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 " + type_name()); + } + + // return element from array at given index + return value_.array->at(static_cast(index)); +} + +/*! +@copydoc json::operator[](const char* key) +*/ +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) +{ + // implicitly convert null to object + if (type_ == value_type::null) + { + type_ = value_type::object; + value_.object = new object_t; + } + + // this [] operator only works for objects + if (type_ != value_type::object) + { + throw std::domain_error("cannot add entry with key " + + std::string(key) + " to " + type_name()); + } + + // if the key does not exist, create it + if (value_.object->find(key) == value_.object->end()) + { + (*value_.object)[key] = json(); + } + + // return reference to element from array at given index + return (*value_.object)[key]; +} + +/*! +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 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 " + type_name()); + } + + // search for the key + 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 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()); +} + +/*! +This function 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 (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) +{ + // 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 " + type_name()); + } + + // return reference to element from array at given index + return value_.object->at(key); +} + +/*! +@copydoc json::at(const char *key) const +*/ +const json& json::at(const std::string& key) const +{ + return at(key.c_str()); +} + +/*! +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 char* 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 " + type_name()); + } + + // return element from array at given key + return value_.object->at(key); +} + + +/*! +Returns the size of the JSON object. + +@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. +*/ +std::size_t json::size() const noexcept +{ + switch (type_) + { + case (value_type::array): + { + return value_.array->size(); + } + case (value_type::object): + { + return value_.object->size(); + } + case (value_type::null): + { + return 0; + } + default: + { + return 1; + } + } +} + +/*! +Returns whether a JSON object is empty. + +@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. +*/ +bool json::empty() const noexcept +{ + switch (type_) + { + case (value_type::array): + { + return value_.array->empty(); + } + case (value_type::object): + { + return value_.object->empty(); + } + case (value_type::null): + { + return true; + } + default: + { + 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. +*/ +void json::clear() noexcept +{ + switch (type_) + { + case (value_type::array): + { + value_.array->clear(); + break; + } + case (value_type::object): + { + value_.object->clear(); + break; + } + case (value_type::string): + { + 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; + } + default: + { + break; + } + } +} + +json::value_type json::type() const noexcept +{ + return type_; +} + +json::iterator json::find(const std::string& key) +{ + return find(key.c_str()); +} + +json::const_iterator json::find(const std::string& key) const +{ + return find(key.c_str()); +} + +json::iterator json::find(const char* key) +{ + if (type_ != value_type::object) + { + return end(); + } + else + { + const object_t::iterator i = value_.object->find(key); + if (i != value_.object->end()) + { + json::iterator result(this); + delete result.oi_; + result.oi_ = nullptr; + result.oi_ = new object_t::iterator(i); + return result; + } + else + { + return end(); + } + } +} + +json::const_iterator json::find(const char* key) const +{ + if (type_ != value_type::object) + { + return end(); + } + else + { + const object_t::const_iterator i = value_.object->find(key); + if (i != value_.object->end()) + { + json::const_iterator result(this); + delete result.oi_; + result.oi_ = nullptr; + result.oi_ = new object_t::const_iterator(i); + return result; + } + else + { + return end(); + } + } +} + +bool json::operator==(const json& o) const noexcept +{ + switch (type_) + { + case (value_type::array): + { + if (o.type_ == value_type::array) + { + return *value_.array == *o.value_.array; + } + break; + } + case (value_type::object): + { + if (o.type_ == value_type::object) + { + return *value_.object == *o.value_.object; + } + break; + } + case (value_type::null): + { + if (o.type_ == value_type::null) + { + return true; + } + break; + } + case (value_type::string): + { + if (o.type_ == value_type::string) + { + return *value_.string == *o.value_.string; + } + break; + } + case (value_type::boolean): + { + if (o.type_ == value_type::boolean) + { + return value_.boolean == o.value_.boolean; + } + break; + } + case (value_type::number): + { + if (o.type_ == value_type::number) + { + return value_.number == o.value_.number; + } + if (o.type_ == value_type::number_float) + { + return value_.number == static_cast(o.value_.number_float); + } + break; + } + case (value_type::number_float): + { + if (o.type_ == value_type::number) + { + return value_.number_float == static_cast(o.value_.number); + } + if (o.type_ == value_type::number_float) + { + return value_.number_float == o.value_.number_float; + } + break; + } + } + + return false; +} + +bool json::operator!=(const json& o) const noexcept +{ + return not operator==(o); +} + + +json::iterator json::begin() noexcept +{ + return json::iterator(this); +} + +json::iterator json::end() noexcept +{ + return json::iterator(); +} + +json::const_iterator json::begin() const noexcept +{ + return json::const_iterator(this); +} + +json::const_iterator json::end() const noexcept +{ + return json::const_iterator(); +} + +json::const_iterator json::cbegin() const noexcept +{ + return json::const_iterator(this); +} + +json::const_iterator json::cend() const noexcept +{ + return json::const_iterator(); +} + + +json::iterator::iterator(json* j) : object_(j) +{ + if (object_ != nullptr) + { + if (object_->type_ == json::value_type::array) + { + vi_ = new array_t::iterator(object_->value_.array->begin()); + } + if (object_->type_ == json::value_type::object) + { + oi_ = new object_t::iterator(object_->value_.object->begin()); + } + } +} + +json::iterator::iterator(const json::iterator& o) : object_(o.object_) +{ + if (object_ != nullptr) + { + if (object_->type_ == json::value_type::array) + { + vi_ = new array_t::iterator(*(o.vi_)); + } + if (object_->type_ == json::value_type::object) + { + oi_ = new object_t::iterator(*(o.oi_)); + } + } +} + +json::iterator::~iterator() +{ + delete vi_; + delete oi_; +} + +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; +} + +bool json::iterator::operator==(const json::iterator& o) const +{ + if (object_ != o.object_) + { + return false; + } + + if (object_ != nullptr) + { + if (object_->type_ == json::value_type::array) + { + return (vi_ == o.vi_); + } + if (object_->type_ == json::value_type::object) + { + return (oi_ == o.oi_); + } + } + + return true; +} + +bool json::iterator::operator!=(const json::iterator& o) const +{ + return not operator==(o); +} + +json::iterator& json::iterator::operator++() +{ + // iterator cannot be incremented + if (object_ == nullptr) + { + return *this; + } + + switch (object_->type_) + { + case (json::value_type::array): + { + if (++(*vi_) == object_->value_.array->end()) + { + object_ = nullptr; + } + break; + } + case (json::value_type::object): + { + if (++(*oi_) == object_->value_.object->end()) + { + object_ = nullptr; + } + break; + } + default: + { + object_ = nullptr; + } + } + return *this; +} + +json& json::iterator::operator*() const +{ + // dereferencing end() is an error + if (object_ == nullptr) + { + throw std::runtime_error("cannot get value"); + } + + switch (object_->type_) + { + case (json::value_type::array): + { + return **vi_; + } + case (json::value_type::object): + { + return (*oi_)->second; + } + default: + { + return *object_; + } + } +} + +json* json::iterator::operator->() const +{ + // dereferencing end() is an error + if (object_ == nullptr) + { + throw std::runtime_error("cannot get value"); + } + + switch (object_->type_) + { + case (json::value_type::array): + { + return &(**vi_); + } + case (json::value_type::object): + { + return &((*oi_)->second); + } + default: + { + return object_; + } + } +} + +std::string json::iterator::key() const +{ + if (object_ != nullptr and object_->type_ == json::value_type::object) + { + return (*oi_)->first; + } + else + { + throw std::out_of_range("cannot get key"); + } +} + +json& json::iterator::value() const +{ + // dereferencing end() is an error + if (object_ == nullptr) + { + throw std::out_of_range("cannot get value"); + } + + switch (object_->type_) + { + case (json::value_type::array): + { + return **vi_; + } + case (json::value_type::object): + { + return (*oi_)->second; + } + default: + { + return *object_; + } + } +} + + +json::const_iterator::const_iterator(const json* j) : object_(j) +{ + if (object_ != nullptr) + { + if (object_->type_ == json::value_type::array) + { + vi_ = new array_t::const_iterator(object_->value_.array->begin()); + } + if (object_->type_ == json::value_type::object) + { + oi_ = new object_t::const_iterator(object_->value_.object->begin()); + } + } +} + +json::const_iterator::const_iterator(const json::const_iterator& o) : object_(o.object_) +{ + if (object_ != nullptr) + { + if (object_->type_ == json::value_type::array) + { + vi_ = new array_t::const_iterator(*(o.vi_)); + } + if (object_->type_ == json::value_type::object) + { + oi_ = new object_t::const_iterator(*(o.oi_)); + } + } +} + +json::const_iterator::const_iterator(const json::iterator& o) : object_(o.object_) +{ + if (object_ != nullptr) + { + if (object_->type_ == json::value_type::array) + { + vi_ = new array_t::const_iterator(*(o.vi_)); + } + if (object_->type_ == json::value_type::object) + { + oi_ = new object_t::const_iterator(*(o.oi_)); + } + } +} + +json::const_iterator::~const_iterator() +{ + delete vi_; + delete oi_; +} + +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; +} + +bool json::const_iterator::operator==(const json::const_iterator& o) const +{ + if (object_ != o.object_) + { + return false; + } + + if (object_ != nullptr) + { + if (object_->type_ == json::value_type::array) + { + return (vi_ == o.vi_); + } + if (object_->type_ == json::value_type::object) + { + return (oi_ == o.oi_); + } + } + + return true; +} + +bool json::const_iterator::operator!=(const json::const_iterator& o) const +{ + return not operator==(o); +} + +json::const_iterator& json::const_iterator::operator++() +{ + // iterator cannot be incremented + if (object_ == nullptr) + { + return *this; + } + + switch (object_->type_) + { + case (json::value_type::array): + { + if (++(*vi_) == object_->value_.array->end()) + { + object_ = nullptr; + } + break; + } + case (json::value_type::object): + { + if (++(*oi_) == object_->value_.object->end()) + { + object_ = nullptr; + } + break; + } + default: + { + object_ = nullptr; + } + } + return *this; +} + +const json& json::const_iterator::operator*() const +{ + // dereferencing end() is an error + if (object_ == nullptr) + { + throw std::runtime_error("cannot get value"); + } + + switch (object_->type_) + { + case (json::value_type::array): + { + return **vi_; + } + case (json::value_type::object): + { + return (*oi_)->second; + } + default: + { + return *object_; + } + } +} + +const json* json::const_iterator::operator->() const +{ + // dereferencing end() is an error + if (object_ == nullptr) + { + throw std::runtime_error("cannot get value"); + } + + switch (object_->type_) + { + case (json::value_type::array): + { + return &(**vi_); + } + case (json::value_type::object): + { + return &((*oi_)->second); + } + default: + { + return object_; + } + } +} + +std::string json::const_iterator::key() const +{ + if (object_ != nullptr and object_->type_ == json::value_type::object) + { + return (*oi_)->first; + } + else + { + throw std::out_of_range("cannot get key"); + } +} + +const json& json::const_iterator::value() const +{ + // dereferencing end() is an error + if (object_ == nullptr) + { + throw std::out_of_range("cannot get value"); + } + + switch (object_->type_) + { + case (json::value_type::array): + { + return **vi_; + } + case (json::value_type::object): + { + return (*oi_)->second; + } + default: + { + 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 first character is + read. Whitespace is skipped. +*/ +json::parser::parser(const char* s) + : buffer_(s) +{ + // read first character + next(); +} + +/*! +@copydoc json::parser::parser(const char* s) +*/ +json::parser::parser(const std::string& s) + : buffer_(s) +{ + // 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) +{ + while (_is) + { + std::string input_line; + std::getline(_is, input_line); + buffer_ += input_line; + } + + // read first character + next(); +} + +json json::parser::parse() +{ + switch (current_) + { + case ('{'): + { + // explicitly set result to object to cope with {} + json result(value_type::object); + + next(); + + // process nonempty object + if (current_ != '}') + { + do + { + // key + auto key = parseString(); + + // colon + expect(':'); + + // value + result[std::move(key)] = parse(); + key.clear(); + } + while (current_ == ',' and next()); + } + + // closing brace + expect('}'); + + return result; + } + + case ('['): + { + // explicitly set result to array to cope with [] + json result(value_type::array); + + next(); + + // process nonempty array + if (current_ != ']') + { + do + { + result.push_back(parse()); + } + while (current_ == ',' and next()); + } + + // closing bracket + expect(']'); + + return result; + } + + case ('\"'): + { + return json(parseString()); + } + + case ('t'): + { + parseTrue(); + return json(true); + } + + case ('f'): + { + parseFalse(); + return json(false); + } + + case ('n'): + { + parseNull(); + return json(); + } + + case ('-'): + case ('0'): + case ('1'): + case ('2'): + case ('3'): + case ('4'): + case ('5'): + case ('6'): + case ('7'): + case ('8'): + case ('9'): + { + // remember position of number's first character + const auto _firstpos_ = pos_ - 1; + + while (next() and (std::isdigit(current_) || current_ == '.' + || current_ == 'e' || current_ == 'E' + || current_ == '+' || current_ == '-')); + + try + { + const auto float_val = std::stod(buffer_.substr(_firstpos_, pos_ - _firstpos_)); + const auto int_val = static_cast(float_val); + + // check if conversion loses precision + if (float_val == int_val) + { + // we would not lose precision -> int + return json(int_val); + } + else + { + // we would lose precision -> float + return json(float_val); + } + } + catch (...) + { + error("error translating " + + buffer_.substr(_firstpos_, pos_ - _firstpos_) + " to number"); + } + } + + default: + { + 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_ == buffer_.size()) + { + return false; + } + + current_ = buffer_[pos_++]; + + // skip trailing whitespace + while (std::isspace(current_)) + { + if (pos_ == buffer_.size()) + { + 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 This function does not return. + +@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. + pos_ is the position after the opening quote. + +@post The character after the closing quote \p " is the current character @ref + current_. Whitespace is skipped. +*/ +std::string json::parser::parseString() +{ + // remember the position where the first character of the string was + const auto startPos = pos_; + // true if and only if the amount of backslashes before the current + // character is even + bool evenAmountOfBackslashes = true; + + // iterate with pos_ over the whole string + for (; pos_ < buffer_.size(); pos_++) + { + char currentChar = buffer_[pos_]; + + // currentChar is a quote, so we might have found the end of the string + if (currentChar == '"') + { + // but only if the amount of backslashes before that quote is even + if (evenAmountOfBackslashes) + { + + const auto stringLength = pos_ - startPos; + // set pos_ behind the trailing quote + pos_++; + // find next char to parse + next(); + + // return string inside the quotes + return buffer_.substr(startPos, stringLength); + } + } + + // remember if we have an even amount of backslashes before the current + // character + if (currentChar == '\\') + { + // jump between even/uneven for each backslash we encounter + evenAmountOfBackslashes = not evenAmountOfBackslashes; + } + else + { + // zero backslashes are also an even number, so as soon as we + // encounter a non-backslash the chain of backslashes breaks and + // we start again from zero + evenAmountOfBackslashes = true; + } + } + + // we iterated over the whole string without finding a unescaped quote + // so the given string is malformed + error("expected '\"'"); +} + +/*! +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 (buffer_.substr(pos_, 3) != "rue") + { + 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 (buffer_.substr(pos_, 4) != "alse") + { + 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 (buffer_.substr(pos_, 3) != "ull") + { + 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 +*/ +nlohmann::json operator "" _json(const char* s, std::size_t) +{ + return nlohmann::json::parse(s); +}