diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d8b020a9d93feb44ab93b30a92e9d68c19b4008..26b8c5a3fb316158e2723f1547ac70a455688a00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,5 +140,7 @@ message(" BZip2: ${ADIOS_USE_BZip2}") message(" ZFP: ${ADIOS_USE_ZFP}") message(" ADIOS1: ${ADIOS_USE_ADIOS1}") message(" DataMan: ${ADIOS_USE_DataMan}") +message(" ZeroMQ: ${ADIOS_USE_DataMan_ZeroMQ}") +message(" ZFP: ${ADIOS_USE_DataMan_ZFP}") message(" HDF5: ${ADIOS_USE_HDF5}") message("") diff --git a/source/dataman/CMakeLists.txt b/source/dataman/CMakeLists.txt index a35ae5b88e13541b1afb93433cfc7b3a5bc95560..f8ee0813ee7226d0632cc7cab9403b57ffeff2b1 100644 --- a/source/dataman/CMakeLists.txt +++ b/source/dataman/CMakeLists.txt @@ -6,8 +6,9 @@ set(dataman_targets) add_library(dataman - DataManBase.h DataManBase.cpp - DataMan.h DataMan.cpp + DataManBase.cpp DataManBase.h + DataMan.cpp DataMan.h + CacheMan.cpp CacheMan.h ) target_include_directories(dataman PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(dataman PRIVATE adios2sys) @@ -17,33 +18,47 @@ list(APPEND dataman_targets dataman) # MODULE libraries are designed to be plugins, i.e. shared libs that nobody # else links to. -add_library(cacheman MODULE CacheMan.h CacheMan.cpp) -target_link_libraries(cacheman PRIVATE dataman) -list(APPEND dataman_targets cacheman) +add_library(dumpman MODULE DumpMan.h DumpMan.cpp) +target_link_libraries(dumpman PRIVATE dataman) +list(APPEND dataman_targets dumpman) + +add_library(temporalman MODULE TemporalMan.h TemporalMan.cpp) +target_link_libraries(temporalman PRIVATE dataman) +list(APPEND dataman_targets temporalman) option(ADIOS_USE_DataMan_ZeroMQ "Enable ZeroMQ for DataMan" OFF) if(ADIOS_USE_DataMan_ZeroMQ) find_package(ZeroMQ REQUIRED) - message(STATUS "DataMan ZeroMQ support not yet implemented") -# add_library(zmqman MODULE ZmqMan.h ZmqMan.cpp) -# target_link_libraries(zmqman PRIVATE dataman ZeroMQ::ZMQ) -# -# list(APPEND dataman_targets zmqman) + # Manually add the ZeroMQ_INCLUDE_DIRS since object libs still don't support + # target usage requirements + add_library(streamman OBJECT StreamMan.h StreamMan.cpp) + target_include_directories(streamman PRIVATE ${ZeroMQ_INCLUDE_DIRS}) + + add_library(zmqman MODULE + ZmqMan.h ZmqMan.cpp + $<TARGET_OBJECTS:streamman> + ) + target_link_libraries(zmqman PRIVATE dataman ZeroMQ::ZMQ) + list(APPEND dataman_targets zmqman) + + add_library(mdtmman MODULE + MdtmMan.h MdtmMan.cpp + $<TARGET_OBJECTS:streamman> + ) + target_link_libraries(mdtmman PRIVATE dataman ZeroMQ::ZMQ) + list(APPEND dataman_targets mdtmman) endif() -# Make this a standalone option if you want to seperately enable/disable -# DataMan ZFP support from ADIOS ZFP support. -set(ADIOS_USE_DataMan_ZFP ${ADIOS_USE_ZFP}) +set(ADIOS_USE_DataMan_ZFP ${ADIOS_USE_ZFP} CACHE INTERNAL "Enable ZFP for DataMan" FORCE) if(ADIOS_USE_DataMan_ZFP) find_package(ZFP REQUIRED) - message(STATUS "DataMan ZFP support not yet implemented") -# add_library(zfpman MODULE ZfpMan.h ZfpMan.cpp) -# target_link_libraries(zfpman PRIVATE dataman zfp::zfp) -# -# list(APPEND dataman_targets zfpman) + add_library(zfpman MODULE ZfpMan.h ZfpMan.cpp) + target_link_libraries(zfpman PRIVATE dataman zfp::zfp) + + list(APPEND dataman_targets zfpman) endif() install( diff --git a/source/dataman/CacheMan.h b/source/dataman/CacheMan.h index 9bc6371b7a119714773ae5bdde85ee9da697b13b..ca62cfb16b1954e0082bfbd4e8403f9d526bdda0 100644 --- a/source/dataman/CacheMan.h +++ b/source/dataman/CacheMan.h @@ -7,8 +7,9 @@ * Created on: Apr 18, 2017 * Author: Jason Wang */ -#ifndef CACHEMAN_H_ -#define CACHEMAN_H_ + +#ifndef DATAMAN_CACHEMAN_H_ +#define DATAMAN_CACHEMAN_H_ #include "DataMan.h" diff --git a/source/dataman/CompressMan.h b/source/dataman/CompressMan.h new file mode 100644 index 0000000000000000000000000000000000000000..5c65cb191cf5eac59b37430d395fdacdf946ea7a --- /dev/null +++ b/source/dataman/CompressMan.h @@ -0,0 +1,24 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * CompressMan.h + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#ifndef DATAMAN_COMPRESSMAN_H_ +#define DATAMAN_COMPRESSMAN_H_ + +#include "DataMan.h" + +class CompressMan : public DataManBase +{ +public: + CompressMan() = default; + virtual ~CompressMan() = default; + virtual std::string type() { return "Compress"; } +}; + +#endif diff --git a/source/dataman/DataMan.cpp b/source/dataman/DataMan.cpp index 5d8f446a4bc8002f797ddaacb8093bff706654f5..dfcea7687d8d41ff07693b5a55cf7aa228975410 100644 --- a/source/dataman/DataMan.cpp +++ b/source/dataman/DataMan.cpp @@ -35,8 +35,10 @@ void DataMan::add_stream(json p_jmsg) std::string method; - if (p_jmsg["method"] != nullptr) + if (p_jmsg["method"].is_string()) + { method = p_jmsg["method"]; + } logging("Streaming method " + method + " added"); @@ -61,7 +63,10 @@ void DataMan::add_stream(json p_jmsg) man->init(p_jmsg); this->add_next(method, man); } - add_man_to_path("zfp", method); + if (p_jmsg["compression_method"].is_string()) + { + add_man_to_path(p_jmsg["compression_method"], method, p_jmsg); + } } void DataMan::flush() { flush_next(); } diff --git a/source/dataman/DataMan.h b/source/dataman/DataMan.h index 6316eb34ed512dd4c732cdb571c34e4dd7977b9d..ec42b199e32a3ad011ba111cece1c11f36dbfc55 100644 --- a/source/dataman/DataMan.h +++ b/source/dataman/DataMan.h @@ -8,8 +8,8 @@ * Author: Jason Wang */ -#ifndef DATAMAN_H_ -#define DATAMAN_H_ +#ifndef DATAMAN_DATAMAN_H_ +#define DATAMAN_DATAMAN_H_ #include "DataManBase.h" @@ -17,6 +17,7 @@ class DataMan : public DataManBase { public: DataMan() = default; + virtual ~DataMan() = default; virtual int init(json p_jmsg); virtual int put(const void *p_data, json p_jmsg); virtual int get(void *p_data, json &p_jmsg); diff --git a/source/dataman/DataManBase.cpp b/source/dataman/DataManBase.cpp index feb55a86b829b694370395094269da6f02141208..9741a6385fdc717c384a6d2526203cfa56aaed66 100644 --- a/source/dataman/DataManBase.cpp +++ b/source/dataman/DataManBase.cpp @@ -152,8 +152,14 @@ int DataManBase::put_end(const void *p_data, json &p_jmsg) m_profiling["manager_mbs"] = m_profiling["total_mb"].get<double>() / m_profiling["total_manager_time"].get<double>(); - if (p_jmsg["compressed_size"] != nullptr) + if (p_jmsg["compressed_size"].is_number()) + { p_jmsg["putbytes"] = p_jmsg["compressed_size"].get<size_t>(); + } + else + { + p_jmsg.erase("compressed_size"); + } put_next(p_data, p_jmsg); return 0; } @@ -276,13 +282,15 @@ bool DataManBase::auto_transform(const void *p_in, void *p_out, json &p_jmsg) } } -void DataManBase::add_man_to_path(std::string p_new, std::string p_path) +void DataManBase::add_man_to_path(std::string p_new, std::string p_path, + json p_jmsg) { if (m_next.count(p_path) > 0) { auto man = get_man(p_new); if (man) { + man->init(p_jmsg); man->add_next(p_path, m_next[p_path]); this->add_next(p_new, man); this->remove_next(p_path); @@ -335,3 +343,212 @@ std::shared_ptr<DataManBase> DataManBase::get_man(std::string method) return nullptr; } } + +void DataManBase::logging(std::string p_msg, std::string p_man, + std::ostream &out) +{ + if (p_man == "") + { + p_man = name(); + } + out << "["; + out << p_man; + out << "]"; + out << " "; + out << p_msg; + out << std::endl; +} + +bool DataManBase::check_json(json p_jmsg, std::vector<std::string> p_strings, + std::string p_man) +{ + if (p_man == "") + { + p_man = name(); + } + for (auto i : p_strings) + { + if (p_jmsg[i] == nullptr) + { + if (p_man != "") + { + logging("JSON key " + i + " not found!", p_man); + } + return false; + } + } + return true; +} + +size_t DataManBase::product(size_t *shape) +{ + size_t s = 1; + if (shape) + { + for (size_t i = 1; i <= shape[0]; i++) + { + s *= shape[i]; + } + } + return s; +} + +size_t DataManBase::product(std::vector<size_t> shape, size_t size) +{ + return accumulate(shape.begin(), shape.end(), size, + std::multiplies<size_t>()); +} + +size_t DataManBase::dsize(std::string dtype) +{ + if (dtype == "char") + { + return sizeof(char); + } + if (dtype == "short") + { + return sizeof(short); + } + if (dtype == "int") + { + return sizeof(int); + } + if (dtype == "long") + { + return sizeof(long); + } + if (dtype == "unsigned char") + { + return sizeof(unsigned char); + } + if (dtype == "unsigned short") + { + return sizeof(unsigned short); + } + if (dtype == "unsigned int") + { + return sizeof(unsigned int); + } + if (dtype == "unsigned long") + { + return sizeof(unsigned long); + } + if (dtype == "float") + { + return sizeof(float); + } + if (dtype == "double") + { + return sizeof(double); + } + if (dtype == "long double") + { + return sizeof(long double); + } + if (dtype == "std::complex<float>" or dtype == "complex<float>") + { + return sizeof(std::complex<float>); + } + if (dtype == "std::complex<double>") + { + return sizeof(std::complex<double>); + } + + if (dtype == "int8_t") + { + return sizeof(int8_t); + } + if (dtype == "uint8_t") + { + return sizeof(uint8_t); + } + if (dtype == "int16_t") + { + return sizeof(int16_t); + } + if (dtype == "uint16_t") + { + return sizeof(uint16_t); + } + if (dtype == "int32_t") + { + return sizeof(int32_t); + } + if (dtype == "uint32_t") + { + return sizeof(uint32_t); + } + if (dtype == "int64_t") + { + return sizeof(int64_t); + } + if (dtype == "uint64_t") + { + return sizeof(uint64_t); + } + return 0; +} + +nlohmann::json DataManBase::atoj(unsigned int *array) +{ + json j; + if (array) + { + if (array[0] > 0) + { + j = {array[1]}; + for (unsigned int i = 2; i <= array[0]; i++) + { + j.insert(j.end(), array[i]); + } + } + } + return j; +} + +int DataManBase::closest(int v, json j, bool up) +{ + int s = 100, k = 0, t; + for (unsigned int i = 0; i < j.size(); i++) + { + if (up) + { + t = j[i].get<int>() - v; + } + else + { + t = v - j[i].get<int>(); + } + if (t >= 0 && t < s) + { + s = t; + k = i; + } + } + return k; +} + +void DataManBase::check_shape(json &p_jmsg) +{ + std::vector<size_t> varshape; + if (check_json(p_jmsg, {"varshape"})) + { + varshape = p_jmsg["varshape"].get<std::vector<size_t>>(); + } + else + { + return; + } + if (p_jmsg["putshape"] == nullptr) + { + p_jmsg["putshape"] = varshape; + } + if (p_jmsg["offset"] == nullptr) + { + p_jmsg["offset"] = std::vector<size_t>(varshape.size(), 0); + } + p_jmsg["putbytes"] = product(p_jmsg["putshape"].get<std::vector<size_t>>(), + dsize(p_jmsg["dtype"].get<std::string>())); + p_jmsg["varbytes"] = + product(varshape, dsize(p_jmsg["dtype"].get<std::string>())); +} diff --git a/source/dataman/DataManBase.h b/source/dataman/DataManBase.h index ebc8cd0097014c36bc9edbb6cfb692798ecabb4b..a838077eddddbe17b2e1d2f32eccedff5471acc6 100644 --- a/source/dataman/DataManBase.h +++ b/source/dataman/DataManBase.h @@ -8,8 +8,8 @@ * Author: Jason Wang */ -#ifndef DATAMANBASE_H_ -#define DATAMANBASE_H_ +#ifndef DATAMAN_DATAMANBASE_H_ +#define DATAMAN_DATAMANBASE_H_ #include <cstdint> @@ -74,7 +74,7 @@ public: protected: bool auto_transform(const void *p_in, void *p_out, json &p_jmsg); - void add_man_to_path(std::string p_new, std::string p_path); + void add_man_to_path(std::string p_new, std::string p_path, json p_jmsg); virtual int flush_next(); @@ -82,180 +82,23 @@ protected: std::shared_ptr<DataManBase> get_man(std::string method); - inline void logging(std::string p_msg, std::string p_man = "", - std::ostream &out = std::cout) - { - if (p_man == "") - p_man = name(); - out << "["; - out << p_man; - out << "]"; - out << " "; - out << p_msg; - out << std::endl; - } - - inline bool check_json(json p_jmsg, std::vector<std::string> p_strings, - std::string p_man = "") - { - if (p_man == "") - p_man = name(); - for (auto i : p_strings) - { - if (p_jmsg[i] == nullptr) - { - if (p_man != "") - { - logging("JSON key " + i + " not found!", p_man); - } - return false; - } - } - return true; - } - - inline size_t product(size_t *shape) - { - size_t s = 1; - if (shape) - { - for (size_t i = 1; i <= shape[0]; i++) - { - s *= shape[i]; - } - } - return s; - } - - inline size_t product(std::vector<size_t> shape, size_t size = 1) - { - return accumulate(shape.begin(), shape.end(), size, - std::multiplies<size_t>()); - } - - inline size_t dsize(std::string dtype) - { - if (dtype == "char") - return sizeof(char); - if (dtype == "short") - return sizeof(short); - if (dtype == "int") - return sizeof(int); - if (dtype == "long") - return sizeof(long); - if (dtype == "unsigned char") - return sizeof(unsigned char); - if (dtype == "unsigned short") - return sizeof(unsigned short); - if (dtype == "unsigned int") - return sizeof(unsigned int); - if (dtype == "unsigned long") - return sizeof(unsigned long); - if (dtype == "float") - return sizeof(float); - if (dtype == "double") - return sizeof(double); - if (dtype == "long double") - return sizeof(long double); - if (dtype == "std::complex<float>" or dtype == "complex<float>") - return sizeof(std::complex<float>); - if (dtype == "std::complex<double>") - return sizeof(std::complex<double>); - - if (dtype == "int8_t") - return sizeof(int8_t); - if (dtype == "uint8_t") - return sizeof(uint8_t); - if (dtype == "int16_t") - return sizeof(int16_t); - if (dtype == "uint16_t") - return sizeof(uint16_t); - if (dtype == "int32_t") - return sizeof(int32_t); - if (dtype == "uint32_t") - return sizeof(uint32_t); - if (dtype == "int64_t") - return sizeof(int64_t); - if (dtype == "uint64_t") - return sizeof(uint64_t); - return 0; - } - - inline json atoj(unsigned int *array) - { - json j; - if (array) - { - if (array[0] > 0) - { - j = {array[1]}; - for (unsigned int i = 2; i <= array[0]; i++) - { - j.insert(j.end(), array[i]); - } - } - } - return j; - } - - inline std::string rmquote(std::string in) - { - return in.substr(1, in.length() - 2); - } - - inline bool isin(std::string a, json j) - { - for (unsigned int i = 0; i < j.size(); i++) - { - if (j[i] == a) - return true; - } - return false; - } - - inline int closest(int v, json j, bool up) - { - int s = 100, k = 0, t; - for (unsigned int i = 0; i < j.size(); i++) - { - if (up) - t = j[i].get<int>() - v; - else - t = v - j[i].get<int>(); - if (t >= 0 && t < s) - { - s = t; - k = i; - } - } - return k; - } - - inline void check_shape(json &p_jmsg) - { - std::vector<size_t> varshape; - if (check_json(p_jmsg, {"varshape"})) - { - varshape = p_jmsg["varshape"].get<std::vector<size_t>>(); - } - else - { - return; - } - if (p_jmsg["putshape"] == nullptr) - { - p_jmsg["putshape"] = varshape; - } - if (p_jmsg["offset"] == nullptr) - { - p_jmsg["offset"] = std::vector<size_t>(varshape.size(), 0); - } - p_jmsg["putbytes"] = - product(p_jmsg["putshape"].get<std::vector<size_t>>(), - dsize(p_jmsg["dtype"].get<std::string>())); - p_jmsg["varbytes"] = - product(varshape, dsize(p_jmsg["dtype"].get<std::string>())); - } + void logging(std::string p_msg, std::string p_man = "", + std::ostream &out = std::cout); + + bool check_json(json p_jmsg, std::vector<std::string> p_strings, + std::string p_man = ""); + + size_t product(size_t *shape); + + size_t product(std::vector<size_t> shape, size_t size = 1); + + size_t dsize(std::string dtype); + + json atoj(unsigned int *array); + + int closest(int v, json j, bool up); + + void check_shape(json &p_jmsg); std::function<void(const void *, std::string, std::string, std::string, std::vector<size_t>)> diff --git a/source/dataman/DumpMan.cpp b/source/dataman/DumpMan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..af6120bf69ce30fed9dd381eb24ca343f81eff60 --- /dev/null +++ b/source/dataman/DumpMan.cpp @@ -0,0 +1,85 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * DumpMan.cpp + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#include "DumpMan.h" + +int DumpMan::init(json p_jmsg) +{ + if (p_jmsg["dumping"].is_boolean()) + { + m_dumping = p_jmsg["dumping"].get<bool>(); + } + return 0; +} +int DumpMan::get(void *p_data, json &p_jmsg) { return 0; } + +int DumpMan::put(const void *p_data, json p_jmsg) +{ + put_begin(p_data, p_jmsg); + + if (!m_dumping) + { + return 1; + } + if (!check_json(p_jmsg, {"doid", "var", "dtype", "putshape"})) + { + return -1; + } + + std::string doid = p_jmsg["doid"]; + std::string var = p_jmsg["var"]; + std::string dtype = p_jmsg["dtype"]; + std::vector<size_t> putshape = + p_jmsg["putshape"].get<std::vector<size_t>>(); + std::vector<size_t> varshape = + p_jmsg["varshape"].get<std::vector<size_t>>(); + std::vector<size_t> offset = p_jmsg["offset"].get<std::vector<size_t>>(); + int numbers_to_print = 100; + if (numbers_to_print > product(putshape, 1)) + { + numbers_to_print = product(putshape, 1); + } + size_t putbytes = product(putshape, dsize(dtype)); + + std::cout << p_jmsg.dump(4) << std::endl; + std::cout << "total MBs = " << product(putshape, dsize(dtype)) / 1000000 + << std::endl; + + const void *data_to_dump; + + std::vector<char> data(putbytes); + + if (auto_transform(p_data, data.data(), p_jmsg)) + { + data_to_dump = data.data(); + } + else + { + data_to_dump = p_data; + } + + for (size_t i = 0; i < numbers_to_print; i++) + { + if (dtype == "float") + { + std::cout << static_cast<const float *>(data_to_dump)[i] << " "; + } + if (dtype == "double") + { + std::cout << static_cast<const double *>(data_to_dump)[i] << " "; + } + } + + std::cout << std::endl; + put_end(p_data, p_jmsg); + return 0; +} + +void DumpMan::flush() {} diff --git a/source/dataman/DumpMan.h b/source/dataman/DumpMan.h new file mode 100644 index 0000000000000000000000000000000000000000..4b327bd6fb3577f7560f57247521cfb099555329 --- /dev/null +++ b/source/dataman/DumpMan.h @@ -0,0 +1,36 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * DumpMan.h + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#ifndef DATAMAN_DUMPMAN_H_ +#define DATAMAN_DUMPMAN_H_ + +#include "DataMan.h" + +class DumpMan : public DataManBase +{ +public: + DumpMan() = default; + virtual ~DumpMan() = default; + + virtual int init(json p_jmsg); + virtual int put(const void *p_data, json p_jmsg); + virtual int get(void *p_data, json &p_jmsg); + void flush(); + std::string name() { return "DumpMan"; } + std::string type() { return "Dump"; } + virtual void transform(const void *p_in, void *p_out, json &p_jmsg){}; + +private: + bool m_dumping = true; +}; + +extern "C" DataManBase *getMan() { return new DumpMan; } + +#endif diff --git a/source/dataman/MdtmMan.cpp b/source/dataman/MdtmMan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7fd0dd183a584e27d9b1f9663491a993eb095472 --- /dev/null +++ b/source/dataman/MdtmMan.cpp @@ -0,0 +1,258 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * MdtmMan.cpp + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#include "MdtmMan.h" + +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <zmq.h> + +MdtmMan::~MdtmMan() +{ + if (zmq_ipc_req) + { + zmq_close(zmq_ipc_req); + } +} + +int MdtmMan::init(json p_jmsg) +{ + + StreamMan::init(p_jmsg); + + if (p_jmsg["pipe_prefix"].is_string()) + { + pipe_desc["pipe_prefix"] = p_jmsg["pipe_prefix"].get<std::string>(); + } + else + { + pipe_desc["pipe_prefix"] = "/tmp/MdtmManPipes/"; + } + + pipe_desc["operation"] = "init"; + pipe_desc["mode"] = m_stream_mode; + + std::string pipename_prefix = "MdtmManPipe"; + for (int i = 0; i < m_num_channels; i++) + { + std::stringstream pipename; + pipename << pipename_prefix << i; + if (i == 0) + { + pipe_desc["pipe_names"] = {pipename.str()}; + pipe_desc["loss_tolerance"] = {m_tolerance[i]}; + pipe_desc["priority"] = {m_priority[i]}; + } + else + { + pipe_desc["pipe_names"].insert(pipe_desc["pipe_names"].end(), + pipename.str()); + pipe_desc["loss_tolerance"].insert( + pipe_desc["loss_tolerance"].end(), m_tolerance[i]); + pipe_desc["priority"].insert(pipe_desc["priority"].end(), + m_priority[i]); + } + } + + // ZMQ_DataMan_MDTM + if (m_stream_mode == "sender") + { + zmq_ipc_req = zmq_socket(zmq_context, ZMQ_REQ); + zmq_connect(zmq_ipc_req, "ipc:///tmp/ADIOS_MDTM_pipe"); + char buffer_return[10]; + zmq_send(zmq_ipc_req, pipe_desc.dump().c_str(), + pipe_desc.dump().length(), 0); + zmq_recv(zmq_ipc_req, buffer_return, sizeof(buffer_return), 0); + } + + // Pipes + mkdir(pipe_desc["pipe_prefix"].get<std::string>().c_str(), 0755); + for (auto i : pipe_desc["pipe_names"].get<std::vector<std::string>>()) + { + std::string filename = pipe_desc["pipe_prefix"].get<std::string>() + i; + mkfifo(filename.c_str(), 0666); + } + + for (int i = 0; i < m_num_channels; i++) + { + std::stringstream pipename; + pipename << pipename_prefix << i; + std::string fullpipename = + pipe_desc["pipe_prefix"].get<std::string>() + pipename.str(); + if (m_stream_mode == "sender") + { + int fp = open(fullpipename.c_str(), O_WRONLY); + pipes.push_back(fp); + } + if (m_stream_mode == "receiver") + { + int fp = open(fullpipename.c_str(), O_RDONLY | O_NONBLOCK); + pipes.push_back(fp); + } + pipenames.push_back(pipename.str()); + } + return 0; +} + +int MdtmMan::put(const void *p_data, json p_jmsg) +{ + put_begin(p_data, p_jmsg); + + std::vector<size_t> putshape = + p_jmsg["putshape"].get<std::vector<size_t>>(); + std::vector<size_t> varshape = + p_jmsg["varshape"].get<std::vector<size_t>>(); + std::string dtype = p_jmsg["dtype"]; + + int priority = 100; + if (p_jmsg["priority"].is_number_integer()) + { + priority = p_jmsg["priority"].get<int>(); + } + + int index; + if (m_parallel_mode == "round") + { + if (m_current_channel == m_num_channels - 1) + { + index = 0; + m_current_channel = 0; + } + else + { + m_current_channel++; + index = m_current_channel; + } + } + else if (m_parallel_mode == "priority") + { + index = closest(priority, pipe_desc["priority"], true); + } + + p_jmsg["pipe"] = pipe_desc["pipe_names"][index]; + size_t putbytes = product(putshape, dsize(dtype)); + p_jmsg["putbytes"] = putbytes; + size_t varbytes = product(varshape, dsize(dtype)); + p_jmsg["varbytes"] = varbytes; + + StreamMan::put(p_data, p_jmsg); + + index = 0; + for (int i = 0; i < pipenames.size(); i++) + { + if (p_jmsg["pipe"].get<std::string>() == pipenames[i]) + { + index = i; + } + } + std::string pipename = pipe_desc["pipe_prefix"].get<std::string>() + + p_jmsg["pipe"].get<std::string>(); + write(pipes[index], p_data, putbytes); + put_end(p_data, p_jmsg); + return 0; +} + +int MdtmMan::get(void *p_data, json &p_jmsg) { return 0; } + +void MdtmMan::on_recv(json jmsg) +{ + + // push new request + jqueue.push(jmsg); + bqueue.push(nullptr); + iqueue.push(0); + + // for flush + if (jqueue.front()["operation"] == "flush") + { + callback(); + m_cache.clean_all("nan"); + bqueue.pop(); + iqueue.pop(); + jqueue.pop(); + } + + if (jqueue.size() == 0) + { + return; + } + + // for put + for (int outloop = 0; outloop < jqueue.size() * 2; outloop++) + { + if (jqueue.front()["operation"] == "put") + { + // allocate buffer + size_t putbytes = jqueue.front()["putbytes"].get<size_t>(); + if (!bqueue.front()) + { + bqueue.front() = malloc(putbytes); + } + + // determine the pipe for the head request + json msg = jqueue.front(); + if (msg == nullptr) + { + break; + } + int pipeindex = 0; + for (int i = 0; i < pipenames.size(); i++) + { + if (msg["pipe"].get<std::string>() == pipenames[i]) + { + pipeindex = i; + } + } + + // read the head request + int error_times = 0; + int s = iqueue.front(); + putbytes = msg["putbytes"].get<int>(); + while (s < putbytes) + { + int ret = + read(pipes[pipeindex], + static_cast<char *>(bqueue.front()) + s, putbytes - s); + if (ret > 0) + { + s += ret; + } + else + { + error_times++; + continue; + } + if (error_times > 1000000) + { + break; + } + } + + if (s == putbytes) + { + m_cache.put(bqueue.front(), msg); + if (bqueue.front()) + { + free(bqueue.front()); + } + bqueue.pop(); + iqueue.pop(); + jqueue.pop(); + break; + } + else + { + iqueue.front() = s; + } + } + } +} diff --git a/source/dataman/MdtmMan.h b/source/dataman/MdtmMan.h new file mode 100644 index 0000000000000000000000000000000000000000..018ded228408d470026236ff211c4042fa56aecc --- /dev/null +++ b/source/dataman/MdtmMan.h @@ -0,0 +1,47 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * MdtmMan.h + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#ifndef DATAMAN_MDTMMAN_H_ +#define DATAMAN_MDTMMAN_H_ + +#include "StreamMan.h" + +#include <queue> + +class MdtmMan : public StreamMan +{ +public: + MdtmMan() = default; + virtual ~MdtmMan(); + + virtual int init(json p_jmsg); + virtual int put(const void *p_data, json p_jmsg); + virtual int get(void *p_data, json &p_jmsg); + virtual void transform(const void *p_in, void *p_out, json &p_jmsg){}; + + void on_recv(json msg); + std::string name() { return "MdtmMan"; } + +private: + void *zmq_ipc_req = NULL; + int zmq_msg_size = 1024; + std::string getmode = "callback"; + std::vector<int> pipes; + std::vector<std::string> pipenames; + std::queue<json> jqueue; + std::queue<void *> bqueue; + std::queue<int> iqueue; + json pipe_desc; + +}; // end of class MdtmMan + +extern "C" DataManBase *getMan() { return new MdtmMan; } + +#endif diff --git a/source/dataman/StreamMan.cpp b/source/dataman/StreamMan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4879d3588d734bf1dcf1c9861a90f5736d138b15 --- /dev/null +++ b/source/dataman/StreamMan.cpp @@ -0,0 +1,144 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * StreamMan.cpp + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#include "StreamMan.h" + +#include <unistd.h> + +#include <iostream> +#include <sstream> + +#include "zmq.h" + +StreamMan::~StreamMan() +{ + if (zmq_meta) + { + zmq_close(zmq_meta); + } + if (zmq_context) + { + zmq_ctx_destroy(zmq_context); + } + zmq_meta_rep_thread_active = false; + if (zmq_meta_rep_thread.joinable()) + { + zmq_meta_rep_thread.join(); + } +} + +int StreamMan::init(json p_jmsg) +{ + if (check_json(p_jmsg, {"stream_mode", "remote_ip", "local_ip", + "remote_port", "local_port"}, + "StreamMan")) + { + m_stream_mode = p_jmsg["stream_mode"]; + m_local_ip = p_jmsg["local_ip"]; + m_remote_ip = p_jmsg["remote_ip"]; + m_local_port = p_jmsg["local_port"]; + m_remote_port = p_jmsg["remote_port"]; + std::string remote_address = + make_address(m_remote_ip, m_remote_port, "tcp"); + std::string local_address = + make_address(m_local_ip, m_local_port, "tcp"); + + m_tolerance.assign(m_num_channels, 0); + m_priority.assign(m_num_channels, 100); + if (p_jmsg["num_channels"].is_number_integer()) + { + m_num_channels = p_jmsg["num_channels"].get<int>(); + } + if (p_jmsg["tolerance"] != nullptr) + { + m_tolerance = p_jmsg["tolerance"].get<std::vector<int>>(); + } + if (p_jmsg["priority"] != nullptr) + { + m_priority = p_jmsg["priority"].get<std::vector<int>>(); + } + + if (!zmq_context) + { + zmq_context = zmq_ctx_new(); + zmq_meta = zmq_socket(zmq_context, ZMQ_PAIR); + if (m_stream_mode == "sender") + { + zmq_connect(zmq_meta, remote_address.c_str()); + logging("StreamMan::init " + remote_address + " connected"); + } + else if (m_stream_mode == "receiver") + { + zmq_bind(zmq_meta, local_address.c_str()); + logging("StreamMan::init " + local_address + " bound"); + } + zmq_meta_rep_thread_active = true; + zmq_meta_rep_thread = + std::thread(&StreamMan::zmq_meta_rep_thread_func, this); + } + return 0; + } + else + { + return -1; + } +} + +void StreamMan::callback() +{ + if (m_callback) + { + std::vector<std::string> do_list = m_cache.get_do_list(); + for (std::string i : do_list) + { + std::vector<std::string> var_list = m_cache.get_var_list(i); + for (std::string j : var_list) + { + m_callback(m_cache.get_buffer(i, j), i, j, + m_cache.get_dtype(i, j), m_cache.get_shape(i, j)); + } + } + } + else + { + logging("callback called but callback function not registered!"); + } +} + +void StreamMan::flush() +{ + json msg; + msg["operation"] = "flush"; + zmq_send(zmq_meta, msg.dump().c_str(), msg.dump().length(), 0); +} + +void StreamMan::zmq_meta_rep_thread_func() +{ + while (zmq_meta_rep_thread_active) + { + char msg[1024] = ""; + int err = zmq_recv(zmq_meta, msg, 1024, ZMQ_NOBLOCK); + std::string smsg = msg; + if (err >= 0) + { + logging("StreamMan::zmq_meta_rep_thread_func: " + smsg); + json j = json::parse(msg); + on_recv(j); + } + usleep(10); + } +} + +int StreamMan::put(const void *p_data, json p_jmsg) +{ + p_jmsg["operation"] = "put"; + zmq_send(zmq_meta, p_jmsg.dump().c_str(), p_jmsg.dump().length(), 0); + return 0; +} diff --git a/source/dataman/StreamMan.h b/source/dataman/StreamMan.h new file mode 100644 index 0000000000000000000000000000000000000000..4167a2dbf2996938cf250cab1219c813f67c9e1d --- /dev/null +++ b/source/dataman/StreamMan.h @@ -0,0 +1,64 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * StreamMan.h + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#ifndef DATAMAN_STREAMMAN_H_ +#define DATAMAN_STREAMMAN_H_ + +#include "CacheMan.h" + +#include <thread> + +class StreamMan : public DataManBase +{ +public: + StreamMan() = default; + virtual ~StreamMan(); + + virtual int init(json p_jmsg); + virtual int put(const void *p_data, json p_jmsg); + virtual void on_recv(json msg) = 0; + void flush(); + virtual std::string type() { return "Stream"; } + +protected: + void *zmq_context = NULL; + CacheMan m_cache; + void callback(); + + std::string m_get_mode = "callback"; + std::string m_stream_mode; + std::string m_local_ip; + std::string m_remote_ip; + int m_local_port; + int m_remote_port; + int m_num_channels = 1; + std::vector<int> m_tolerance; + std::vector<int> m_priority; + + // parallel + std::string m_parallel_mode = "round"; // round, priority + int m_current_channel = 0; + + inline std::string make_address(std::string ip, int port, + std::string protocol) + { + std::stringstream address; + address << protocol << "://" << ip << ":" << port; + return address.str(); + } + +private: + void *zmq_meta = NULL; + void zmq_meta_rep_thread_func(); + bool zmq_meta_rep_thread_active; + std::thread zmq_meta_rep_thread; +}; + +#endif diff --git a/source/dataman/TemporalMan.cpp b/source/dataman/TemporalMan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a3582e614859e962ecc1ac5e67901a0ab64ab876 --- /dev/null +++ b/source/dataman/TemporalMan.cpp @@ -0,0 +1,26 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * TemporalMan.cpp + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#include "TemporalMan.h" + +int TemporalMan::init(json p_jmsg) { return 0; } + +int TemporalMan::put(const void *p_data, json p_jmsg) +{ + put_begin(p_data, p_jmsg); + put_end(p_data, p_jmsg); + return 0; +} + +int TemporalMan::get(void *p_data, json &p_jmsg) { return 0; } + +void TemporalMan::flush() {} + +void TemporalMan::transform(const void *p_in, void *p_out, json &p_jmsg) {} diff --git a/source/dataman/TemporalMan.h b/source/dataman/TemporalMan.h new file mode 100644 index 0000000000000000000000000000000000000000..8e120b8e429a58c05e385c72c477bc1fb5d60ffb --- /dev/null +++ b/source/dataman/TemporalMan.h @@ -0,0 +1,31 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * TemporalMan.h + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#ifndef DATAMAN_TEMPORALMAN_H_ +#define DATAMAN_TEMPORALMAN_H_ + +#include "CompressMan.h" + +class TemporalMan : public CompressMan +{ +public: + TemporalMan() = default; + virtual ~TemporalMan() = default; + virtual int init(json p_jmsg); + virtual int put(const void *p_data, json p_jmsg); + virtual int get(void *p_data, json &p_jmsg); + virtual void flush(); + virtual void transform(const void *p_in, void *p_out, json &p_jmsg); + std::string name() { return "TemporalMan"; } +}; + +extern "C" DataManBase *getMan() { return new TemporalMan; } + +#endif diff --git a/source/dataman/ZfpMan.cpp b/source/dataman/ZfpMan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c1cf3fa9fa4f891c8878fb51d0428b73cfa27206 --- /dev/null +++ b/source/dataman/ZfpMan.cpp @@ -0,0 +1,280 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * ZfpMan.cpp + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#include "ZfpMan.h" + +#include <zfp.h> + +int ZfpMan::init(json p_jmsg) +{ + if (p_jmsg["compression_rate"].is_number()) + { + m_compression_rate = p_jmsg["compression_rate"].get<double>(); + } + return 0; +} + +int ZfpMan::put(const void *p_data, json p_jmsg) +{ + put_begin(p_data, p_jmsg); + + void *compressed_data = NULL; + if (check_json(p_jmsg, {"doid", "var", "dtype", "putshape"}, "ZfpMan")) + { + if (not p_jmsg["compression_rate"].is_number()) + { + p_jmsg["compression_rate"] = m_compression_rate; + } + compressed_data = compress(const_cast<void *>(p_data), p_jmsg); + } + + put_end(compressed_data, p_jmsg); + if (compressed_data) + { + free(compressed_data); + } + return 0; +} + +int ZfpMan::get(void *p_data, json &p_jmsg) { return 0; } + +void ZfpMan::flush() { flush_next(); } + +void *ZfpMan::compress(void *p_data, json &p_jmsg) +{ + + std::string dtype = p_jmsg["dtype"]; + std::vector<size_t> shape = p_jmsg["putshape"].get<std::vector<size_t>>(); + int compression_rate = p_jmsg["compression_rate"].get<int>(); + + int status = 0; // return value: 0 = success + uint dim = 1; + zfp_type type = zfp_type_none; // array scalar type + zfp_field *field; // array meta data + zfp_stream *zfp; // compressed stream + size_t bufsize; // byte size of compressed buffer + bitstream *stream; // bit stream to write to or read from + size_t zfpsize; // byte size of compressed stream + + // allocate meta data for the 3D array a[nz][ny][nx] + if (dtype == "int") + { + type = zfp_type_int32; + } + else if (dtype == "long") + { + type = zfp_type_int64; + } + else if (dtype == "float") + { + type = zfp_type_float; + } + else if (dtype == "double") + { + type = zfp_type_double; + } + + switch (shape.size()) + { + case 3: + field = zfp_field_3d(p_data, type, shape[0], shape[1], shape[2]); + dim = 3; + break; + case 2: + field = zfp_field_2d(p_data, type, shape[0], shape[1]); + dim = 2; + break; + case 1: + field = zfp_field_1d(p_data, type, shape[0]); + break; + default: + field = zfp_field_1d(p_data, type, product(shape)); + } + + // allocate meta data for a compressed stream + zfp = zfp_stream_open(NULL); + + // set compression mode and parameters via one of three functions + zfp_stream_set_rate(zfp, compression_rate, type, dim, 0); + // zfp_stream_set_precision(zfp, m_precision, type); + // zfp_stream_set_accuracy(zfp, m_accuracy, type); + + // allocate buffer for compressed data + bufsize = zfp_stream_maximum_size(zfp, field); + void *buffer = malloc(bufsize); + + // associate bit stream with allocated buffer + stream = stream_open(buffer, bufsize); + zfp_stream_set_bit_stream(zfp, stream); + zfp_stream_rewind(zfp); + + // compress or decompress entire array + + zfpsize = zfp_compress(zfp, field); + + if (!zfpsize) + { + logging("ZFP compression failed!"); + status = 1; + } + + p_jmsg["compressed_size"] = bufsize; + p_jmsg["compression_method"] = "zfp"; + + // clean up + zfp_field_free(field); + zfp_stream_close(zfp); + stream_close(stream); + + return buffer; +} + +void *ZfpMan::decompress(void *p_data, json p_jmsg) +{ + + std::string dtype = p_jmsg["dtype"]; + std::vector<size_t> shape = p_jmsg["putshape"].get<std::vector<size_t>>(); + int compression_rate = p_jmsg["compression_rate"].get<int>(); + + int status = 0; // return value: 0 = success + uint dim = 1; + zfp_type type = zfp_type_none; // array scalar type + zfp_field *field; // array meta data + zfp_stream *zfp; // compressed stream + size_t bufsize = p_jmsg["compressed_size"] + .get<size_t>(); // byte size of compressed buffer + bitstream *stream; // bit stream to write to or read from + size_t zfpsize; // byte size of compressed stream + + // allocate meta data for the 3D array a[nz][ny][nx] + if (dtype == "int") + { + type = zfp_type_int32; + } + else if (dtype == "long") + { + type = zfp_type_int64; + } + else if (dtype == "float") + { + type = zfp_type_float; + } + else if (dtype == "double") + { + type = zfp_type_double; + } + + void *data; + data = malloc(product(shape, dsize(dtype))); + + switch (shape.size()) + { + case 3: + field = zfp_field_3d(data, type, shape[0], shape[1], shape[2]); + dim = 3; + break; + case 2: + field = zfp_field_2d(data, type, shape[0], shape[1]); + dim = 2; + break; + case 1: + field = zfp_field_1d(data, type, shape[0]); + break; + default: + field = zfp_field_1d(data, type, product(shape)); + } + + zfp = zfp_stream_open(NULL); + zfp_stream_set_rate(zfp, compression_rate, type, dim, 0); + stream = stream_open(p_data, bufsize); + zfp_stream_set_bit_stream(zfp, stream); + zfp_stream_rewind(zfp); + if (!zfp_decompress(zfp, field)) + { + fprintf(stderr, "decompression failed\n"); + status = 1; + } + zfp_field_free(field); + zfp_stream_close(zfp); + stream_close(stream); + + return data; +} + +void ZfpMan::transform(const void *p_in, void *p_out, json &p_jmsg) +{ + + std::string dtype = p_jmsg["dtype"]; + std::vector<size_t> shape = p_jmsg["putshape"].get<std::vector<size_t>>(); + int compression_rate = p_jmsg["compression_rate"].get<int>(); + + int status = 0; // return value: 0 = success + uint dim = 1; + zfp_type type = zfp_type_none; // array scalar type + zfp_field *field; // array meta data + zfp_stream *zfp; // compressed stream + size_t bufsize = p_jmsg["compressed_size"] + .get<size_t>(); // byte size of compressed buffer + bitstream *stream; // bit stream to write to or read from + size_t zfpsize; // byte size of compressed stream + + // allocate meta data for the 3D array a[nz][ny][nx] + if (dtype == "int") + { + type = zfp_type_int32; + } + else if (dtype == "long") + { + type = zfp_type_int64; + } + else if (dtype == "float") + { + type = zfp_type_float; + } + else if (dtype == "double") + { + type = zfp_type_double; + } + + switch (shape.size()) + { + case 3: + field = zfp_field_3d(p_out, type, shape[0], shape[1], shape[2]); + dim = 3; + break; + case 2: + field = zfp_field_2d(p_out, type, shape[0], shape[1]); + dim = 2; + break; + case 1: + field = zfp_field_1d(p_out, type, shape[0]); + break; + default: + field = zfp_field_1d(p_out, type, product(shape)); + } + + zfp = zfp_stream_open(NULL); + zfp_stream_set_rate(zfp, compression_rate, type, dim, 0); + stream = stream_open(const_cast<void *>(p_in), bufsize); + zfp_stream_set_bit_stream(zfp, stream); + zfp_stream_rewind(zfp); + if (!zfp_decompress(zfp, field)) + { + fprintf(stderr, "decompression failed\n"); + status = 1; + } + zfp_field_free(field); + zfp_stream_close(zfp); + stream_close(stream); + + p_jmsg.erase("compression_rate"); + p_jmsg.erase("compression_method"); + p_jmsg.erase("compressed_size"); +} diff --git a/source/dataman/ZfpMan.h b/source/dataman/ZfpMan.h new file mode 100644 index 0000000000000000000000000000000000000000..94f75f92a1a8a46d4dd2c6371b33751c513cf2a4 --- /dev/null +++ b/source/dataman/ZfpMan.h @@ -0,0 +1,35 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * ZfpMan.h + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#ifndef DATAMAN_ZFPMAN_H_ +#define DATAMAN_ZFPMAN_H_ + +#include "CompressMan.h" + +class ZfpMan : public CompressMan +{ +public: + ZfpMan() = default; + virtual ~ZfpMan() = default; + virtual int init(json p_jmsg); + virtual int put(const void *p_data, json p_jmsg); + virtual int get(void *p_data, json &p_jmsg); + virtual void flush(); + void *compress(void *p_data, json &p_jmsg); + void *decompress(void *p_data, json p_jmsg); + virtual void transform(const void *p_in, void *p_out, json &p_jmsg); + std::string name() { return "ZfpMan"; } +private: + double m_compression_rate = 8; +}; + +extern "C" DataManBase *getMan() { return new ZfpMan; } + +#endif diff --git a/source/dataman/ZmqMan.cpp b/source/dataman/ZmqMan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..839a8eb81121794603b7cc497369b263a4dc65ac --- /dev/null +++ b/source/dataman/ZmqMan.cpp @@ -0,0 +1,88 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * ZmqMan.cpp + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#include "ZmqMan.h" + +#include <sys/stat.h> +#include <unistd.h> + +#include "zmq.h" + +ZmqMan::~ZmqMan() +{ + if (zmq_data) + zmq_close(zmq_data); +} + +int ZmqMan::init(json p_jmsg) +{ + StreamMan::init(p_jmsg); + zmq_data = zmq_socket(zmq_context, ZMQ_PAIR); + std::string local_address = + make_address(m_local_ip, m_local_port + 1, "tcp"); + std::string remote_address = + make_address(m_remote_ip, m_remote_port + 1, "tcp"); + if (m_stream_mode == "sender") + { + zmq_connect(zmq_data, remote_address.c_str()); + logging("ZmqMan::init " + remote_address + " connected"); + } + else if (m_stream_mode == "receiver") + { + zmq_bind(zmq_data, local_address.c_str()); + logging("ZmqMan::init " + local_address + " bound"); + } + return 0; +} + +int ZmqMan::put(const void *p_data, json p_jmsg) +{ + put_begin(p_data, p_jmsg); + StreamMan::put(p_data, p_jmsg); + zmq_send(zmq_data, p_data, p_jmsg["putbytes"], 0); + put_end(p_data, p_jmsg); + return 0; +} + +int ZmqMan::get(void *p_data, json &p_jmsg) { return 0; } + +void ZmqMan::on_recv(json msg) +{ + if (msg["operation"] == "put") + { + if (msg["compression_method"] == nullptr) + { + size_t putbytes = msg["putbytes"].get<size_t>(); + std::vector<char> data; + data.resize(putbytes); + int err = zmq_recv(zmq_data, data.data(), putbytes, 0); + m_cache.put(data.data(), msg); + } + else + { + size_t putbytes = msg["putbytes"].get<size_t>(); + size_t compressed_size = msg["compressed_size"].get<size_t>(); + std::vector<char> compressed_data; + compressed_data.resize(compressed_size); + std::vector<char> data; + data.resize(putbytes); + int err = + zmq_recv(zmq_data, compressed_data.data(), compressed_size, 0); + auto_transform(compressed_data.data(), data.data(), msg); + m_cache.put(data.data(), msg); + } + } + else if (msg["operation"] == "flush") + { + callback(); + m_cache.flush(); + m_cache.clean_all("nan"); + } +} diff --git a/source/dataman/ZmqMan.h b/source/dataman/ZmqMan.h new file mode 100644 index 0000000000000000000000000000000000000000..fc14026a7df4575597f23345b7d260ff5328e812 --- /dev/null +++ b/source/dataman/ZmqMan.h @@ -0,0 +1,36 @@ +/* + * Distributed under the OSI-approved Apache License, Version 2.0. See + * accompanying file Copyright.txt for details. + * + * ZmqMan.h + * + * Created on: Apr 20, 2017 + * Author: Jason Wang + */ + +#ifndef DATAMAN_ZMQMAN_H_ +#define DATAMAN_ZMQMAN_H_ + +#include "StreamMan.h" + +class ZmqMan : public StreamMan +{ +public: + ZmqMan() = default; + virtual ~ZmqMan(); + + virtual int init(json p_jmsg); + virtual int put(const void *p_data, json p_jmsg); + virtual int get(void *p_data, json &p_jmsg); + virtual void transform(const void *p_in, void *p_out, json &p_jmsg){}; + + virtual void on_recv(json msg); + std::string name() { return "ZmqMan"; } + +private: + void *zmq_data = NULL; +}; + +extern "C" DataManBase *getMan() { return new ZmqMan; } + +#endif diff --git a/source/dataman/json.hpp b/source/dataman/json.hpp index 2f277300c2dba19d598efc486ea80f21a742ad5f..86a62c0b065b525d9686b623b44e2656be74f5d8 100644 --- a/source/dataman/json.hpp +++ b/source/dataman/json.hpp @@ -1,7 +1,7 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.0.10 +| | |__ | | | | | | version 2.1.1 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License <http://opensource.org/licenses/MIT>. @@ -29,30 +29,29 @@ SOFTWARE. #ifndef NLOHMANN_JSON_HPP #define NLOHMANN_JSON_HPP -#include <algorithm> // all_of, for_each, transform -#include <array> // array -#include <cassert> // assert -#include <cctype> // isdigit -#include <ciso646> // and, not, or -#include <cmath> // isfinite, ldexp, signbit -#include <cstddef> // nullptr_t, ptrdiff_t, size_t -#include <cstdint> // int64_t, uint64_t -#include <cstdlib> // strtod, strtof, strtold, strtoul -#include <cstring> // strlen +#include <algorithm> // all_of, copy, fill, find, for_each, none_of, remove, reverse, transform +#include <array> // array +#include <cassert> // assert +#include <ciso646> // and, not, or +#include <clocale> // lconv, localeconv +#include <cmath> // isfinite, labs, ldexp, signbit +#include <cstddef> // nullptr_t, ptrdiff_t, size_t +#include <cstdint> // int64_t, uint64_t +#include <cstdlib> // abort, strtod, strtof, strtold, strtoul, strtoll, strtoull +#include <cstring> // strlen +#include <forward_list> // forward_list #include <functional> // function, hash, less #include <initializer_list> // initializer_list -#include <iomanip> // setw #include <iostream> // istream, ostream -#include <iterator> // advance, begin, bidirectional_iterator_tag, distance, end, inserter, iterator, iterator_traits, next, random_access_iterator_tag, reverse_iterator +#include <iterator> // advance, begin, back_inserter, bidirectional_iterator_tag, distance, end, inserter, iterator, iterator_traits, next, random_access_iterator_tag, reverse_iterator #include <limits> // numeric_limits #include <locale> // locale #include <map> // map #include <memory> // addressof, allocator, allocator_traits, unique_ptr #include <numeric> // accumulate #include <sstream> // stringstream -#include <stdexcept> // domain_error, invalid_argument, out_of_range #include <string> // getline, stoi, string, to_string -#include <type_traits> // add_pointer, enable_if, is_arithmetic, is_base_of, is_const, is_constructible, is_convertible, is_floating_point, is_integral, is_nothrow_move_assignable, std::is_nothrow_move_constructible, std::is_pointer, std::is_reference, std::is_same, remove_const, remove_pointer, remove_reference +#include <type_traits> // add_pointer, conditional, decay, enable_if, false_type, integral_constant, is_arithmetic, is_base_of, is_const, is_constructible, is_convertible, is_default_constructible, is_enum, is_floating_point, is_integral, is_nothrow_move_assignable, is_nothrow_move_constructible, is_pointer, is_reference, is_same, is_scalar, is_signed, remove_const, remove_cv, remove_pointer, remove_reference, true_type, underlying_type #include <utility> // declval, forward, make_pair, move, pair, swap #include <vector> // vector @@ -92,7 +91,9 @@ SOFTWARE. #endif // allow to disable exceptions -#if not defined(JSON_NOEXCEPTION) || defined(__EXCEPTIONS) +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || \ + defined(_CPPUNWIND)) && \ + not defined(JSON_NOEXCEPTION) #define JSON_THROW(exception) throw exception #define JSON_TRY try #define JSON_CATCH(exception) catch (exception) @@ -112,1564 +113,2365 @@ namespace nlohmann /*! @brief unnamed namespace with internal helper functions -@since version 1.0.0 + +This namespace collects some functions that could not be defined inside the +@ref basic_json class. + +@since version 2.1.0 */ -namespace +namespace detail { +//////////////// +// exceptions // +//////////////// + /*! -@brief Helper to determine whether there's a key_type for T. +@brief general exception of the @ref basic_json class -Thus helper is used to tell associative containers apart from other containers -such as sequence containers. For instance, `std::map` passes the test as it -contains a `mapped_type`, whereas `std::vector` fails the test. +Extension of std::exception objects with a member @a id for exception ids. -@sa http://stackoverflow.com/a/7728728/266378 -@since version 1.0.0, overworked in version 2.0.6 +@note To have nothrow-copy-constructible exceptions, we internally use + std::runtime_error which can cope with arbitrary-length error messages. + Intermediate strings are built with static functions and then passed to + the actual constructor. + +@since version 3.0.0 */ -template <typename T> -struct has_mapped_type +class exception : public std::exception { -private: - template <typename U, typename = typename U::mapped_type> - static int detect(U &&); +public: + /// returns the explanatory string + virtual const char *what() const noexcept override { return m.what(); } - static void detect(...); + /// the id of the exception + const int id; -public: - static constexpr bool value = - std::is_integral<decltype(detect(std::declval<T>()))>::value; -}; +protected: + exception(int id_, const char *what_arg) : id(id_), m(what_arg) {} + + static std::string name(const std::string &ename, int id) + { + return "[json.exception." + ename + "." + std::to_string(id) + "] "; + } -} // namespace +private: + /// an exception object as storage for error messages + std::runtime_error m; +}; /*! -@brief a class to store JSON values +@brief exception indicating a parse error + +This excpetion is thrown by the library when a parse error occurs. Parse +errors can occur during the deserialization of JSON text as well as when +using JSON Patch. + +Member @a byte holds the byte index of the last read character in the input +file. + +@note For an input with n bytes, 1 is the index of the first character + and n+1 is the index of the terminating null byte or the end of + file. This also holds true when reading a byte vector (CBOR or + MessagePack). + +Exceptions have ids 1xx. + +name / id | example massage | description +------------------------------ | --------------- | ------------------------- +json.exception.parse_error.101 | parse error at 2: unexpected end of input; +expected string literal | This error indicates a syntax error while +deserializing a JSON text. The error message describes that an unexpected token +(character) was encountered, and the member @a byte indicates the error +position. +json.exception.parse_error.102 | parse error at 14: missing or wrong low +surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code +points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate +pairs"). This error indicates that the surrogate pair is incomplete or contains +an invalid code point. +json.exception.parse_error.103 | parse error: code points above 0x10FFFF are +invalid | Unicode supports code points up to 0x10FFFF. Code points above +0x10FFFF are invalid. +json.exception.parse_error.104 | parse error: JSON patch must be an array of +objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch +document to be a JSON document that represents an array of objects. +json.exception.parse_error.105 | parse error: operation must have string member +'op' | An operation of a JSON Patch document must contain exactly one "op" +member, whose value indicates the operation to perform. Its value must be one of +"add", "remove", "replace", "move", "copy", or "test"; other values are errors. +json.exception.parse_error.106 | parse error: array index '01' must not begin +with '0' | An array index in a JSON Pointer ([RFC +6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number wihtout a +leading `0`. +json.exception.parse_error.107 | parse error: JSON pointer must be empty or +begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing +a sequence of zero or more reference tokens, each prefixed by a `/` character. +json.exception.parse_error.108 | parse error: escape character '~' must be +followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid +escape sequences. +json.exception.parse_error.109 | parse error: array index 'one' is not a number +| A JSON Pointer array index must be a number. +json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from +vector | When parsing CBOR or MessagePack, the byte vector ends before the +complete value has been read. +json.exception.parse_error.111 | parse error: bad input stream | Parsing CBOR or +MessagePack from an input stream where the [`badbit` or +`failbit`](http://en.cppreference.com/w/cpp/io/ios_base/iostate) is set. +json.exception.parse_error.112 | parse error at 1: error reading CBOR; last +byte: 0xf8 | Not all types of CBOR or MessagePack are supported. This exception +occurs if an unsupported byte was read. +json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last +byte: 0x98 | While parsing a map key, a value that is not a string has been +read. + +@since version 3.0.0 +*/ +class parse_error : public exception +{ +public: + /*! + @brief create a parse error exception + @param[in] id the id of the exception + @param[in] byte_ the byte index where the error occured (or 0 if + the position cannot be determined) + @param[in] what_arg the explanatory string + @return parse_error object + */ + static parse_error create(int id, size_t byte_, const std::string &what_arg) + { + std::string w = exception::name("parse_error", id) + "parse error" + + (byte_ != 0 ? (" at " + std::to_string(byte_)) : "") + + ": " + what_arg; + return parse_error(id, byte_, w.c_str()); + } -@tparam ObjectType type for JSON objects (`std::map` by default; will be used -in @ref object_t) -@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used -in @ref array_t) -@tparam StringType type for JSON strings and object keys (`std::string` by -default; will be used in @ref string_t) -@tparam BooleanType type for JSON booleans (`bool` by default; will be used -in @ref boolean_t) -@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by -default; will be used in @ref number_integer_t) -@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c -`uint64_t` by default; will be used in @ref number_unsigned_t) -@tparam NumberFloatType type for JSON floating-point numbers (`double` by -default; will be used in @ref number_float_t) -@tparam AllocatorType type of the allocator to use (`std::allocator` by -default) + /*! + @brief byte index of the parse error -@requirement The class satisfies the following concept requirements: -- Basic - - -[DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): - JSON values can be default constructed. The result will be a JSON null - value. - - -[MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): - A JSON value can be constructed from an rvalue argument. - - -[CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): - A JSON value can be copy-constructed from an lvalue expression. - - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): - A JSON value van be assigned from an rvalue argument. - - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): - A JSON value can be copy-assigned from an lvalue expression. - - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): - JSON values can be destructed. -- Layout - - -[StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): - JSON values have - [standard -layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): - All non-static data members are private and standard layout types, the - class has no virtual functions or (virtual) base classes. -- Library-wide - - -[EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): - JSON values can be compared with `==`, see @ref - operator==(const_reference,const_reference). - - -[LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): - JSON values can be compared with `<`, see @ref - operator<(const_reference,const_reference). - - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): - Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of - other compatible types, using unqualified function call @ref swap(). - - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): - JSON values can be compared against `std::nullptr_t` objects which are used - to model the `null` value. -- Container - - [Container](http://en.cppreference.com/w/cpp/concept/Container): - JSON values can be used like STL containers and provide iterator access. - - -[ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); - JSON values can be used like STL containers and provide reverse iterator - access. + The byte index of the last read character in the input file. -@invariant The member variables @a m_value and @a m_type have the following -relationship: -- If `m_type == value_t::object`, then `m_value.object != nullptr`. -- If `m_type == value_t::array`, then `m_value.array != nullptr`. -- If `m_type == value_t::string`, then `m_value.string != nullptr`. -The invariants are checked by member function assert_invariant(). + @note For an input with n bytes, 1 is the index of the first character + and n+1 is the index of the terminating null byte or the end of + file. This also holds true when reading a byte vector (CBOR or + MessagePack). + */ + const size_t byte; -@internal -@note ObjectType trick from http://stackoverflow.com/a/9860911 -@endinternal +private: + parse_error(int id_, size_t byte_, const char *what_arg) + : exception(id_, what_arg), byte(byte_) + { + } +}; -@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange -Format](http://rfc7159.net/rfc7159) +/*! +@brief exception indicating errors with iterators + +Exceptions have ids 2xx. + +name / id | example massage | description +----------------------------------- | --------------- | +------------------------- +json.exception.invalid_iterator.201 | iterators are not compatible | The +iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are +not compatible, meaning they do not belong to the same container. Therefore, the +range (@a first, @a last) is invalid. +json.exception.invalid_iterator.202 | iterator does not fit current value | In +an erase or insert function, the passed iterator @a pos does not belong to the +JSON value for which the function was called. It hence does not define a valid +position for the deletion/insertion. +json.exception.invalid_iterator.203 | iterators do not fit current value | +Either iterator passed to function @ref erase(IteratorType first, IteratorType +last) does not belong to the JSON value from which values shall be erased. It +hence does not define a valid range to delete values from. +json.exception.invalid_iterator.204 | iterators out of range | When an iterator +range for a primitive type (number, boolean, or string) is passed to a +constructor or an erase function, this range has to be exactly (@ref begin(), +@ref end()), because this is the only way the single stored value is expressed. +All other ranges are invalid. +json.exception.invalid_iterator.205 | iterator out of range | When an iterator +for a primitive type (number, boolean, or string) is passed to an erase +function, the iterator has to be the @ref begin() iterator, because it is the +only way to address the stored value. All other iterators are invalid. +json.exception.invalid_iterator.206 | cannot construct with iterators from null +| The iterators passed to constructor @ref basic_json(InputIT first, InputIT +last) belong to a JSON null value and hence to not define a valid range. +json.exception.invalid_iterator.207 | cannot use key() for non-object iterators +| The key() member function can only be used on iterators belonging to a JSON +object, because other types do not have a concept of a key. +json.exception.invalid_iterator.208 | cannot use operator[] for object iterators +| The operator[] to specify a concrete offset cannot be used on iterators +belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.209 | cannot use offsets with object iterators | +The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a +JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.210 | iterators do not fit | The iterator range +passed to the insert function are not compatible, meaning they do not belong to +the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.211 | passed iterators may not belong to +container | The iterator range passed to the insert function must not be a +subrange of the container to insert to. +json.exception.invalid_iterator.212 | cannot compare iterators of different +containers | When two iterators are compared, they must belong to the same +container. +json.exception.invalid_iterator.213 | cannot compare order of object iterators | +The order of object iterators cannot be compated, because JSON objects are +unordered. +json.exception.invalid_iterator.214 | cannot get value | Cannot get value for +iterator: Either the iterator belongs to a null value or it is an iterator to a +primitive type (number, boolean, or string), but the iterator is different to +@ref begin(). + +@since version 3.0.0 +*/ +class invalid_iterator : public exception +{ +public: + static invalid_iterator create(int id, const std::string &what_arg) + { + std::string w = exception::name("invalid_iterator", id) + what_arg; + return invalid_iterator(id, w.c_str()); + } -@since version 1.0.0 +private: + invalid_iterator(int id_, const char *what_arg) : exception(id_, what_arg) + { + } +}; -@nosubgrouping +/*! +@brief exception indicating executing a member function with a wrong type + +Exceptions have ids 3xx. + +name / id | example massage | description +----------------------------- | --------------- | ------------------------- +json.exception.type_error.301 | cannot create object from initializer list | To +create an object from an initializer list, the initializer list must consist +only of a list of pairs whose first element is a string. When this constraint is +violated, an array is created instead. +json.exception.type_error.302 | type must be object, but is array | During +implicit or explicit value conversion, the JSON type must be compatible to the +target type. For instance, a JSON string can only be converted into string +types, but not into numbers or boolean types. +json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual +type is object | To retrieve a reference to a value stored in a @ref basic_json +object with @ref get_ref, the type of the reference must match the value type. +For instance, for a JSON array, the @a ReferenceType must be @ref array_t&. +json.exception.type_error.304 | cannot use at() with string | The @ref at() +member functions can only be executed for certain JSON types. +json.exception.type_error.305 | cannot use operator[] with string | The @ref +operator[] member functions can only be executed for certain JSON types. +json.exception.type_error.306 | cannot use value() with string | The @ref +value() member functions can only be executed for certain JSON types. +json.exception.type_error.307 | cannot use erase() with string | The @ref +erase() member functions can only be executed for certain JSON types. +json.exception.type_error.308 | cannot use push_back() with string | The @ref +push_back() and @ref operator+= member functions can only be executed for +certain JSON types. +json.exception.type_error.309 | cannot use insert() with | The @ref insert() +member functions can only be executed for certain JSON types. +json.exception.type_error.310 | cannot use swap() with number | The @ref swap() +member functions can only be executed for certain JSON types. +json.exception.type_error.311 | cannot use emplace_back() with string | The @ref +emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten +function converts an object whose keys are JSON Pointers back into an arbitrary +nested JSON value. The JSON Pointers must not overlap, because then the +resulting value would not be well defined. +json.exception.type_error.314 | only objects can be unflattened | The @ref +unflatten function only works for an object whose keys are JSON Pointers. +json.exception.type_error.315 | values in object must be primitive | The @ref +unflatten function only works for an object whose keys are JSON Pointers and +whose values are primitive. + +@since version 3.0.0 */ -template <template <typename U, typename V, typename... Args> class ObjectType = - std::map, - template <typename U, typename... Args> class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template <typename U> class AllocatorType = std::allocator> -class basic_json +class type_error : public exception { +public: + static type_error create(int id, const std::string &what_arg) + { + std::string w = exception::name("type_error", id) + what_arg; + return type_error(id, w.c_str()); + } + private: - /// workaround type for MSVC - using basic_json_t = - basic_json<ObjectType, ArrayType, StringType, BooleanType, - NumberIntegerType, NumberUnsignedType, NumberFloatType, - AllocatorType>; + type_error(int id_, const char *what_arg) : exception(id_, what_arg) {} +}; +/*! +@brief exception indicating access out of the defined range + +Exceptions have ids 4xx. + +name / id | example massage | description +------------------------------- | --------------- | ------------------------- +json.exception.out_of_range.401 | array index 3 is out of range | The provided +array index @a i is larger than @a size-1. +json.exception.out_of_range.402 | array index '-' (3) is out of range | The +special array index `-` in a JSON Pointer never describes a valid element of the +array, but the index past the end. That is, it can only be used to add elements +at this position, but not to read it. +json.exception.out_of_range.403 | key 'foo' not found | The provided key was not +found in the JSON object. +json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference +token in a JSON Pointer could not be resolved. +json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch +operations 'remove' and 'add' can not be applied to the root element of the JSON +value. +json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed +number could not be stored as without changing it to NaN or INF. + +@since version 3.0.0 +*/ +class out_of_range : public exception +{ public: - // forward declarations - template <typename U> - class iter_impl; - template <typename Base> - class json_reverse_iterator; - class json_pointer; + static out_of_range create(int id, const std::string &what_arg) + { + std::string w = exception::name("out_of_range", id) + what_arg; + return out_of_range(id, w.c_str()); + } - ///////////////////// - // container types // - ///////////////////// +private: + out_of_range(int id_, const char *what_arg) : exception(id_, what_arg) {} +}; - /// @name container types - /// The canonic container types to use @ref basic_json like any other STL - /// container. - /// @{ +/*! +@brief exception indicating other errors - /// the type of elements in a basic_json container - using value_type = basic_json; +Exceptions have ids 5xx. - /// the type of an element reference - using reference = value_type &; - /// the type of an element const reference - using const_reference = const value_type &; +name / id | example massage | description +------------------------------ | --------------- | ------------------------- +json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", +"value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful +operation is also printed. - /// a type to represent differences between iterators - using difference_type = std::ptrdiff_t; - /// a type to represent container sizes - using size_type = std::size_t; +@since version 3.0.0 +*/ +class other_error : public exception +{ +public: + static other_error create(int id, const std::string &what_arg) + { + std::string w = exception::name("other_error", id) + what_arg; + return other_error(id, w.c_str()); + } - /// the allocator type - using allocator_type = AllocatorType<basic_json>; +private: + other_error(int id_, const char *what_arg) : exception(id_, what_arg) {} +}; - /// the type of an element pointer - using pointer = typename std::allocator_traits<allocator_type>::pointer; - /// the type of an element const pointer - using const_pointer = - typename std::allocator_traits<allocator_type>::const_pointer; +/////////////////////////// +// JSON type enumeration // +/////////////////////////// - /// an iterator for a basic_json container - using iterator = iter_impl<basic_json>; - /// a const iterator for a basic_json container - using const_iterator = iter_impl<const basic_json>; - /// a reverse iterator for a basic_json container - using reverse_iterator = - json_reverse_iterator<typename basic_json::iterator>; - /// a const reverse iterator for a basic_json container - using const_reverse_iterator = - json_reverse_iterator<typename basic_json::const_iterator>; +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type - /// @} +@since version 1.0.0 +*/ +enum class value_t : uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function +}; - /*! - @brief returns the allocator associated with the container - */ - static allocator_type get_allocator() { return allocator_type(); } +/*! +@brief comparison operator for JSON types - /*! - @brief returns version information on the library - */ - static basic_json meta() +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string +- furthermore, each type is not smaller than itself + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array<uint8_t, 8> order = {{ + 0, // null + 3, // object + 4, // array + 5, // string + 1, // boolean + 2, // integer + 2, // unsigned + 2, // float + }}; + + // discarded values are not comparable + if (lhs == value_t::discarded or rhs == value_t::discarded) { - basic_json result; + return false; + } - result["copyright"] = "(C) 2013-2017 Niels Lohmann"; - result["name"] = "JSON for Modern C++"; - result["url"] = "https://github.com/nlohmann/json"; - result["version"] = { - {"string", "2.0.10"}, {"major", 2}, {"minor", 0}, {"patch", 10}, - }; + return order[static_cast<std::size_t>(lhs)] < + order[static_cast<std::size_t>(rhs)]; +} -#ifdef _WIN32 - result["platform"] = "win32"; -#elif defined __linux__ - result["platform"] = "linux"; -#elif defined __APPLE__ - result["platform"] = "apple"; -#elif defined __unix__ - result["platform"] = "unix"; -#else - result["platform"] = "unknown"; -#endif +///////////// +// helpers // +///////////// -#if defined(__clang__) - result["compiler"] = {{"family", "clang"}, - {"version", __clang_version__}}; -#elif defined(__ICC) || defined(__INTEL_COMPILER) - result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; -#elif defined(__GNUC__) || defined(__GNUG__) - result["compiler"] = { - {"family", "gcc"}, - {"version", std::to_string(__GNUC__) + "." + - std::to_string(__GNUC_MINOR__) + "." + - std::to_string(__GNUC_PATCHLEVEL__)}}; -#elif defined(__HP_cc) || defined(__HP_aCC) - result["compiler"] = "hp" -#elif defined(__IBMCPP__) - result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; -#elif defined(_MSC_VER) - result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; -#elif defined(__PGI) - result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; -#elif defined(__SUNPRO_CC) - result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; -#else - result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; -#endif - -#ifdef __cplusplus - result["compiler"]["c++"] = std::to_string(__cplusplus); -#else - result["compiler"]["c++"] = "unknown"; -#endif - return result; - } +// alias templates to reduce boilerplate +template <bool B, typename T = void> +using enable_if_t = typename std::enable_if<B, T>::type; - /////////////////////////// - // JSON value data types // - /////////////////////////// +template <typename T> +using uncvref_t = + typename std::remove_cv<typename std::remove_reference<T>::type>::type; - /// @name JSON value data types - /// The data types to store a JSON value. These types are derived from - /// the template arguments passed to class @ref basic_json. - /// @{ +/* +Implementation of two C++17 constructs: conjunction, negation. This is needed +to avoid evaluating all the traits in a condition - /*! - @brief a type for an object +For example: not std::is_same<void, T>::value and has_value_type<T>::value +will not compile when T = void (on MSVC at least). Whereas +conjunction<negation<std::is_same<void, T>>, has_value_type<T>>::value will +stop evaluating if negation<...>::value == false - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: - > An object is an unordered collection of zero or more name/value pairs, - > where a name is a string and a value is a string, number, boolean, null, - > object, or array. +Please note that those constructs must be used with caution, since symbols can +become very long quickly (which can slow down compilation and cause MSVC +internal compiler errors). Only use it when you have to (see example ahead). +*/ +template <class...> +struct conjunction : std::true_type +{ +}; +template <class B1> +struct conjunction<B1> : B1 +{ +}; +template <class B1, class... Bn> +struct conjunction<B1, Bn...> + : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type +{ +}; - To store objects in C++, a type is defined by the template parameters - described below. +template <class B> +struct negation : std::integral_constant<bool, !B::value> +{ +}; - @tparam ObjectType the container to store objects (e.g., `std::map` or - `std::unordered_map`) - @tparam StringType the type of the keys or names (e.g., `std::string`). - The comparison function `std::less<StringType>` is used to order elements - inside the container. - @tparam AllocatorType the allocator to use for objects (e.g., - `std::allocator`) +// dispatch utility (taken from ranges-v3) +template <unsigned N> +struct priority_tag : priority_tag<N - 1> +{ +}; +template <> +struct priority_tag<0> +{ +}; - #### Default type +////////////////// +// constructors // +////////////////// - With the default values for @a ObjectType (`std::map`), @a StringType - (`std::string`), and @a AllocatorType (`std::allocator`), the default - value for @a object_t is: +template <value_t> +struct external_constructor; - @code {.cpp} - std::map< - std::string, // key_type - basic_json, // value_type - std::less<std::string>, // key_compare - std::allocator<std::pair<const std::string, basic_json>> // allocator_type - > - @endcode +template <> +struct external_constructor<value_t::boolean> +{ + template <typename BasicJsonType> + static void construct(BasicJsonType &j, + typename BasicJsonType::boolean_t b) noexcept + { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } +}; - #### Behavior +template <> +struct external_constructor<value_t::string> +{ + template <typename BasicJsonType> + static void construct(BasicJsonType &j, + const typename BasicJsonType::string_t &s) + { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } +}; - The choice of @a object_t influences the behavior of the JSON class. With - the default type, objects have the following behavior: +template <> +struct external_constructor<value_t::number_float> +{ + template <typename BasicJsonType> + static void construct(BasicJsonType &j, + typename BasicJsonType::number_float_t val) noexcept + { + j.m_type = value_t::number_float; + j.m_value = val; + j.assert_invariant(); + } +}; - - When all names are unique, objects will be interoperable in the sense - that all software implementations receiving that object will agree on - the name-value mappings. - - When the names within an object are not unique, later stored name/value - pairs overwrite previously stored name/value pairs, leaving the used - names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will - be treated as equal and both stored as `{"key": 1}`. - - Internally, name/value pairs are stored in lexicographical order of the - names. Objects will also be serialized (see @ref dump) in this order. - For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored - and serialized as `{"a": 2, "b": 1}`. - - When comparing objects, the order of the name/value pairs is irrelevant. - This makes objects interoperable in the sense that they will not be - affected by these differences. For instance, `{"b": 1, "a": 2}` and - `{"a": 2, "b": 1}` will be treated as equal. +template <> +struct external_constructor<value_t::number_unsigned> +{ + template <typename BasicJsonType> + static void + construct(BasicJsonType &j, + typename BasicJsonType::number_unsigned_t val) noexcept + { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } +}; - #### Limits +template <> +struct external_constructor<value_t::number_integer> +{ + template <typename BasicJsonType> + static void construct(BasicJsonType &j, + typename BasicJsonType::number_integer_t val) noexcept + { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } +}; - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the maximum depth of nesting. +template <> +struct external_constructor<value_t::array> +{ + template <typename BasicJsonType> + static void construct(BasicJsonType &j, + const typename BasicJsonType::array_t &arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } - In this class, the object's limit of nesting is not constraint explicitly. - However, a maximum depth of nesting may be introduced by the compiler or - runtime environment. A theoretical limit can be queried by calling the - @ref max_size function of a JSON object. + template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t<not std::is_same<CompatibleArrayType, + typename BasicJsonType::array_t>::value, + int> = 0> + static void construct(BasicJsonType &j, const CompatibleArrayType &arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = j.template create<typename BasicJsonType::array_t>( + begin(arr), end(arr)); + j.assert_invariant(); + } - #### Storage + template <typename BasicJsonType> + static void construct(BasicJsonType &j, const std::vector<bool> &arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->reserve(arr.size()); + for (bool x : arr) + { + j.m_value.array->push_back(x); + } + j.assert_invariant(); + } +}; - Objects are stored as pointers in a @ref basic_json type. That is, for any - access to object values, a pointer of type `object_t*` must be - dereferenced. +template <> +struct external_constructor<value_t::object> +{ + template <typename BasicJsonType> + static void construct(BasicJsonType &j, + const typename BasicJsonType::object_t &obj) + { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } - @sa @ref array_t -- type for an array value + template < + typename BasicJsonType, typename CompatibleObjectType, + enable_if_t<not std::is_same<CompatibleObjectType, + typename BasicJsonType::object_t>::value, + int> = 0> + static void construct(BasicJsonType &j, const CompatibleObjectType &obj) + { + using std::begin; + using std::end; - @since version 1.0.0 + j.m_type = value_t::object; + j.m_value.object = j.template create<typename BasicJsonType::object_t>( + begin(obj), end(obj)); + j.assert_invariant(); + } +}; - @note The order name/value pairs are added to the object is *not* - preserved by the library. Therefore, iterating an object may return - name/value pairs in a different order than they were originally stored. In - fact, keys will be traversed in alphabetical order as `std::map` with - `std::less` is used by default. Please note this behavior conforms to [RFC - 7159](http://rfc7159.net/rfc7159), because any order implements the - specified "unordered" nature of JSON objects. - */ - using object_t = - ObjectType<StringType, basic_json, std::less<StringType>, - AllocatorType<std::pair<const StringType, basic_json>>>; +//////////////////////// +// has_/is_ functions // +//////////////////////// - /*! - @brief a type for an array +/*! +@brief Helper to determine whether there's a key_type for T. - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: - > An array is an ordered sequence of zero or more values. +This helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. - To store objects in C++, a type is defined by the template parameters - explained below. +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0, overworked in version 2.0.6 +*/ +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template <typename T> \ + struct has_##type \ + { \ + private: \ + template <typename U, typename = typename U::type> \ + static int detect(U &&); \ + static void detect(...); \ + \ + public: \ + static constexpr bool value = \ + std::is_integral<decltype(detect(std::declval<T>()))>::value; \ + } + +NLOHMANN_JSON_HAS_HELPER(mapped_type); +NLOHMANN_JSON_HAS_HELPER(key_type); +NLOHMANN_JSON_HAS_HELPER(value_type); +NLOHMANN_JSON_HAS_HELPER(iterator); + +#undef NLOHMANN_JSON_HAS_HELPER + +template <bool B, class RealType, class CompatibleObjectType> +struct is_compatible_object_type_impl : std::false_type +{ +}; - @tparam ArrayType container type to store arrays (e.g., `std::vector` or - `std::list`) - @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) +template <class RealType, class CompatibleObjectType> +struct is_compatible_object_type_impl<true, RealType, CompatibleObjectType> +{ + static constexpr auto value = + std::is_constructible< + typename RealType::key_type, + typename CompatibleObjectType::key_type>::value and + std::is_constructible< + typename RealType::mapped_type, + typename CompatibleObjectType::mapped_type>::value; +}; - #### Default type +template <class BasicJsonType, class CompatibleObjectType> +struct is_compatible_object_type +{ + static auto constexpr value = is_compatible_object_type_impl< + conjunction<negation<std::is_same<void, CompatibleObjectType>>, + has_mapped_type<CompatibleObjectType>, + has_key_type<CompatibleObjectType>>::value, + typename BasicJsonType::object_t, CompatibleObjectType>::value; +}; - With the default values for @a ArrayType (`std::vector`) and @a - AllocatorType (`std::allocator`), the default value for @a array_t is: +template <typename BasicJsonType, typename T> +struct is_basic_json_nested_type +{ + static auto constexpr value = + std::is_same<T, typename BasicJsonType::iterator>::value or + std::is_same<T, typename BasicJsonType::const_iterator>::value or + std::is_same<T, typename BasicJsonType::reverse_iterator>::value or + std::is_same<T, + typename BasicJsonType::const_reverse_iterator>::value or + std::is_same<T, typename BasicJsonType::json_pointer>::value; +}; - @code {.cpp} - std::vector< - basic_json, // value_type - std::allocator<basic_json> // allocator_type - > - @endcode +template <class BasicJsonType, class CompatibleArrayType> +struct is_compatible_array_type +{ + static auto constexpr value = conjunction< + negation<std::is_same<void, CompatibleArrayType>>, + negation<is_compatible_object_type<BasicJsonType, CompatibleArrayType>>, + negation<std::is_constructible<typename BasicJsonType::string_t, + CompatibleArrayType>>, + negation<is_basic_json_nested_type<BasicJsonType, CompatibleArrayType>>, + has_value_type<CompatibleArrayType>, + has_iterator<CompatibleArrayType>>::value; +}; - #### Limits +template <bool, typename, typename> +struct is_compatible_integer_type_impl : std::false_type +{ +}; - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the maximum depth of nesting. +template <typename RealIntegerType, typename CompatibleNumberIntegerType> +struct is_compatible_integer_type_impl<true, RealIntegerType, + CompatibleNumberIntegerType> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits<RealIntegerType>; + using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>; + + static constexpr auto value = + std::is_constructible<RealIntegerType, + CompatibleNumberIntegerType>::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; - In this class, the array's limit of nesting is not constraint explicitly. - However, a maximum depth of nesting may be introduced by the compiler or - runtime environment. A theoretical limit can be queried by calling the - @ref max_size function of a JSON array. +template <typename RealIntegerType, typename CompatibleNumberIntegerType> +struct is_compatible_integer_type +{ + static constexpr auto + value = is_compatible_integer_type_impl < + std::is_integral<CompatibleNumberIntegerType>::value and + not std::is_same<bool, CompatibleNumberIntegerType>::value, + RealIntegerType, CompatibleNumberIntegerType > ::value; +}; - #### Storage +// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists +template <typename BasicJsonType, typename T> +struct has_from_json +{ +private: + // also check the return type of from_json + template <typename U, typename = enable_if_t<std::is_same< + void, decltype(uncvref_t<U>::from_json( + std::declval<BasicJsonType>(), + std::declval<T &>()))>::value>> + static int detect(U &&); + static void detect(...); - Arrays are stored as pointers in a @ref basic_json type. That is, for any - access to array values, a pointer of type `array_t*` must be dereferenced. +public: + static constexpr bool value = std::is_integral<decltype( + detect(std::declval<typename BasicJsonType::template json_serializer< + T, void>>()))>::value; +}; - @sa @ref object_t -- type for an object value +// This trait checks if JSONSerializer<T>::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template <typename BasicJsonType, typename T> +struct has_non_default_from_json +{ +private: + template <typename U, typename = enable_if_t<std::is_same< + T, decltype(uncvref_t<U>::from_json( + std::declval<BasicJsonType>()))>::value>> + static int detect(U &&); + static void detect(...); - @since version 1.0.0 - */ - using array_t = ArrayType<basic_json, AllocatorType<basic_json>>; +public: + static constexpr bool value = std::is_integral<decltype( + detect(std::declval<typename BasicJsonType::template json_serializer< + T, void>>()))>::value; +}; - /*! - @brief a type for a string +// This trait checks if BasicJsonType::json_serializer<T>::to_json exists +template <typename BasicJsonType, typename T> +struct has_to_json +{ +private: + template <typename U, + typename = decltype(uncvref_t<U>::to_json( + std::declval<BasicJsonType &>(), std::declval<T>()))> + static int detect(U &&); + static void detect(...); - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: - > A string is a sequence of zero or more Unicode characters. +public: + static constexpr bool value = std::is_integral<decltype( + detect(std::declval<typename BasicJsonType::template json_serializer< + T, void>>()))>::value; +}; - To store objects in C++, a type is defined by the template parameter - described below. Unicode values are split by the JSON class into - byte-sized characters during deserialization. +///////////// +// to_json // +///////////// - @tparam StringType the container to store strings (e.g., `std::string`). - Note this container is used for keys/names in objects, see @ref object_t. +template <typename BasicJsonType, typename T, + enable_if_t<std::is_same<T, typename BasicJsonType::boolean_t>::value, + int> = 0> +void to_json(BasicJsonType &j, T b) noexcept +{ + external_constructor<value_t::boolean>::construct(j, b); +} - #### Default type +template <typename BasicJsonType, typename CompatibleString, + enable_if_t<std::is_constructible<typename BasicJsonType::string_t, + CompatibleString>::value, + int> = 0> +void to_json(BasicJsonType &j, const CompatibleString &s) +{ + external_constructor<value_t::string>::construct(j, s); +} - With the default values for @a StringType (`std::string`), the default - value for @a string_t is: +template <typename BasicJsonType, typename FloatType, + enable_if_t<std::is_floating_point<FloatType>::value, int> = 0> +void to_json(BasicJsonType &j, FloatType val) noexcept +{ + external_constructor<value_t::number_float>::construct( + j, static_cast<typename BasicJsonType::number_float_t>(val)); +} - @code {.cpp} - std::string - @endcode +template <typename BasicJsonType, typename CompatibleNumberUnsignedType, + enable_if_t<is_compatible_integer_type< + typename BasicJsonType::number_unsigned_t, + CompatibleNumberUnsignedType>::value, + int> = 0> +void to_json(BasicJsonType &j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor<value_t::number_unsigned>::construct( + j, static_cast<typename BasicJsonType::number_unsigned_t>(val)); +} - #### Encoding +template <typename BasicJsonType, typename CompatibleNumberIntegerType, + enable_if_t<is_compatible_integer_type< + typename BasicJsonType::number_integer_t, + CompatibleNumberIntegerType>::value, + int> = 0> +void to_json(BasicJsonType &j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor<value_t::number_integer>::construct( + j, static_cast<typename BasicJsonType::number_integer_t>(val)); +} - Strings are stored in UTF-8 encoding. Therefore, functions like - `std::string::size()` or `std::string::length()` return the number of - bytes in the string rather than the number of characters or glyphs. +template <typename BasicJsonType, typename EnumType, + enable_if_t<std::is_enum<EnumType>::value, int> = 0> +void to_json(BasicJsonType &j, EnumType e) noexcept +{ + using underlying_type = typename std::underlying_type<EnumType>::type; + external_constructor<value_t::number_integer>::construct( + j, static_cast<underlying_type>(e)); +} - #### String comparison +template <typename BasicJsonType> +void to_json(BasicJsonType &j, const std::vector<bool> &e) +{ + external_constructor<value_t::array>::construct(j, e); +} - [RFC 7159](http://rfc7159.net/rfc7159) states: - > Software implementations are typically required to test names of object - > members for equality. Implementations that transform the textual - > representation into sequences of Unicode code units and then perform the - > comparison numerically, code unit by code unit, are interoperable in the - > sense that implementations will agree in all cases on equality or - > inequality of two strings. For example, implementations that compare - > strings with escaped characters unconverted may incorrectly find that - > `"a\\b"` and `"a\u005Cb"` are not equal. +template <typename BasicJsonType, typename CompatibleArrayType, + enable_if_t<is_compatible_array_type<BasicJsonType, + CompatibleArrayType>::value or + std::is_same<typename BasicJsonType::array_t, + CompatibleArrayType>::value, + int> = 0> +void to_json(BasicJsonType &j, const CompatibleArrayType &arr) +{ + external_constructor<value_t::array>::construct(j, arr); +} - This implementation is interoperable as it does compare strings code unit - by code unit. +template <typename BasicJsonType, typename CompatibleObjectType, + enable_if_t<is_compatible_object_type<BasicJsonType, + CompatibleObjectType>::value, + int> = 0> +void to_json(BasicJsonType &j, const CompatibleObjectType &arr) +{ + external_constructor<value_t::object>::construct(j, arr); +} - #### Storage +template <typename BasicJsonType, typename T, std::size_t N, + enable_if_t<not std::is_constructible< + typename BasicJsonType::string_t, T (&)[N]>::value, + int> = 0> +void to_json(BasicJsonType &j, T (&arr)[N]) +{ + external_constructor<value_t::array>::construct(j, arr); +} - String values are stored as pointers in a @ref basic_json type. That is, - for any access to string values, a pointer of type `string_t*` must be - dereferenced. +/////////////// +// from_json // +/////////////// + +// overloads for basic_json template parameters +template < + typename BasicJsonType, typename ArithmeticType, + enable_if_t<std::is_arithmetic<ArithmeticType>::value and + not std::is_same<ArithmeticType, + typename BasicJsonType::boolean_t>::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType &j, ArithmeticType &val) +{ + switch (static_cast<value_t>(j)) + { + case value_t::number_unsigned: + { + val = static_cast<ArithmeticType>( + *j.template get_ptr< + const typename BasicJsonType::number_unsigned_t *>()); + break; + } + case value_t::number_integer: + { + val = static_cast<ArithmeticType>( + *j.template get_ptr< + const typename BasicJsonType::number_integer_t *>()); + break; + } + case value_t::number_float: + { + val = static_cast<ArithmeticType>( + *j.template get_ptr< + const typename BasicJsonType::number_float_t *>()); + break; + } + default: + { + JSON_THROW(type_error::create(302, "type must be number, but is " + + j.type_name())); + } + } +} - @since version 1.0.0 - */ - using string_t = StringType; +template <typename BasicJsonType> +void from_json(const BasicJsonType &j, typename BasicJsonType::boolean_t &b) +{ + if (not j.is_boolean()) + { + JSON_THROW(type_error::create(302, "type must be boolean, but is " + + j.type_name())); + } + b = *j.template get_ptr<const typename BasicJsonType::boolean_t *>(); +} - /*! - @brief a type for a boolean +template <typename BasicJsonType> +void from_json(const BasicJsonType &j, typename BasicJsonType::string_t &s) +{ + if (not j.is_string()) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + + j.type_name())); + } + s = *j.template get_ptr<const typename BasicJsonType::string_t *>(); +} - [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a - type which differentiates the two literals `true` and `false`. +template <typename BasicJsonType> +void from_json(const BasicJsonType &j, + typename BasicJsonType::number_float_t &val) +{ + get_arithmetic_value(j, val); +} - To store objects in C++, a type is defined by the template parameter @a - BooleanType which chooses the type to use. +template <typename BasicJsonType> +void from_json(const BasicJsonType &j, + typename BasicJsonType::number_unsigned_t &val) +{ + get_arithmetic_value(j, val); +} - #### Default type +template <typename BasicJsonType> +void from_json(const BasicJsonType &j, + typename BasicJsonType::number_integer_t &val) +{ + get_arithmetic_value(j, val); +} - With the default values for @a BooleanType (`bool`), the default value for - @a boolean_t is: +template <typename BasicJsonType, typename EnumType, + enable_if_t<std::is_enum<EnumType>::value, int> = 0> +void from_json(const BasicJsonType &j, EnumType &e) +{ + typename std::underlying_type<EnumType>::type val; + get_arithmetic_value(j, val); + e = static_cast<EnumType>(val); +} - @code {.cpp} - bool - @endcode +template <typename BasicJsonType> +void from_json(const BasicJsonType &j, typename BasicJsonType::array_t &arr) +{ + if (not j.is_array()) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + + j.type_name())); + } + arr = *j.template get_ptr<const typename BasicJsonType::array_t *>(); +} - #### Storage +// forward_list doesn't have an insert method +template <typename BasicJsonType, typename T, typename Allocator, + enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> +void from_json(const BasicJsonType &j, std::forward_list<T, Allocator> &l) +{ + if (not j.is_array()) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + + j.type_name())); + } - Boolean values are stored directly inside a @ref basic_json type. + for (auto it = j.rbegin(), end = j.rend(); it != end; ++it) + { + l.push_front(it->template get<T>()); + } +} - @since version 1.0.0 - */ - using boolean_t = BooleanType; +template <typename BasicJsonType, typename CompatibleArrayType> +void from_json_array_impl(const BasicJsonType &j, CompatibleArrayType &arr, + priority_tag<0>) +{ + using std::begin; + using std::end; + + std::transform( + j.begin(), j.end(), std::inserter(arr, end(arr)), + [](const BasicJsonType &i) { + // get<BasicJsonType>() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get<typename CompatibleArrayType::value_type>(); + }); +} - /*! - @brief a type for a number (integer) +template <typename BasicJsonType, typename CompatibleArrayType> +auto from_json_array_impl(const BasicJsonType &j, CompatibleArrayType &arr, + priority_tag<1>) + -> decltype( + arr.reserve(std::declval<typename CompatibleArrayType::size_type>()), + void()) +{ + using std::begin; + using std::end; + + arr.reserve(j.size()); + std::transform( + j.begin(), j.end(), std::inserter(arr, end(arr)), + [](const BasicJsonType &i) { + // get<BasicJsonType>() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get<typename CompatibleArrayType::value_type>(); + }); +} - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t< + is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value and + std::is_convertible<BasicJsonType, typename CompatibleArrayType:: + value_type>::value and + not std::is_same<typename BasicJsonType::array_t, + CompatibleArrayType>::value, + int> = 0> +void from_json(const BasicJsonType &j, CompatibleArrayType &arr) +{ + if (not j.is_array()) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + + j.type_name())); + } - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. + from_json_array_impl(j, arr, priority_tag<1>{}); +} - To store integer numbers in C++, a type is defined by the template - parameter @a NumberIntegerType which chooses the type to use. +template <typename BasicJsonType, typename CompatibleObjectType, + enable_if_t<is_compatible_object_type<BasicJsonType, + CompatibleObjectType>::value, + int> = 0> +void from_json(const BasicJsonType &j, CompatibleObjectType &obj) +{ + if (not j.is_object()) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + + j.type_name())); + } - #### Default type + auto inner_object = + j.template get_ptr<const typename BasicJsonType::object_t *>(); + using std::begin; + using std::end; + // we could avoid the assignment, but this might require a for loop, which + // might be less efficient than the container constructor for some + // containers (would it?) + obj = CompatibleObjectType(begin(*inner_object), end(*inner_object)); +} - With the default values for @a NumberIntegerType (`int64_t`), the default - value for @a number_integer_t is: +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template < + typename BasicJsonType, typename ArithmeticType, + enable_if_t< + std::is_arithmetic<ArithmeticType>::value and + not std::is_same<ArithmeticType, typename BasicJsonType:: + number_unsigned_t>::value and + not std::is_same<ArithmeticType, typename BasicJsonType:: + number_integer_t>::value and + not std::is_same<ArithmeticType, + typename BasicJsonType::number_float_t>::value and + not std::is_same<ArithmeticType, + typename BasicJsonType::boolean_t>::value, + int> = 0> +void from_json(const BasicJsonType &j, ArithmeticType &val) +{ + switch (static_cast<value_t>(j)) + { + case value_t::number_unsigned: + { + val = static_cast<ArithmeticType>( + *j.template get_ptr< + const typename BasicJsonType::number_unsigned_t *>()); + break; + } + case value_t::number_integer: + { + val = static_cast<ArithmeticType>( + *j.template get_ptr< + const typename BasicJsonType::number_integer_t *>()); + break; + } + case value_t::number_float: + { + val = static_cast<ArithmeticType>( + *j.template get_ptr< + const typename BasicJsonType::number_float_t *>()); + break; + } + case value_t::boolean: + { + val = static_cast<ArithmeticType>( + *j.template get_ptr<const typename BasicJsonType::boolean_t *>()); + break; + } + default: + { + JSON_THROW(type_error::create(302, "type must be number, but is " + + j.type_name())); + } + } +} - @code {.cpp} - int64_t - @endcode +struct to_json_fn +{ +private: + template <typename BasicJsonType, typename T> + auto call(BasicJsonType &j, T &&val, priority_tag<1>) const + noexcept(noexcept(to_json(j, std::forward<T>(val)))) + -> decltype(to_json(j, std::forward<T>(val)), void()) + { + return to_json(j, std::forward<T>(val)); + } - #### Default behavior + template <typename BasicJsonType, typename T> + void call(BasicJsonType &, T &&, priority_tag<0>) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find to_json() method in T's namespace"); + } - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in integer literals lead to an interpretation as octal - number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. - During deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. +public: + template <typename BasicJsonType, typename T> + void operator()(BasicJsonType &j, T &&val) const + noexcept(noexcept(std::declval<to_json_fn>().call(j, + std::forward<T>(val), + priority_tag<1>{}))) + { + return call(j, std::forward<T>(val), priority_tag<1>{}); + } +}; - #### Limits +struct from_json_fn +{ +private: + template <typename BasicJsonType, typename T> + auto call(const BasicJsonType &j, T &val, priority_tag<1>) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the range and precision of numbers. + template <typename BasicJsonType, typename T> + void call(const BasicJsonType &, T &, priority_tag<0>) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find from_json() method in T's namespace"); + } - When the default type is used, the maximal integer number that can be - stored is `9223372036854775807` (INT64_MAX) and the minimal integer number - that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers - that are out of range will yield over/underflow when used in a - constructor. During deserialization, too large or small integer numbers - will be automatically be stored as @ref number_unsigned_t or @ref - number_float_t. +public: + template <typename BasicJsonType, typename T> + void operator()(const BasicJsonType &j, T &val) const + noexcept(noexcept(std::declval<from_json_fn>().call(j, val, + priority_tag<1>{}))) + { + return call(j, val, priority_tag<1>{}); + } +}; - [RFC 7159](http://rfc7159.net/rfc7159) further states: - > Note that when such software is used, numbers that are integers and are - > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense - > that implementations will agree exactly on their numeric values. +// taken from ranges-v3 +template <typename T> +struct static_const +{ + static constexpr T value{}; +}; - As this range is a subrange of the exactly supported range [INT64_MIN, - INT64_MAX], this class's integer type is interoperable. +template <typename T> +constexpr T static_const<T>::value; +} // namespace detail - #### Storage +/// namespace to hold default `to_json` / `from_json` functions +namespace +{ +constexpr const auto &to_json = detail::static_const<detail::to_json_fn>::value; +constexpr const auto &from_json = + detail::static_const<detail::from_json_fn>::value; +} - Integer number values are stored directly inside a @ref basic_json type. +/*! +@brief default JSONSerializer template argument - @sa @ref number_float_t -- type for number values (floating-point) +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template <typename = void, typename = void> +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type - @sa @ref number_unsigned_t -- type for number values (unsigned integer) + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). - @since version 1.0.0 + @param[in] j JSON value to read from + @param[in,out] val value to write to */ - using number_integer_t = NumberIntegerType; + template <typename BasicJsonType, typename ValueType> + static void from_json(BasicJsonType &&j, ValueType &val) noexcept( + noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val))) + { + ::nlohmann::from_json(std::forward<BasicJsonType>(j), val); + } /*! - @brief a type for a number (unsigned) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. + @brief convert any value type to a JSON value - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - To store unsigned integer numbers in C++, a type is defined by the - template parameter @a NumberUnsignedType which chooses the type to use. - - #### Default type - - With the default values for @a NumberUnsignedType (`uint64_t`), the - default value for @a number_unsigned_t is: - - @code {.cpp} - uint64_t - @endcode + This function is usually called by the constructors of the @ref basic_json + class. - #### Default behavior + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template <typename BasicJsonType, typename ValueType> + static void to_json(BasicJsonType &j, ValueType &&val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward<ValueType>(val)))) + { + ::nlohmann::to_json(j, std::forward<ValueType>(val)); + } +}; - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in integer literals lead to an interpretation as octal - number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. - During deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. +/*! +@brief a class to store JSON values - #### Limits +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) +@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` +and `from_json()` (@ref adl_serializer by default) - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the range and precision of numbers. - - When the default type is used, the maximal integer number that can be - stored is `18446744073709551615` (UINT64_MAX) and the minimal integer - number that can be stored is `0`. Integer numbers that are out of range - will yield over/underflow when used in a constructor. During - deserialization, too large or small integer numbers will be automatically - be stored as @ref number_integer_t or @ref number_float_t. - - [RFC 7159](http://rfc7159.net/rfc7159) further states: - > Note that when such software is used, numbers that are integers and are - > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense - > that implementations will agree exactly on their numeric values. - - As this range is a subrange (when considered in conjunction with the - number_integer_t type) of the exactly supported range [0, UINT64_MAX], - this class's integer type is interoperable. - - #### Storage - - Integer number values are stored directly inside a @ref basic_json type. - - @sa @ref number_float_t -- type for number values (floating-point) - @sa @ref number_integer_t -- type for number values (integer) +@requirement The class satisfies the following concept requirements: +- Basic + - +[DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null + value. + - +[MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - +[CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): + JSON values can be destructed. +- Layout + - +[StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): + JSON values have + [standard +layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the + class has no virtual functions or (virtual) base classes. +- Library-wide + - +[EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - +[LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](http://en.cppreference.com/w/cpp/concept/Container): + JSON values can be used like STL containers and provide iterator access. + - +[ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. - @since version 2.0.0 - */ - using number_unsigned_t = NumberUnsignedType; +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). - /*! - @brief a type for a number (floating-point) +@internal +@note ObjectType trick from http://stackoverflow.com/a/9860911 +@endinternal - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. +@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange +Format](http://rfc7159.net/rfc7159) - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. +@since version 1.0.0 - To store floating-point numbers in C++, a type is defined by the template - parameter @a NumberFloatType which chooses the type to use. +@nosubgrouping +*/ +template <template <typename U, typename V, typename... Args> class ObjectType = + std::map, + template <typename U, typename... Args> class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template <typename U> class AllocatorType = std::allocator, + template <typename T, typename SFINAE = void> class JSONSerializer = + adl_serializer> +class basic_json +{ +private: + template <detail::value_t> + friend struct detail::external_constructor; + /// workaround type for MSVC + using basic_json_t = + basic_json<ObjectType, ArrayType, StringType, BooleanType, + NumberIntegerType, NumberUnsignedType, NumberFloatType, + AllocatorType, JSONSerializer>; - #### Default type +public: + using value_t = detail::value_t; + // forward declarations + template <typename U> + class iter_impl; + template <typename Base> + class json_reverse_iterator; + class json_pointer; + template <typename T, typename SFINAE> + using json_serializer = JSONSerializer<T, SFINAE>; - With the default values for @a NumberFloatType (`double`), the default - value for @a number_float_t is: + //////////////// + // exceptions // + //////////////// - @code {.cpp} - double - @endcode + /// @name exceptions + /// Classes to implement user-defined exceptions. + /// @{ - #### Default behavior + /// @copydoc detail::exception + using exception = detail::exception; + /// @copydoc detail::parse_error + using parse_error = detail::parse_error; + /// @copydoc detail::invalid_iterator + using invalid_iterator = detail::invalid_iterator; + /// @copydoc detail::type_error + using type_error = detail::type_error; + /// @copydoc detail::out_of_range + using out_of_range = detail::out_of_range; + /// @copydoc detail::other_error + using other_error = detail::other_error; - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in floating-point literals will be ignored. Internally, - the value will be stored as decimal number. For instance, the C++ - floating-point literal `01.2` will be serialized to `1.2`. During - deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. + /// @} - #### Limits + ///////////////////// + // container types // + ///////////////////// - [RFC 7159](http://rfc7159.net/rfc7159) states: - > This specification allows implementations to set limits on the range and - > precision of numbers accepted. Since software that implements IEEE - > 754-2008 binary64 (double precision) numbers is generally available and - > widely used, good interoperability can be achieved by implementations - > that expect no more precision or range than these provide, in the sense - > that implementations will approximate JSON numbers within the expected - > precision. + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ - This implementation does exactly follow this approach, as it uses double - precision floating-point numbers. Note values smaller than - `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` - will be stored as NaN internally and be serialized to `null`. + /// the type of elements in a basic_json container + using value_type = basic_json; - #### Storage + /// the type of an element reference + using reference = value_type &; + /// the type of an element const reference + using const_reference = const value_type &; - Floating-point number values are stored directly inside a @ref basic_json - type. + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; - @sa @ref number_integer_t -- type for number values (integer) + /// the allocator type + using allocator_type = AllocatorType<basic_json>; - @sa @ref number_unsigned_t -- type for number values (unsigned integer) + /// the type of an element pointer + using pointer = typename std::allocator_traits<allocator_type>::pointer; + /// the type of an element const pointer + using const_pointer = + typename std::allocator_traits<allocator_type>::const_pointer; - @since version 1.0.0 - */ - using number_float_t = NumberFloatType; + /// an iterator for a basic_json container + using iterator = iter_impl<basic_json>; + /// a const iterator for a basic_json container + using const_iterator = iter_impl<const basic_json>; + /// a reverse iterator for a basic_json container + using reverse_iterator = + json_reverse_iterator<typename basic_json::iterator>; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = + json_reverse_iterator<typename basic_json::const_iterator>; /// @} - /////////////////////////// - // JSON type enumeration // - /////////////////////////// - /*! - @brief the JSON type enumeration + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() { return allocator_type(); } - This enumeration collects the different JSON types. It is internally used - to distinguish the stored values, and the functions @ref is_null(), @ref - is_object(), @ref is_array(), @ref is_string(), @ref is_boolean(), @ref - is_number() (with @ref is_number_integer(), @ref is_number_unsigned(), and - @ref is_number_float()), @ref is_discarded(), @ref is_primitive(), and - @ref is_structured() rely on it. + /*! + @brief returns version information on the library - @note There are three enumeration entries (number_integer, - number_unsigned, and number_float), because the library distinguishes - these three types for numbers: @ref number_unsigned_t is used for unsigned - integers, @ref number_integer_t is used for signed integers, and @ref - number_float_t is used for floating-point numbers or to approximate - integers which do not fit in the limits of their respective type. + This function returns a JSON object with information about the library, + including the version number and information on the platform and compiler. + + @return JSON object holding version information + key | description + ----------- | --------------- + `compiler` | Information on the used compiler. It is an object with the + following keys: `c++` (the used C++ standard), `family` (the compiler + family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, + `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). + `copyright` | The copyright line for the library as string. + `name` | The name of the library as string. + `platform` | The used platform as string. Possible values are `win32`, + `linux`, `apple`, `unix`, and `unknown`. + `url` | The URL of the project as string. + `version` | The version of the library. It is an object with the following + keys: `major`, `minor`, and `patch` as defined by [Semantic + Versioning](http://semver.org), and `string` (the version string). + + @liveexample{The following code shows an example output of the `meta()` + function.,meta} - @sa @ref basic_json(const value_t value_type) -- create a JSON value with - the default value for a given type + @complexity Constant. - @since version 1.0.0 + @since 2.1.0 */ - enum class value_t : uint8_t - { - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - discarded ///< discarded by the the parser callback function - }; - -private: - /// helper for exception-safe object creation - template <typename T, typename... Args> - static T *create(Args &&... args) + static basic_json meta() { - AllocatorType<T> alloc; - auto deleter = [&](T *object) { alloc.deallocate(object, 1); }; - std::unique_ptr<T, decltype(deleter)> object(alloc.allocate(1), - deleter); - alloc.construct(object.get(), std::forward<Args>(args)...); - assert(object != nullptr); - return object.release(); - } - - //////////////////////// - // JSON value storage // - //////////////////////// - - /*! - @brief a JSON value + basic_json result; - The actual storage for a JSON value of the @ref basic_json class. This - union combines the different storage types for the JSON value types - defined in @ref value_t. + result["copyright"] = "(C) 2013-2017 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["url"] = "https://github.com/nlohmann/json"; + result["version"] = { + {"string", "2.1.1"}, {"major", 2}, {"minor", 1}, {"patch", 1}}; - JSON type | value_t type | used type - --------- | --------------- | ------------------------ - object | object | pointer to @ref object_t - array | array | pointer to @ref array_t - string | string | pointer to @ref string_t - boolean | boolean | @ref boolean_t - number | number_integer | @ref number_integer_t - number | number_unsigned | @ref number_unsigned_t - number | number_float | @ref number_float_t - null | null | *no value is stored* +#ifdef _WIN32 + result["platform"] = "win32"; +#elif defined __linux__ + result["platform"] = "linux"; +#elif defined __APPLE__ + result["platform"] = "apple"; +#elif defined __unix__ + result["platform"] = "unix"; +#else + result["platform"] = "unknown"; +#endif - @note Variable-length types (objects, arrays, and strings) are stored as - pointers. The size of the union should not exceed 64 bits if the default - value types are used. +#if defined(__clang__) + result["compiler"] = {{"family", "clang"}, + {"version", __clang_version__}}; +#elif defined(__ICC) || defined(__INTEL_COMPILER) + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__GNUC__) || defined(__GNUG__) + result["compiler"] = { + {"family", "gcc"}, + {"version", std::to_string(__GNUC__) + "." + + std::to_string(__GNUC_MINOR__) + "." + + std::to_string(__GNUC_PATCHLEVEL__)}}; +#elif defined(__HP_cc) || defined(__HP_aCC) + result["compiler"] = "hp" +#elif defined(__IBMCPP__) + result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; +#elif defined(_MSC_VER) + result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; +#elif defined(__PGI) + result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; +#elif defined(__SUNPRO_CC) + result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; +#else + result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; +#endif - @since version 1.0.0 - */ - union json_value { - /// object (stored with pointer to save storage) - object_t *object; - /// array (stored with pointer to save storage) - array_t *array; - /// string (stored with pointer to save storage) - string_t *string; - /// boolean - boolean_t boolean; - /// number (integer) - number_integer_t number_integer; - /// number (unsigned integer) - number_unsigned_t number_unsigned; - /// number (floating-point) - number_float_t number_float; +#ifdef __cplusplus + result["compiler"]["c++"] = std::to_string(__cplusplus); +#else + result["compiler"]["c++"] = "unknown"; +#endif + return result; + } - /// default constructor (for null values) - json_value() = default; - /// constructor for booleans - json_value(boolean_t v) noexcept : boolean(v) {} - /// constructor for numbers (integer) - json_value(number_integer_t v) noexcept : number_integer(v) {} - /// constructor for numbers (unsigned) - json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} - /// constructor for numbers (floating-point) - json_value(number_float_t v) noexcept : number_float(v) {} - /// constructor for empty values of a given type - json_value(value_t t) - { - switch (t) - { - case value_t::object: - { - object = create<object_t>(); - break; - } + /////////////////////////// + // JSON value data types // + /////////////////////////// - case value_t::array: - { - array = create<array_t>(); - break; - } + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ - case value_t::string: - { - string = create<string_t>(""); - break; - } + /*! + @brief a type for an object - case value_t::boolean: - { - boolean = boolean_t(false); - break; - } + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. - case value_t::number_integer: - { - number_integer = number_integer_t(0); - break; - } + To store objects in C++, a type is defined by the template parameters + described below. - case value_t::number_unsigned: - { - number_unsigned = number_unsigned_t(0); - break; - } + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less<StringType>` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) - case value_t::number_float: - { - number_float = number_float_t(0.0); - break; - } + #### Default type - case value_t::null: - { - break; - } + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: - default: - { - if (t == value_t::null) - { - JSON_THROW(std::domain_error( - "961c151d2e87f2686a955a9be24d316f1362b" - "f21 2.0.10")); // LCOV_EXCL_LINE - } - break; - } - } - } + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less<std::string>, // key_compare + std::allocator<std::pair<const std::string, basic_json>> // allocator_type + > + @endcode - /// constructor for strings - json_value(const string_t &value) { string = create<string_t>(value); } + #### Behavior - /// constructor for objects - json_value(const object_t &value) { object = create<object_t>(value); } + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: - /// constructor for arrays - json_value(const array_t &value) { array = create<array_t>(value); } - }; + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, later stored name/value + pairs overwrite previously stored name/value pairs, leaving the used + names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will + be treated as equal and both stored as `{"key": 1}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. - /*! - @brief checks the class invariants + #### Limits - This function asserts the class invariants. It needs to be called at the - end of every constructor to make sure that created objects respect the - invariant. Furthermore, it has to be called each time the type of a JSON - value is changed, because the invariant expresses a relationship between - @a m_type and @a m_value. - */ - void assert_invariant() const - { - assert(m_type != value_t::object or m_value.object != nullptr); - assert(m_type != value_t::array or m_value.array != nullptr); - assert(m_type != value_t::string or m_value.string != nullptr); - } + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. -public: - ////////////////////////// - // JSON parser callback // - ////////////////////////// + In this class, the object's limit of nesting is not constraint explicitly. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. - /*! - @brief JSON callback events + #### Storage - This enumeration lists the parser events that can trigger calling a - callback function of type @ref parser_callback_t during parsing. + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. - @image html callback_events.png "Example when certain parse events are - triggered" + @sa @ref array_t -- type for an array value @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. */ - enum class parse_event_t : uint8_t - { - /// the parser read `{` and started to process a JSON object - object_start, - /// the parser read `}` and finished processing a JSON object - object_end, - /// the parser read `[` and started to process a JSON array - array_start, - /// the parser read `]` and finished processing a JSON array - array_end, - /// the parser read a key of a value in an object - key, - /// the parser finished reading a JSON value - value - }; + using object_t = + ObjectType<StringType, basic_json, std::less<StringType>, + AllocatorType<std::pair<const StringType, basic_json>>>; + + /*! + @brief a type for an array - /*! - @brief per-element parser callback type + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. - With a parser callback function, the result of parsing a JSON text can be - influenced. When passed to @ref parse(std::istream&, const - parser_callback_t) or @ref parse(const CharT, const parser_callback_t), - it is called on certain events (passed as @ref parse_event_t via parameter - @a event) with a set recursion depth @a depth and context JSON value - @a parsed. The return value of the callback function is a boolean - indicating whether the element that emitted the callback shall be kept or - not. + To store objects in C++, a type is defined by the template parameters + explained below. - We distinguish six scenarios (determined by the event type) in which the - callback function can be called. The following table describes the values - of the parameters @a depth, @a event, and @a parsed. + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) - parameter @a event | description | parameter @a depth | parameter @a parsed - ------------------ | ----------- | ------------------ | ------------------- - parse_event_t::object_start | the parser read `{` and started to process a - JSON object | depth of the parent of the JSON object | a JSON value with - type - discarded - parse_event_t::key | the parser read a key of a value in an object | depth - of - the currently parsed JSON object | a JSON string containing the key - parse_event_t::object_end | the parser read `}` and finished processing a - JSON - object | depth of the parent of the JSON object | the parsed JSON object - parse_event_t::array_start | the parser read `[` and started to process a - JSON - array | depth of the parent of the JSON array | a JSON value with type - discarded - parse_event_t::array_end | the parser read `]` and finished processing a - JSON - array | depth of the parent of the JSON array | the parsed JSON array - parse_event_t::value | the parser finished reading a JSON value | depth of - the - value | the parsed JSON value + #### Default type - @image html callback_events.png "Example when certain parse events are - triggered" + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: - Discarding a value (i.e., returning `false`) has different effects - depending on the context in which function was called: + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator<basic_json> // allocator_type + > + @endcode - - Discarded values in structured types are skipped. That is, the parser - will behave as if the discarded value was never read. - - In case a value outside a structured type is skipped, it is replaced - with `null`. This case happens if the top-level element is skipped. + #### Limits - @param[in] depth the depth of the recursion during parsing + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. - @param[in] event an event of type parse_event_t indicating the context in - the callback function has been called + In this class, the array's limit of nesting is not constraint explicitly. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. - @param[in,out] parsed the current intermediate parse result; note that - writing to this value has no effect for parse_event_t::key events + #### Storage - @return Whether the JSON value which called the function during parsing - should be kept (`true`) or not (`false`). In the latter case, it is either - skipped completely or replaced by an empty discarded object. + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. - @sa @ref parse(std::istream&, parser_callback_t) or - @ref parse(const CharT, const parser_callback_t) for examples + @sa @ref object_t -- type for an object value @since version 1.0.0 */ - using parser_callback_t = - std::function<bool(int depth, parse_event_t event, basic_json &parsed)>; + using array_t = ArrayType<basic_json, AllocatorType<basic_json>>; - ////////////////// - // constructors // - ////////////////// + /*! + @brief a type for a string - /// @name constructors and destructors - /// Constructors of class @ref basic_json, copy/move constructor, copy - /// assignment, static functions creating objects, and the destructor. - /// @{ + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. - /*! - @brief create an empty value with a given type + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. - Create an empty JSON value with a given type. The value will be default - initialized with an empty value which depends on the type: + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. - Value type | initial value - ----------- | ------------- - null | `null` - boolean | `false` - string | `""` - number | `0` - object | `{}` - array | `[]` + #### Default type - @param[in] value_type the type of the value to create + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: - @complexity Constant. + @code {.cpp} + std::string + @endcode - @throw std::bad_alloc if allocation for object, array, or string value - fails + #### Encoding - @liveexample{The following code shows the constructor for different @ref - value_t values,basic_json__value_t} + Strings are stored in UTF-8 encoding. Therefore, functions like + `std::string::size()` or `std::string::length()` return the number of + bytes in the string rather than the number of characters or glyphs. + + #### String comparison + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage - @sa @ref basic_json(std::nullptr_t) -- create a `null` value - @sa @ref basic_json(boolean_t value) -- create a boolean value - @sa @ref basic_json(const string_t&) -- create a string value - @sa @ref basic_json(const object_t&) -- create a object value - @sa @ref basic_json(const array_t&) -- create a array value - @sa @ref basic_json(const number_float_t) -- create a number - (floating-point) value - @sa @ref basic_json(const number_integer_t) -- create a number (integer) - value - @sa @ref basic_json(const number_unsigned_t) -- create a number (unsigned) - value + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. @since version 1.0.0 */ - basic_json(const value_t value_type) - : m_type(value_type), m_value(value_type) - { - assert_invariant(); - } + using string_t = StringType; /*! - @brief create a null object + @brief a type for a boolean - Create a `null` JSON value. It either takes a null pointer as parameter - (explicitly creating `null`) or no parameter (implicitly creating `null`). - The passed null pointer itself is not read -- it is only used to choose - the right constructor. + [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. - @complexity Constant. + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. - @exceptionsafety No-throw guarantee: this constructor never throws - exceptions. + #### Default type - @liveexample{The following code shows the constructor with and without a - null pointer parameter.,basic_json__nullptr_t} + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. @since version 1.0.0 */ - basic_json(std::nullptr_t = nullptr) noexcept : basic_json(value_t::null) - { - assert_invariant(); - } + using boolean_t = BooleanType; /*! - @brief create an object (explicit) + @brief a type for a number (integer) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. - Create an object JSON value with a given content. + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. - @param[in] val a value for the object + #### Default type - @complexity Linear in the size of the passed @a val. + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: - @throw std::bad_alloc if allocation for object value fails + @code {.cpp} + int64_t + @endcode - @liveexample{The following code shows the constructor with an @ref - object_t parameter.,basic_json__object_t} + #### Default behavior - @sa @ref basic_json(const CompatibleObjectType&) -- create an object value - from a compatible STL container + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. - @since version 1.0.0 - */ - basic_json(const object_t &val) : m_type(value_t::object), m_value(val) - { - assert_invariant(); - } + #### Limits - /*! - @brief create an object (implicit) + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. - Create an object JSON value with a given content. This constructor allows - any type @a CompatibleObjectType that can be used to construct values of - type @ref object_t. + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. - @tparam CompatibleObjectType An object type whose `key_type` and - `value_type` is compatible to @ref object_t. Examples include `std::map`, - `std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with - a `key_type` of `std::string`, and a `value_type` from which a @ref - basic_json value can be constructed. + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. - @param[in] val a value for the object + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. - @complexity Linear in the size of the passed @a val. + #### Storage - @throw std::bad_alloc if allocation for object value fails + Integer number values are stored directly inside a @ref basic_json type. - @liveexample{The following code shows the constructor with several - compatible object type parameters.,basic_json__CompatibleObjectType} + @sa @ref number_float_t -- type for number values (floating-point) - @sa @ref basic_json(const object_t&) -- create an object value + @sa @ref number_unsigned_t -- type for number values (unsigned integer) @since version 1.0.0 */ - template <class CompatibleObjectType, - typename std::enable_if< - std::is_constructible< - typename object_t::key_type, - typename CompatibleObjectType::key_type>::value and - std::is_constructible< - basic_json, - typename CompatibleObjectType::mapped_type>::value, - int>::type = 0> - basic_json(const CompatibleObjectType &val) : m_type(value_t::object) - { - using std::begin; - using std::end; - m_value.object = create<object_t>(begin(val), end(val)); - assert_invariant(); - } + using number_integer_t = NumberIntegerType; /*! - @brief create an array (explicit) + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. - Create an array JSON value with a given content. + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. - @param[in] val a value for the array + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. - @complexity Linear in the size of the passed @a val. + #### Default type - @throw std::bad_alloc if allocation for array value fails + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: - @liveexample{The following code shows the constructor with an @ref array_t - parameter.,basic_json__array_t} + @code {.cpp} + uint64_t + @endcode - @sa @ref basic_json(const CompatibleArrayType&) -- create an array value - from a compatible STL containers + #### Default behavior - @since version 1.0.0 - */ - basic_json(const array_t &val) : m_type(value_t::array), m_value(val) - { - assert_invariant(); - } + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. - /*! - @brief create an array (implicit) + #### Limits - Create an array JSON value with a given content. This constructor allows - any type @a CompatibleArrayType that can be used to construct values of - type @ref array_t. + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. - @tparam CompatibleArrayType An object type whose `value_type` is - compatible to @ref array_t. Examples include `std::vector`, `std::deque`, - `std::list`, `std::forward_list`, `std::array`, `std::set`, - `std::unordered_set`, `std::multiset`, and `unordered_multiset` with a - `value_type` from which a @ref basic_json value can be constructed. + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. - @param[in] val a value for the array + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. - @complexity Linear in the size of the passed @a val. + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. - @throw std::bad_alloc if allocation for array value fails + #### Storage - @liveexample{The following code shows the constructor with several - compatible array type parameters.,basic_json__CompatibleArrayType} + Integer number values are stored directly inside a @ref basic_json type. - @sa @ref basic_json(const array_t&) -- create an array value + @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_integer_t -- type for number values (integer) - @since version 1.0.0 + @since version 2.0.0 */ - template < - class CompatibleArrayType, - typename std::enable_if< - not std::is_same<CompatibleArrayType, - typename basic_json_t::iterator>::value and - not std::is_same< - CompatibleArrayType, - typename basic_json_t::const_iterator>::value and - not std::is_same< - CompatibleArrayType, - typename basic_json_t::reverse_iterator>::value and - not std::is_same< - CompatibleArrayType, - typename basic_json_t::const_reverse_iterator>::value and - not std::is_same<CompatibleArrayType, - typename array_t::iterator>::value and - not std::is_same<CompatibleArrayType, - typename array_t::const_iterator>::value and - std::is_constructible<basic_json, typename CompatibleArrayType:: - value_type>::value, - int>::type = 0> - basic_json(const CompatibleArrayType &val) : m_type(value_t::array) - { - using std::begin; - using std::end; - m_value.array = create<array_t>(begin(val), end(val)); - assert_invariant(); - } + using number_unsigned_t = NumberUnsignedType; /*! - @brief create a string (explicit) + @brief a type for a number (floating-point) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. - Create an string JSON value with a given content. + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. - @param[in] val a value for the string + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. - @complexity Linear in the size of the passed @a val. + #### Default type - @throw std::bad_alloc if allocation for string value fails + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: - @liveexample{The following code shows the constructor with an @ref - string_t parameter.,basic_json__string_t} + @code {.cpp} + double + @endcode - @sa @ref basic_json(const typename string_t::value_type*) -- create a - string value from a character pointer - @sa @ref basic_json(const CompatibleStringType&) -- create a string value - from a compatible string container + #### Default behavior - @since version 1.0.0 - */ - basic_json(const string_t &val) : m_type(value_t::string), m_value(val) - { - assert_invariant(); - } + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. - /*! - @brief create a string (explicit) + #### Limits - Create a string JSON value with a given content. + [RFC 7159](http://rfc7159.net/rfc7159) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. - @param[in] val a literal value for the string + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. - @complexity Linear in the size of the passed @a val. + #### Storage - @throw std::bad_alloc if allocation for string value fails + Floating-point number values are stored directly inside a @ref basic_json + type. - @liveexample{The following code shows the constructor with string literal - parameter.,basic_json__string_t_value_type} + @sa @ref number_integer_t -- type for number values (integer) - @sa @ref basic_json(const string_t&) -- create a string value - @sa @ref basic_json(const CompatibleStringType&) -- create a string value - from a compatible string container + @sa @ref number_unsigned_t -- type for number values (unsigned integer) @since version 1.0.0 */ - basic_json(const typename string_t::value_type *val) - : basic_json(string_t(val)) - { - assert_invariant(); - } - - /*! - @brief create a string (implicit) + using number_float_t = NumberFloatType; - Create a string JSON value with a given content. + /// @} - @param[in] val a value for the string +private: + /// helper for exception-safe object creation + template <typename T, typename... Args> + static T *create(Args &&... args) + { + AllocatorType<T> alloc; + auto deleter = [&](T *object) { alloc.deallocate(object, 1); }; + std::unique_ptr<T, decltype(deleter)> object(alloc.allocate(1), + deleter); + alloc.construct(object.get(), std::forward<Args>(args)...); + assert(object != nullptr); + return object.release(); + } - @tparam CompatibleStringType an string type which is compatible to @ref - string_t, for instance `std::string`. + //////////////////////// + // JSON value storage // + //////////////////////// - @complexity Linear in the size of the passed @a val. + /*! + @brief a JSON value - @throw std::bad_alloc if allocation for string value fails + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. - @liveexample{The following code shows the construction of a string value - from a compatible type.,basic_json__CompatibleStringType} + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + null | null | *no value is stored* - @sa @ref basic_json(const string_t&) -- create a string value - @sa @ref basic_json(const typename string_t::value_type*) -- create a - string value from a character pointer + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. @since version 1.0.0 */ - template <class CompatibleStringType, - typename std::enable_if< - std::is_constructible<string_t, CompatibleStringType>::value, - int>::type = 0> - basic_json(const CompatibleStringType &val) : basic_json(string_t(val)) - { - assert_invariant(); - } + union json_value { + /// object (stored with pointer to save storage) + object_t *object; + /// array (stored with pointer to save storage) + array_t *array; + /// string (stored with pointer to save storage) + string_t *string; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; - /*! - @brief create a boolean (explicit) + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create<object_t>(); + break; + } - Creates a JSON boolean type from a given value. + case value_t::array: + { + array = create<array_t>(); + break; + } - @param[in] val a boolean value to store + case value_t::string: + { + string = create<string_t>(""); + break; + } - @complexity Constant. + case value_t::boolean: + { + boolean = boolean_t(false); + break; + } - @liveexample{The example below demonstrates boolean - values.,basic_json__boolean_t} + case value_t::number_integer: + { + number_integer = number_integer_t(0); + break; + } - @since version 1.0.0 - */ - basic_json(boolean_t val) noexcept : m_type(value_t::boolean), m_value(val) - { - assert_invariant(); - } + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } - /*! - @brief create an integer number (explicit) + case value_t::number_float: + { + number_float = number_float_t(0.0); + break; + } - Create an integer number JSON value with a given content. + case value_t::null: + { + break; + } - @tparam T A helper type to remove this function via SFINAE in case @ref - number_integer_t is the same as `int`. In this case, this constructor - would have the same signature as @ref basic_json(const int value). Note - the helper type @a T is not visible in this constructor's interface. + default: + { + if (t == value_t::null) + { + JSON_THROW( + other_error::create(500, + "961c151d2e87f2686a955a9be24d316f13" + "62bf21 2.1.1")); // LCOV_EXCL_LINE + } + break; + } + } + } - @param[in] val an integer to create a JSON number from + /// constructor for strings + json_value(const string_t &value) { string = create<string_t>(value); } - @complexity Constant. + /// constructor for objects + json_value(const object_t &value) { object = create<object_t>(value); } - @liveexample{The example below shows the construction of an integer - number value.,basic_json__number_integer_t} + /// constructor for arrays + json_value(const array_t &value) { array = create<array_t>(value); } + }; - @sa @ref basic_json(const int) -- create a number value (integer) - @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number - value (integer) from a compatible number type + /*! + @brief checks the class invariants - @since version 1.0.0 + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. */ - template <typename T, typename std::enable_if< - not(std::is_same<T, int>::value) and - std::is_same<T, number_integer_t>::value, - int>::type = 0> - basic_json(const number_integer_t val) noexcept - : m_type(value_t::number_integer), - m_value(val) + void assert_invariant() const { - assert_invariant(); + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); } - /*! - @brief create an integer number from an enum type (explicit) - - Create an integer number JSON value with a given content. - - @param[in] val an integer to create a JSON number from - - @note This constructor allows to pass enums directly to a constructor. As - C++ has no way of specifying the type of an anonymous enum explicitly, we - can only rely on the fact that such values implicitly convert to int. As - int may already be the same type of number_integer_t, we may need to - switch off the constructor @ref basic_json(const number_integer_t). +public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// - @complexity Constant. + /*! + @brief JSON callback events - @liveexample{The example below shows the construction of an integer - number value from an anonymous enum.,basic_json__const_int} + This enumeration lists the parser events that can trigger calling a + callback function of type @ref parser_callback_t during parsing. - @sa @ref basic_json(const number_integer_t) -- create a number value - (integer) - @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number - value (integer) from a compatible number type + @image html callback_events.png "Example when certain parse events are + triggered" @since version 1.0.0 */ - basic_json(const int val) noexcept - : m_type(value_t::number_integer), - m_value(static_cast<number_integer_t>(val)) + enum class parse_event_t : uint8_t { - assert_invariant(); - } + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value + }; /*! - @brief create an integer number (implicit) + @brief per-element parser callback type - Create an integer number JSON value with a given content. This constructor - allows any type @a CompatibleNumberIntegerType that can be used to - construct values of type @ref number_integer_t. + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse(std::istream&, const + parser_callback_t) or @ref parse(const CharT, const parser_callback_t), + it is called on certain events (passed as @ref parse_event_t via parameter + @a event) with a set recursion depth @a depth and context JSON value + @a parsed. The return value of the callback function is a boolean + indicating whether the element that emitted the callback shall be kept or + not. - @tparam CompatibleNumberIntegerType An integer type which is compatible to - @ref number_integer_t. Examples include the types `int`, `int32_t`, - `long`, and `short`. + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. - @param[in] val an integer to create a JSON number from + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a + JSON object | depth of the parent of the JSON object | a JSON value with + type discarded + parse_event_t::key | the parser read a key of a value in an object | depth + of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a + JSON object | depth of the parent of the JSON object | the parsed JSON + object + parse_event_t::array_start | the parser read `[` and started to process a + JSON array | depth of the parent of the JSON array | a JSON value with type + discarded + parse_event_t::array_end | the parser read `]` and finished processing a + JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of + the value | the parsed JSON value - @complexity Constant. + @image html callback_events.png "Example when certain parse events are + triggered" - @liveexample{The example below shows the construction of several integer - number values from compatible - types.,basic_json__CompatibleIntegerNumberType} + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: - @sa @ref basic_json(const number_integer_t) -- create a number value - (integer) - @sa @ref basic_json(const int) -- create a number value (integer) + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. - @since version 1.0.0 - */ - template < - typename CompatibleNumberIntegerType, - typename std::enable_if< - std::is_constructible<number_integer_t, - CompatibleNumberIntegerType>::value and - std::numeric_limits<CompatibleNumberIntegerType>::is_integer and - std::numeric_limits<CompatibleNumberIntegerType>::is_signed, - CompatibleNumberIntegerType>::type = 0> - basic_json(const CompatibleNumberIntegerType val) noexcept - : m_type(value_t::number_integer), - m_value(static_cast<number_integer_t>(val)) - { - assert_invariant(); - } + @param[in] depth the depth of the recursion during parsing - /*! - @brief create an unsigned integer number (explicit) + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called - Create an unsigned integer number JSON value with a given content. + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events - @tparam T helper type to compare number_unsigned_t and unsigned int (not - visible in) the interface. + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. - @param[in] val an integer to create a JSON number from + @sa @ref parse(std::istream&, parser_callback_t) or + @ref parse(const CharT, const parser_callback_t) for examples - @complexity Constant. + @since version 1.0.0 + */ + using parser_callback_t = + std::function<bool(int depth, parse_event_t event, basic_json &parsed)>; - @sa @ref basic_json(const CompatibleNumberUnsignedType) -- create a number - value (unsigned integer) from a compatible number type + ////////////////// + // constructors // + ////////////////// - @since version 2.0.0 - */ - template <typename T, typename std::enable_if< - not(std::is_same<T, int>::value) and - std::is_same<T, number_unsigned_t>::value, - int>::type = 0> - basic_json(const number_unsigned_t val) noexcept - : m_type(value_t::number_unsigned), - m_value(val) - { - assert_invariant(); - } + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ /*! - @brief create an unsigned number (implicit) + @brief create an empty value with a given type - Create an unsigned number JSON value with a given content. This - constructor allows any type @a CompatibleNumberUnsignedType that can be - used to construct values of type @ref number_unsigned_t. + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: - @tparam CompatibleNumberUnsignedType An integer type which is compatible - to @ref number_unsigned_t. Examples may include the types `unsigned int`, - `uint32_t`, or `unsigned short`. + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` - @param[in] val an unsigned integer to create a JSON number from + @param[in] value_type the type of the value to create @complexity Constant. - @sa @ref basic_json(const number_unsigned_t) -- create a number value - (unsigned) + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} - @since version 2.0.0 + @since version 1.0.0 */ - template <typename CompatibleNumberUnsignedType, - typename std::enable_if< - std::is_constructible<number_unsigned_t, - CompatibleNumberUnsignedType>::value and - std::numeric_limits< - CompatibleNumberUnsignedType>::is_integer and - not std::numeric_limits< - CompatibleNumberUnsignedType>::is_signed, - CompatibleNumberUnsignedType>::type = 0> - basic_json(const CompatibleNumberUnsignedType val) noexcept - : m_type(value_t::number_unsigned), - m_value(static_cast<number_unsigned_t>(val)) + basic_json(const value_t value_type) + : m_type(value_type), m_value(value_type) { assert_invariant(); } /*! - @brief create a floating-point number (explicit) - - Create a floating-point number JSON value with a given content. - - @param[in] val a floating-point value to create a JSON number from + @brief create a null object - @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 - disallows NaN values: - > Numeric values that cannot be represented in the grammar below (such as - > Infinity and NaN) are not permitted. - In case the parameter @a val is not a number, a JSON null value is created - instead. + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. @complexity Constant. - @liveexample{The following example creates several floating-point - values.,basic_json__number_float_t} + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. - @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number - value (floating-point) from a compatible number type + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} @since version 1.0.0 */ - basic_json(const number_float_t val) noexcept - : m_type(value_t::number_float), - m_value(val) + basic_json(std::nullptr_t = nullptr) noexcept : basic_json(value_t::null) { - // replace infinity and NAN by null - if (not std::isfinite(val)) - { - m_type = value_t::null; - m_value = json_value(); - } - assert_invariant(); } /*! - @brief create an floating-point number (implicit) + @brief create a JSON value - Create an floating-point number JSON value with a given content. This - constructor allows any type @a CompatibleNumberFloatType that can be used - to construct values of type @ref number_float_t. + This is a "catch all" constructor for all compatible JSON types; that is, + types for which a `to_json()` method exsits. The constructor forwards the + parameter @a val to that method (to `json_serializer<U>::to_json` method + with `U = uncvref_t<CompatibleType>`, to be exact). - @tparam CompatibleNumberFloatType A floating-point type which is - compatible to @ref number_float_t. Examples may include the types `float` - or `double`. + Template type @a CompatibleType includes, but is not limited to, the + following types: + - **arrays**: @ref array_t and all kinds of compatible containers such as + `std::vector`, `std::deque`, `std::list`, `std::forward_list`, + `std::array`, `std::set`, `std::unordered_set`, `std::multiset`, and + `unordered_multiset` with a `value_type` from which a @ref basic_json + value can be constructed. + - **objects**: @ref object_t and all kinds of compatible associative + containers such as `std::map`, `std::unordered_map`, `std::multimap`, + and `std::unordered_multimap` with a `key_type` compatible to + @ref string_t and a `value_type` from which a @ref basic_json value can + be constructed. + - **strings**: @ref string_t, string literals, and all compatible string + containers can be used. + - **numbers**: @ref number_integer_t, @ref number_unsigned_t, + @ref number_float_t, and all convertible number types such as `int`, + `size_t`, `int64_t`, `float` or `double` can be used. + - **boolean**: @ref boolean_t / `bool` can be used. - @param[in] val a floating-point to create a JSON number from + See the examples below. - @note [RFC 7159](http://www.rfc-editor.org/rfc/rfc7159.txt), section 6 - disallows NaN values: - > Numeric values that cannot be represented in the grammar below (such as - > Infinity and NaN) are not permitted. - In case the parameter @a val is not a number, a JSON null value is - created instead. + @tparam CompatibleType a type such that: + - @a CompatibleType is not derived from `std::istream`, + - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move + constructors), + - @a CompatibleType is not a @ref basic_json nested type (e.g., + @ref json_pointer, @ref iterator, etc ...) + - @ref @ref json_serializer<U> has a + `to_json(basic_json_t&, CompatibleType&&)` method - @complexity Constant. + @tparam U = `uncvref_t<CompatibleType>` - @liveexample{The example below shows the construction of several - floating-point number values from compatible - types.,basic_json__CompatibleNumberFloatType} + @param[in] val the value to be forwarded - @sa @ref basic_json(const number_float_t) -- create a number value - (floating-point) + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. - @since version 1.0.0 + @throw what `json_serializer<U>::to_json()` throws + + @liveexample{The following code shows the constructor with several + compatible types.,basic_json__CompatibleType} + + @since version 2.1.0 */ template < - typename CompatibleNumberFloatType, - typename = typename std::enable_if< - std::is_constructible<number_float_t, - CompatibleNumberFloatType>::value and - std::is_floating_point<CompatibleNumberFloatType>::value>::type> - basic_json(const CompatibleNumberFloatType val) noexcept - : basic_json(number_float_t(val)) - { + typename CompatibleType, typename U = detail::uncvref_t<CompatibleType>, + detail::enable_if_t<not std::is_base_of<std::istream, U>::value and + not std::is_same<U, basic_json_t>::value and + not detail::is_basic_json_nested_type< + basic_json_t, U>::value and + detail::has_to_json<basic_json, U>::value, + int> = 0> + basic_json(CompatibleType &&val) noexcept( + noexcept(JSONSerializer<U>::to_json(std::declval<basic_json_t &>(), + std::forward<CompatibleType>(val)))) + { + JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val)); assert_invariant(); } @@ -1725,10 +2527,12 @@ public: value_t::array and @ref value_t::object are valid); when @a type_deduction is set to `true`, this parameter has no effect - @throw std::domain_error if @a type_deduction is `false`, @a manual_type - is `value_t::object`, but @a init contains an element which is not a pair - whose first element is a string; example: `"cannot create object from - initializer list"` + @throw type_error.301 if @a type_deduction is `false`, @a manual_type is + `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string. In this case, the constructor could not + create an object. If @a type_deduction would have be `true`, an array + would have been created. See @ref object(std::initializer_list<basic_json>) + for an example. @complexity Linear in the size of the initializer list @a init. @@ -1765,8 +2569,8 @@ public: // if object is wanted but impossible, throw an exception if (manual_type == value_t::object and not is_an_object) { - JSON_THROW(std::domain_error( - "cannot create object from initializer list")); + JSON_THROW(type_error::create( + 301, "cannot create object from initializer list")); } } @@ -1850,9 +2654,12 @@ public: @return JSON object value - @throw std::domain_error if @a init is not a pair whose first elements are - strings; thrown by - @ref basic_json(std::initializer_list<basic_json>, bool, value_t) + @throw type_error.301 if @a init is not a list of pairs whose first + elements are strings. In this case, no object can be created. When such a + value is passed to @ref basic_json(std::initializer_list<basic_json>, bool, + value_t), + an array would have been created from the passed initializer list @a init. + See example below. @complexity Linear in the size of @a init. @@ -1903,10 +2710,10 @@ public: The semantics depends on the different types a JSON value can have: - In case of primitive types (number, boolean, or string), @a first must be `begin()` and @a last must be `end()`. In this case, the value is - copied. Otherwise, std::out_of_range is thrown. + copied. Otherwise, invalid_iterator.204 is thrown. - In case of structured types (array, object), the constructor behaves as similar versions for `std::vector`. - - In case of a null type, std::domain_error is thrown. + - In case of a null type, invalid_iterator.206 is thrown. @tparam InputIT an input iterator type (@ref iterator or @ref const_iterator) @@ -1917,14 +2724,19 @@ public: @pre Iterators @a first and @a last must be initialized. **This precondition is enforced with an assertion.** - @throw std::domain_error if iterators are not compatible; that is, do not - belong to the same JSON value; example: `"iterators are not compatible"` - @throw std::out_of_range if iterators are for a primitive type (number, - boolean, or string) where an out of range error can be detected easily; - example: `"iterators out of range"` - @throw std::bad_alloc if allocation for object, array, or string fails - @throw std::domain_error if called with a null value; example: `"cannot - use construct with iterators from null"` + @pre Range `[first, last)` is valid. Usually, this precondition cannot be + checked efficiently. Only certain edge cases are detected; see the + description of the exceptions below. + + @throw invalid_iterator.201 if iterators @a first and @a last are not + compatible (i.e., do not belong to the same JSON value). In this case, + the range `[first, last)` is undefined. + @throw invalid_iterator.204 if iterators @a first and @a last belong to a + primitive type (number, boolean, or string), but @a first does not point + to the first element any more. In this case, the range `[first, last)` is + undefined. See example code below. + @throw invalid_iterator.206 if iterators @a first and @a last belong to a + null value. In this case, the range `[first, last)` is undefined. @complexity Linear in distance between @a first and @a last. @@ -1948,7 +2760,8 @@ public: // make sure iterator fits the current value if (first.m_object != last.m_object) { - JSON_THROW(std::domain_error("iterators are not compatible")); + JSON_THROW( + invalid_iterator::create(201, "iterators are not compatible")); } // copy type from first iterator @@ -1966,7 +2779,8 @@ public: if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) { - JSON_THROW(std::out_of_range("iterators out of range")); + JSON_THROW( + invalid_iterator::create(204, "iterators out of range")); } break; } @@ -2025,50 +2839,15 @@ public: default: { - JSON_THROW( - std::domain_error("cannot use construct with iterators from " + - first.m_object->type_name())); + JSON_THROW(invalid_iterator::create( + 206, "cannot construct with iterators from " + + first.m_object->type_name())); } } assert_invariant(); } - /*! - @brief construct a JSON value given an input stream - - @param[in,out] i stream to read a serialized JSON value from - @param[in] cb a parser callback function of type @ref parser_callback_t - which is used to control the deserialization by filtering unwanted values - (optional) - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. The complexity can be higher if the parser callback function - @a cb has a super-linear complexity. - - @note A UTF-8 byte order mark is silently ignored. - - @deprecated This constructor is deprecated and will be removed in version - 3.0.0 to unify the interface of the library. Deserialization will be - done by stream operators or by calling one of the `parse` functions, - e.g. @ref parse(std::istream&, const parser_callback_t). That is, calls - like `json j(i);` for an input stream @a i need to be replaced by - `json j = json::parse(i);`. See the example below. - - @liveexample{The example below demonstrates constructing a JSON value from - a `std::stringstream` with and without callback - function.,basic_json__istream} - - @since version 2.0.0, deprecated in version 2.0.3, to be removed in - version 3.0.0 - */ - JSON_DEPRECATED - explicit basic_json(std::istream &i, const parser_callback_t cb = nullptr) - { - *this = parser(i, cb).parse(); - assert_invariant(); - } - /////////////////////////////////////// // other constructors and destructor // /////////////////////////////////////// @@ -2088,8 +2867,6 @@ public: - The complexity is linear. - As postcondition, it holds: `other == basic_json(other)`. - @throw std::bad_alloc if allocation for object, array, or string fails. - @liveexample{The following code shows an example for the copy constructor.,basic_json__basic_json} @@ -2314,22 +3091,15 @@ public: string_t dump(const int indent = -1) const { std::stringstream ss; - // fix locale problems - ss.imbue(std::locale::classic()); - - // 6, 15 or 16 digits of precision allows round-trip IEEE 754 - // string->float->string, string->double->string or string->long - // double->string; to be safe, we read this value from - // std::numeric_limits<number_float_t>::digits10 - ss.precision(std::numeric_limits<double>::digits10); + serializer s(ss); if (indent >= 0) { - dump(ss, true, static_cast<unsigned int>(indent)); + s.dump(*this, true, static_cast<unsigned int>(indent)); } else { - dump(ss, false, 0); + s.dump(*this, false, 0); } return ss.str(); @@ -2676,171 +3446,18 @@ public: exceptions. @liveexample{The following code exemplifies the @ref value_t operator for - all JSON types.,operator__value_t} - - @since version 1.0.0 - */ - constexpr operator value_t() const noexcept { return m_type; } - - /// @} - -private: - ////////////////// - // value access // - ////////////////// - - /// get an object (explicit) - template <class T, - typename std::enable_if< - std::is_convertible<typename object_t::key_type, - typename T::key_type>::value and - std::is_convertible<basic_json_t, - typename T::mapped_type>::value, - int>::type = 0> - T get_impl(T * /*unused*/) const - { - if (is_object()) - { - return T(m_value.object->begin(), m_value.object->end()); - } - - JSON_THROW( - std::domain_error("type must be object, but is " + type_name())); - } - - /// get an object (explicit) - object_t get_impl(object_t * /*unused*/) const - { - if (is_object()) - { - return *(m_value.object); - } - - JSON_THROW( - std::domain_error("type must be object, but is " + type_name())); - } - - /// get an array (explicit) - template < - class T, - typename std::enable_if< - std::is_convertible<basic_json_t, typename T::value_type>::value and - not std::is_same<basic_json_t, - typename T::value_type>::value and - not std::is_arithmetic<T>::value and - not std::is_convertible<std::string, T>::value and - not has_mapped_type<T>::value, - int>::type = 0> - T get_impl(T * /*unused*/) const - { - if (is_array()) - { - T to_vector; - std::transform( - m_value.array->begin(), m_value.array->end(), - std::inserter(to_vector, to_vector.end()), - [](basic_json i) { return i.get<typename T::value_type>(); }); - return to_vector; - } - - JSON_THROW( - std::domain_error("type must be array, but is " + type_name())); - } - - /// get an array (explicit) - template <class T, typename std::enable_if< - std::is_convertible<basic_json_t, T>::value and - not std::is_same<basic_json_t, T>::value, - int>::type = 0> - std::vector<T> get_impl(std::vector<T> * /*unused*/) const - { - if (is_array()) - { - std::vector<T> to_vector; - to_vector.reserve(m_value.array->size()); - std::transform(m_value.array->begin(), m_value.array->end(), - std::inserter(to_vector, to_vector.end()), - [](basic_json i) { return i.get<T>(); }); - return to_vector; - } - - JSON_THROW( - std::domain_error("type must be array, but is " + type_name())); - } - - /// get an array (explicit) - template <class T, - typename std::enable_if< - std::is_same<basic_json, typename T::value_type>::value and - not has_mapped_type<T>::value, - int>::type = 0> - T get_impl(T * /*unused*/) const - { - if (is_array()) - { - return T(m_value.array->begin(), m_value.array->end()); - } - - JSON_THROW( - std::domain_error("type must be array, but is " + type_name())); - } - - /// get an array (explicit) - array_t get_impl(array_t * /*unused*/) const - { - if (is_array()) - { - return *(m_value.array); - } - - JSON_THROW( - std::domain_error("type must be array, but is " + type_name())); - } - - /// get a string (explicit) - template <typename T, - typename std::enable_if<std::is_convertible<string_t, T>::value, - int>::type = 0> - T get_impl(T * /*unused*/) const - { - if (is_string()) - { - return *m_value.string; - } - - JSON_THROW( - std::domain_error("type must be string, but is " + type_name())); - } - - /// get a number (explicit) - template <typename T, typename std::enable_if<std::is_arithmetic<T>::value, - int>::type = 0> - T get_impl(T * /*unused*/) const - { - switch (m_type) - { - case value_t::number_integer: - { - return static_cast<T>(m_value.number_integer); - } - - case value_t::number_unsigned: - { - return static_cast<T>(m_value.number_unsigned); - } + all JSON types.,operator__value_t} - case value_t::number_float: - { - return static_cast<T>(m_value.number_float); - } + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept { return m_type; } - default: - { - JSON_THROW(std::domain_error("type must be number, but is " + - type_name())); - } - } - } + /// @} + +private: + ////////////////// + // value access // + ////////////////// /// get a boolean (explicit) boolean_t get_impl(boolean_t * /*unused*/) const @@ -2849,11 +3466,9 @@ private: { return m_value.boolean; } - else - { - JSON_THROW(std::domain_error("type must be boolean, but is " + - type_name())); - } + + JSON_THROW(type_error::create(302, "type must be boolean, but is " + + type_name())); } /// get a pointer to the value (object) @@ -2955,7 +3570,7 @@ private: @tparam ThisType will be deduced as `basic_json` or `const basic_json` - @throw std::domain_error if ReferenceType does not match underlying value + @throw type_error.303 if ReferenceType does not match underlying value type of the current JSON */ template <typename ReferenceType, typename ThisType> @@ -2972,9 +3587,9 @@ private: return *ptr; } - throw std::domain_error( - "incompatible ReferenceType for get_ref, actual type is " + - obj.type_name()); + JSON_THROW(type_error::create( + 303, "incompatible ReferenceType for get_ref, actual type is " + + obj.type_name())); } public: @@ -2982,21 +3597,61 @@ public: /// Direct access to the stored value of a JSON value. /// @{ + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template <typename BasicJsonType, + detail::enable_if_t< + std::is_same<typename std::remove_const<BasicJsonType>::type, + basic_json_t>::value, + int> = 0> + basic_json get() const + { + return *this; + } + /*! @brief get a value (explicit) - Explicit type conversion between the JSON value and a compatible value. + Explicit type conversion between the JSON value and a compatible value + which is + [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and + [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer<ValueType> + `from_json()` method. - @tparam ValueType non-pointer type compatible to the JSON value, for - instance `int` for JSON integer numbers, `bool` for JSON booleans, or - `std::vector` types for JSON arrays + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer<ValueType>::from_json(*this, ret); + return ret; + @endcode - @return copy of the JSON value, converted to type @a ValueType + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer<ValueType> has a `from_json()` method of the form + `void from_json(const @ref basic_json&, ValueType&)`, and + - @ref json_serializer<ValueType> does not have a `from_json()` method of + the form `ValueType from_json(const @ref basic_json&)` - @throw std::domain_error in case passed type @a ValueType is incompatible - to JSON; example: `"type must be object, but is null"` + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type - @complexity Linear in the size of the JSON value. + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer<ValueType> `from_json()` method throws @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can @@ -3005,22 +3660,83 @@ public: associative containers such as `std::unordered_map<std::string\, json>`.,get__ValueType_const} - @internal - The idea of using a casted null pointer to choose the correct - implementation is from <http://stackoverflow.com/a/8315197/266378>. - @endinternal + @since version 2.1.0 + */ + template <typename ValueTypeCV, + typename ValueType = detail::uncvref_t<ValueTypeCV>, + detail::enable_if_t< + not std::is_same<basic_json_t, ValueType>::value and + detail::has_from_json<basic_json_t, ValueType>::value and + not detail::has_non_default_from_json<basic_json_t, + ValueType>::value, + int> = 0> + ValueType get() const + noexcept(noexcept(JSONSerializer<ValueType>::from_json( + std::declval<const basic_json_t &>(), std::declval<ValueType &>()))) + { + // we cannot static_assert on ValueTypeCV being non-const, because + // there is support for get<const basic_json_t>(), which is why we + // still need the uncvref + static_assert(not std::is_reference<ValueTypeCV>::value, + "get() cannot be used with reference types, you might " + "want to use get_ref()"); + static_assert( + std::is_default_constructible<ValueType>::value, + "types must be DefaultConstructible when used with get()"); + + ValueType ret; + JSONSerializer<ValueType>::from_json(*this, ret); + return ret; + } - @sa @ref operator ValueType() const for implicit conversion - @sa @ref get() for pointer-member access + /*! + @brief get a value (explicit); special case - @since version 1.0.0 + Explicit type conversion between the JSON value and a compatible value + which is **not** + [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and **not** + [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer<ValueType> + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer<ValueTypeCV>::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer<ValueType> has a `from_json()` method of the form + `ValueType from_json(const @ref basic_json&)` + + @note If @ref json_serializer<ValueType> has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer<ValueType> `from_json()` method throws + + @since version 2.1.0 */ - template <typename ValueType, - typename std::enable_if<not std::is_pointer<ValueType>::value, - int>::type = 0> + template < + typename ValueTypeCV, + typename ValueType = detail::uncvref_t<ValueTypeCV>, + detail::enable_if_t<not std::is_same<basic_json_t, ValueType>::value and + detail::has_non_default_from_json< + basic_json_t, ValueType>::value, + int> = 0> ValueType get() const + noexcept(noexcept(JSONSerializer<ValueTypeCV>::from_json( + std::declval<const basic_json_t &>()))) { - return get_impl(static_cast<ValueType *>(nullptr)); + static_assert(not std::is_reference<ValueTypeCV>::value, + "get() cannot be used with reference types, you might " + "want to use get_ref()"); + return JSONSerializer<ValueTypeCV>::from_json(*this); } /*! @@ -3154,7 +3870,7 @@ public: /*! @brief get a reference value (implicit) - Implict reference access to the internally stored JSON value. No copies + Implicit reference access to the internally stored JSON value. No copies are made. @warning Writing data to the referee of the result yields an undefined @@ -3166,10 +3882,10 @@ public: @return reference to the internally stored JSON value if the requested reference type @a ReferenceType fits to the JSON value; throws - std::domain_error otherwise + type_error.303 otherwise - @throw std::domain_error in case passed type @a ReferenceType is - incompatible with the stored JSON value + @throw type_error.303 in case passed type @a ReferenceType is incompatible + with the stored JSON value; see example below @complexity Constant. @@ -3216,8 +3932,9 @@ public: @return copy of the JSON value, converted to type @a ValueType - @throw std::domain_error in case passed type @a ValueType is incompatible - to JSON, thrown by @ref get() const + @throw type_error.302 in case passed type @a ValueType is incompatible + to the JSON value type (e.g., the JSON value is of type boolean, but a + string is requested); see example below @complexity Linear in the size of the JSON value. @@ -3230,19 +3947,25 @@ public: @since version 1.0.0 */ - template <typename ValueType, - typename std::enable_if< - not std::is_pointer<ValueType>::value and - not std::is_same<ValueType, - typename string_t::value_type>::value -#ifndef _MSC_VER // fix for issue #167 operator<< abiguity under VS2015 - and - not std::is_same< - ValueType, std::initializer_list< - typename string_t::value_type>>::value + template < + typename ValueType, + typename std::enable_if< + not std::is_pointer<ValueType>::value and + not std::is_same<ValueType, + typename string_t::value_type>::value +#ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 + and + not std::is_same< + ValueType, + std::initializer_list<typename string_t::value_type>>::value #endif - , - int>::type = 0> +#if defined(_MSC_VER) && defined(_HAS_CXX17) && \ + _HAS_CXX17 == 1 // fix for issue #464 + and + not std::is_same<ValueType, typename std::string_view>::value +#endif + , + int>::type = 0> operator ValueType() const { // delegate the call to get<>() const @@ -3269,17 +3992,21 @@ public: @return reference to the element at index @a idx - @throw std::domain_error if the JSON value is not an array; example: - `"cannot use at() with string"` - @throw std::out_of_range if the index @a idx is out of range of the array; - that is, `idx >= size()`; example: `"array index 7 is out of range"` + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. - @complexity Constant. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. - @liveexample{The example below shows how array elements can be read and - written using `at()`.,at__size_type} + @complexity Constant. @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__size_type} */ reference at(size_type idx) { @@ -3290,14 +4017,15 @@ public: JSON_CATCH(std::out_of_range &) { // create better exception explanation - JSON_THROW(std::out_of_range( - "array index " + std::to_string(idx) + " is out of range")); + JSON_THROW(out_of_range::create(401, "array index " + + std::to_string(idx) + + " is out of range")); } } else { JSON_THROW( - std::domain_error("cannot use at() with " + type_name())); + type_error::create(304, "cannot use at() with " + type_name())); } } @@ -3311,17 +4039,21 @@ public: @return const reference to the element at index @a idx - @throw std::domain_error if the JSON value is not an array; example: - `"cannot use at() with string"` - @throw std::out_of_range if the index @a idx is out of range of the array; - that is, `idx >= size()`; example: `"array index 7 is out of range"` + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. - @complexity Constant. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. - @liveexample{The example below shows how array elements can be read using - `at()`.,at__size_type_const} + @complexity Constant. @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__size_type_const} */ const_reference at(size_type idx) const { @@ -3332,14 +4064,15 @@ public: JSON_CATCH(std::out_of_range &) { // create better exception explanation - JSON_THROW(std::out_of_range( - "array index " + std::to_string(idx) + " is out of range")); + JSON_THROW(out_of_range::create(401, "array index " + + std::to_string(idx) + + " is out of range")); } } else { JSON_THROW( - std::domain_error("cannot use at() with " + type_name())); + type_error::create(304, "cannot use at() with " + type_name())); } } @@ -3353,21 +4086,25 @@ public: @return reference to the element at key @a key - @throw std::domain_error if the JSON value is not an object; example: - `"cannot use at() with boolean"` - @throw std::out_of_range if the key @a key is is not stored in the object; - that is, `find(key) == end()`; example: `"key "the fast" not found"` + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. - @complexity Logarithmic in the size of the container. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. - @liveexample{The example below shows how object elements can be read and - written using `at()`.,at__object_t_key_type} + @complexity Logarithmic in the size of the container. @sa @ref operator[](const typename object_t::key_type&) for unchecked access by reference @sa @ref value() for access by value with a default value @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__object_t_key_type} */ reference at(const typename object_t::key_type &key) { @@ -3378,13 +4115,14 @@ public: JSON_CATCH(std::out_of_range &) { // create better exception explanation - JSON_THROW(std::out_of_range("key '" + key + "' not found")); + JSON_THROW( + out_of_range::create(403, "key '" + key + "' not found")); } } else { JSON_THROW( - std::domain_error("cannot use at() with " + type_name())); + type_error::create(304, "cannot use at() with " + type_name())); } } @@ -3398,21 +4136,25 @@ public: @return const reference to the element at key @a key - @throw std::domain_error if the JSON value is not an object; example: - `"cannot use at() with boolean"` - @throw std::out_of_range if the key @a key is is not stored in the object; - that is, `find(key) == end()`; example: `"key "the fast" not found"` + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. - @complexity Logarithmic in the size of the container. + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. - @liveexample{The example below shows how object elements can be read using - `at()`.,at__object_t_key_type_const} + @complexity Logarithmic in the size of the container. @sa @ref operator[](const typename object_t::key_type&) for unchecked access by reference @sa @ref value() for access by value with a default value @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__object_t_key_type_const} */ const_reference at(const typename object_t::key_type &key) const { @@ -3423,13 +4165,14 @@ public: JSON_CATCH(std::out_of_range &) { // create better exception explanation - JSON_THROW(std::out_of_range("key '" + key + "' not found")); + JSON_THROW( + out_of_range::create(403, "key '" + key + "' not found")); } } else { JSON_THROW( - std::domain_error("cannot use at() with " + type_name())); + type_error::create(304, "cannot use at() with " + type_name())); } } @@ -3446,8 +4189,8 @@ public: @return reference to the element at index @a idx - @throw std::domain_error if JSON is not an array or null; example: - `"cannot use operator[] with string"` + @throw type_error.305 if the JSON value is not an array or null; in that + cases, using the [] operator with an index makes no sense. @complexity Constant if @a idx is in the range of the array. Otherwise linear in `idx - size()`. @@ -3482,8 +4225,8 @@ public: return m_value.array->operator[](idx); } - JSON_THROW( - std::domain_error("cannot use operator[] with " + type_name())); + JSON_THROW(type_error::create(305, "cannot use operator[] with " + + type_name())); } /*! @@ -3495,8 +4238,8 @@ public: @return const reference to the element at index @a idx - @throw std::domain_error if JSON is not an array; example: `"cannot use - operator[] with null"` + @throw type_error.305 if the JSON value is not an array; in that cases, + using the [] operator with an index makes no sense. @complexity Constant. @@ -3513,8 +4256,8 @@ public: return m_value.array->operator[](idx); } - JSON_THROW( - std::domain_error("cannot use operator[] with " + type_name())); + JSON_THROW(type_error::create(305, "cannot use operator[] with " + + type_name())); } /*! @@ -3530,8 +4273,8 @@ public: @return reference to the element at key @a key - @throw std::domain_error if JSON is not an object or null; example: - `"cannot use operator[] with string"` + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -3560,8 +4303,8 @@ public: return m_value.object->operator[](key); } - JSON_THROW( - std::domain_error("cannot use operator[] with " + type_name())); + JSON_THROW(type_error::create(305, "cannot use operator[] with " + + type_name())); } /*! @@ -3580,8 +4323,8 @@ public: @pre The element with key @a key must exist. **This precondition is enforced with an assertion.** - @throw std::domain_error if JSON is not an object; example: `"cannot use - operator[] with null"` + @throw type_error.305 if the JSON value is not an object; in that cases, + using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -3603,8 +4346,8 @@ public: return m_value.object->find(key)->second; } - JSON_THROW( - std::domain_error("cannot use operator[] with " + type_name())); + JSON_THROW(type_error::create(305, "cannot use operator[] with " + + type_name())); } /*! @@ -3620,8 +4363,8 @@ public: @return reference to the element at key @a key - @throw std::domain_error if JSON is not an object or null; example: - `"cannot use operator[] with string"` + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -3655,8 +4398,8 @@ public: @return const reference to the element at key @a key - @throw std::domain_error if JSON is not an object; example: `"cannot use - operator[] with null"` + @throw type_error.305 if the JSON value is not an object; in that cases, + using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -3688,8 +4431,8 @@ public: @return reference to the element at key @a key - @throw std::domain_error if JSON is not an object or null; example: - `"cannot use operator[] with string"` + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -3719,8 +4462,8 @@ public: return m_value.object->operator[](key); } - JSON_THROW( - std::domain_error("cannot use operator[] with " + type_name())); + JSON_THROW(type_error::create(305, "cannot use operator[] with " + + type_name())); } /*! @@ -3739,8 +4482,8 @@ public: @pre The element with key @a key must exist. **This precondition is enforced with an assertion.** - @throw std::domain_error if JSON is not an object; example: `"cannot use - operator[] with null"` + @throw type_error.305 if the JSON value is not an object; in that cases, + using the [] operator with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -3763,8 +4506,8 @@ public: return m_value.object->find(key)->second; } - JSON_THROW( - std::domain_error("cannot use operator[] with " + type_name())); + JSON_THROW(type_error::create(305, "cannot use operator[] with " + + type_name())); } /*! @@ -3777,7 +4520,7 @@ public: @code {.cpp} try { return at(key); - } catch(std::out_of_range) { + } catch(out_of_range) { return default_value; } @endcode @@ -3800,8 +4543,8 @@ public: @return copy of the element at key @a key or @a default_value if @a key is not found - @throw std::domain_error if JSON is not an object; example: `"cannot use - value() with null"` + @throw type_error.306 if the JSON value is not an objec; in that cases, + using `value()` with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -3836,8 +4579,8 @@ public: } else { - JSON_THROW( - std::domain_error("cannot use value() with " + type_name())); + JSON_THROW(type_error::create(306, "cannot use value() with " + + type_name())); } } @@ -3862,7 +4605,7 @@ public: @code {.cpp} try { return at(ptr); - } catch(std::out_of_range) { + } catch(out_of_range) { return default_value; } @endcode @@ -3881,8 +4624,8 @@ public: @return copy of the element at key @a key or @a default_value if @a key is not found - @throw std::domain_error if JSON is not an object; example: `"cannot use - value() with null"` + @throw type_error.306 if the JSON value is not an objec; in that cases, + using `value()` with a key makes no sense. @complexity Logarithmic in the size of the container. @@ -3904,10 +4647,11 @@ public: { // if pointer resolves a value, return it or use default value JSON_TRY { return ptr.get_checked(this); } - JSON_CATCH(std::out_of_range &) { return default_value; } + JSON_CATCH(out_of_range &) { return default_value; } } - JSON_THROW(std::domain_error("cannot use value() with " + type_name())); + JSON_THROW( + type_error::create(306, "cannot use value() with " + type_name())); } /*! @@ -3936,7 +4680,7 @@ public: assertions**). @post The JSON value remains unchanged. - @throw std::out_of_range when called on `null` value + @throw invalid_iterator.214 when called on `null` value @liveexample{The following code shows an example for `front()`.,front} @@ -3973,7 +4717,8 @@ public: assertions**). @post The JSON value remains unchanged. - @throw std::out_of_range when called on `null` value. + @throw invalid_iterator.214 when called on a `null` value. See example + below. @liveexample{The following code shows an example for `back()`.,back} @@ -4017,17 +4762,18 @@ public: @post Invalidates iterators and references at or after the point of the erase, including the `end()` iterator. - @throw std::domain_error if called on a `null` value; example: `"cannot - use erase() with null"` - @throw std::domain_error if called on an iterator which does not belong to - the current JSON value; example: `"iterator does not fit current value"` - @throw std::out_of_range if called on a primitive type with invalid + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.202 if called on an iterator which does not belong + to the current JSON value; example: `"iterator does not fit current + value"` + @throw invalid_iterator.205 if called on a primitive type with invalid iterator (i.e., any iterator which is not `begin()`); example: `"iterator out of range"` @complexity The complexity depends on the type: - objects: amortized constant - - arrays: linear in distance between pos and the end of the container + - arrays: linear in distance between @a pos and the end of the container - strings: linear in the length of the string - other types: constant @@ -4056,8 +4802,8 @@ public: // make sure iterator fits the current value if (this != pos.m_object) { - JSON_THROW( - std::domain_error("iterator does not fit current value")); + JSON_THROW(invalid_iterator::create( + 202, "iterator does not fit current value")); } IteratorType result = end(); @@ -4072,7 +4818,8 @@ public: { if (not pos.m_it.primitive_iterator.is_begin()) { - JSON_THROW(std::out_of_range("iterator out of range")); + JSON_THROW( + invalid_iterator::create(205, "iterator out of range")); } if (is_string()) @@ -4104,8 +4851,8 @@ public: default: { - JSON_THROW( - std::domain_error("cannot use erase() with " + type_name())); + JSON_THROW(type_error::create(307, "cannot use erase() with " + + type_name())); } } @@ -4132,11 +4879,11 @@ public: @post Invalidates iterators and references at or after the point of the erase, including the `end()` iterator. - @throw std::domain_error if called on a `null` value; example: `"cannot - use erase() with null"` - @throw std::domain_error if called on iterators which does not belong to - the current JSON value; example: `"iterators do not fit current value"` - @throw std::out_of_range if called on a primitive type with invalid + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.203 if called on iterators which does not belong + to the current JSON value; example: `"iterators do not fit current value"` + @throw invalid_iterator.204 if called on a primitive type with invalid iterators (i.e., if `first != begin()` and `last != end()`); example: `"iterators out of range"` @@ -4171,7 +4918,8 @@ public: // make sure iterator fits the current value if (this != first.m_object or this != last.m_object) { - JSON_THROW(std::domain_error("iterators do not fit current value")); + JSON_THROW(invalid_iterator::create( + 203, "iterators do not fit current value")); } IteratorType result = end(); @@ -4187,7 +4935,8 @@ public: if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) { - JSON_THROW(std::out_of_range("iterators out of range")); + JSON_THROW( + invalid_iterator::create(204, "iterators out of range")); } if (is_string()) @@ -4219,8 +4968,8 @@ public: default: { - JSON_THROW( - std::domain_error("cannot use erase() with " + type_name())); + JSON_THROW(type_error::create(307, "cannot use erase() with " + + type_name())); } } @@ -4241,7 +4990,7 @@ public: @post References and iterators to the erased elements are invalidated. Other references and iterators are not affected. - @throw std::domain_error when called on a type other than JSON object; + @throw type_error.307 when called on a type other than JSON object; example: `"cannot use erase() with null"` @complexity `log(size()) + count(key)` @@ -4264,7 +5013,8 @@ public: return m_value.object->erase(key); } - JSON_THROW(std::domain_error("cannot use erase() with " + type_name())); + JSON_THROW( + type_error::create(307, "cannot use erase() with " + type_name())); } /*! @@ -4274,9 +5024,9 @@ public: @param[in] idx index of the element to remove - @throw std::domain_error when called on a type other than JSON array; + @throw type_error.307 when called on a type other than JSON object; example: `"cannot use erase() with null"` - @throw std::out_of_range when `idx >= size()`; example: `"array index 17 + @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 is out of range"` @complexity Linear in distance between @a idx and the end of the container. @@ -4298,8 +5048,9 @@ public: { if (idx >= size()) { - JSON_THROW(std::out_of_range( - "array index " + std::to_string(idx) + " is out of range")); + JSON_THROW(out_of_range::create(401, "array index " + + std::to_string(idx) + + " is out of range")); } m_value.array->erase(m_value.array->begin() + @@ -4307,8 +5058,8 @@ public: } else { - JSON_THROW( - std::domain_error("cannot use erase() with " + type_name())); + JSON_THROW(type_error::create(307, "cannot use erase() with " + + type_name())); } } @@ -4990,7 +5741,7 @@ public: @param[in] val the value to add to the JSON array - @throw std::domain_error when called on a type other than JSON array or + @throw type_error.308 when called on a type other than JSON array or null; example: `"cannot use push_back() with number"` @complexity Amortized constant. @@ -5006,8 +5757,8 @@ public: // push_back only works for null objects or arrays if (not(is_null() or is_array())) { - JSON_THROW(std::domain_error("cannot use push_back() with " + - type_name())); + JSON_THROW(type_error::create(308, "cannot use push_back() with " + + type_name())); } // transform null object into an array @@ -5043,8 +5794,8 @@ public: // push_back only works for null objects or arrays if (not(is_null() or is_array())) { - JSON_THROW(std::domain_error("cannot use push_back() with " + - type_name())); + JSON_THROW(type_error::create(308, "cannot use push_back() with " + + type_name())); } // transform null object into an array @@ -5078,7 +5829,7 @@ public: @param[in] val the value to add to the JSON object - @throw std::domain_error when called on a type other than JSON object or + @throw type_error.308 when called on a type other than JSON object or null; example: `"cannot use push_back() with number"` @complexity Logarithmic in the size of the container, O(log(`size()`)). @@ -5094,8 +5845,8 @@ public: // push_back only works for null objects or objects if (not(is_null() or is_object())) { - JSON_THROW(std::domain_error("cannot use push_back() with " + - type_name())); + JSON_THROW(type_error::create(308, "cannot use push_back() with " + + type_name())); } // transform null object into an object @@ -5133,7 +5884,7 @@ public: @ref push_back(const typename object_t::value_type&). Otherwise, @a init is converted to a JSON value and added using @ref push_back(basic_json&&). - @param init an initializer list + @param[in] init an initializer list @complexity Linear in the size of the initializer list @a init. @@ -5178,7 +5929,7 @@ public: @param[in] args arguments to forward to a constructor of @ref basic_json @tparam Args compatible types to create a @ref basic_json object - @throw std::domain_error when called on a type other than JSON array or + @throw type_error.311 when called on a type other than JSON array or null; example: `"cannot use emplace_back() with number"` @complexity Amortized constant. @@ -5195,8 +5946,8 @@ public: // emplace_back only works for null objects or arrays if (not(is_null() or is_array())) { - JSON_THROW(std::domain_error("cannot use emplace_back() with " + - type_name())); + JSON_THROW(type_error::create( + 311, "cannot use emplace_back() with " + type_name())); } // transform null object into an array @@ -5226,7 +5977,7 @@ public: already-existing element if no insertion happened, and a bool denoting whether the insertion took place. - @throw std::domain_error when called on a type other than JSON object or + @throw type_error.311 when called on a type other than JSON object or null; example: `"cannot use emplace() with number"` @complexity Logarithmic in the size of the container, O(log(`size()`)). @@ -5244,8 +5995,8 @@ public: // emplace only works for null objects or arrays if (not(is_null() or is_object())) { - JSON_THROW( - std::domain_error("cannot use emplace() with " + type_name())); + JSON_THROW(type_error::create(311, "cannot use emplace() with " + + type_name())); } // transform null object into an object @@ -5276,12 +6027,12 @@ public: @param[in] val element to insert @return iterator pointing to the inserted @a val. - @throw std::domain_error if called on JSON values other than arrays; + @throw type_error.309 if called on JSON values other than arrays; example: `"cannot use insert() with string"` - @throw std::domain_error if @a pos is not an iterator of *this; example: - `"iterator does not fit current value"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` - @complexity Constant plus linear in the distance between pos and end of + @complexity Constant plus linear in the distance between @a pos and end of the container. @liveexample{The example shows how `insert()` is used.,insert} @@ -5296,8 +6047,8 @@ public: // check if iterator pos fits to this JSON value if (pos.m_object != this) { - JSON_THROW( - std::domain_error("iterator does not fit current value")); + JSON_THROW(invalid_iterator::create( + 202, "iterator does not fit current value")); } // insert to array and return iterator @@ -5308,7 +6059,7 @@ public: } JSON_THROW( - std::domain_error("cannot use insert() with " + type_name())); + type_error::create(309, "cannot use insert() with " + type_name())); } /*! @@ -5332,10 +6083,10 @@ public: @return iterator pointing to the first element inserted, or @a pos if `cnt==0` - @throw std::domain_error if called on JSON values other than arrays; - example: `"cannot use insert() with string"` - @throw std::domain_error if @a pos is not an iterator of *this; example: - `"iterator does not fit current value"` + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` @complexity Linear in @a cnt plus linear in the distance between @a pos and end of the container. @@ -5352,8 +6103,8 @@ public: // check if iterator pos fits to this JSON value if (pos.m_object != this) { - JSON_THROW( - std::domain_error("iterator does not fit current value")); + JSON_THROW(invalid_iterator::create( + 202, "iterator does not fit current value")); } // insert to array and return iterator @@ -5364,7 +6115,7 @@ public: } JSON_THROW( - std::domain_error("cannot use insert() with " + type_name())); + type_error::create(309, "cannot use insert() with " + type_name())); } /*! @@ -5377,13 +6128,13 @@ public: @param[in] first begin of the range of elements to insert @param[in] last end of the range of elements to insert - @throw std::domain_error if called on JSON values other than arrays; - example: `"cannot use insert() with string"` - @throw std::domain_error if @a pos is not an iterator of *this; example: - `"iterator does not fit current value"` - @throw std::domain_error if @a first and @a last do not belong to the same - JSON value; example: `"iterators do not fit"` - @throw std::domain_error if @a first or @a last are iterators into + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + @throw invalid_iterator.211 if @a first or @a last are iterators into container for which insert is called; example: `"passed iterators may not belong to container"` @@ -5403,27 +6154,27 @@ public: // insert only works for arrays if (not is_array()) { - JSON_THROW( - std::domain_error("cannot use insert() with " + type_name())); + JSON_THROW(type_error::create(309, "cannot use insert() with " + + type_name())); } // check if iterator pos fits to this JSON value if (pos.m_object != this) { - JSON_THROW( - std::domain_error("iterator does not fit current value")); + JSON_THROW(invalid_iterator::create( + 202, "iterator does not fit current value")); } // check if range iterators belong to the same JSON object if (first.m_object != last.m_object) { - JSON_THROW(std::domain_error("iterators do not fit")); + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); } if (first.m_object == this or last.m_object == this) { - JSON_THROW(std::domain_error( - "passed iterators may not belong to container")); + JSON_THROW(invalid_iterator::create( + 211, "passed iterators may not belong to container")); } // insert to array and return iterator @@ -5443,10 +6194,10 @@ public: the end() iterator @param[in] ilist initializer list to insert the values from - @throw std::domain_error if called on JSON values other than arrays; - example: `"cannot use insert() with string"` - @throw std::domain_error if @a pos is not an iterator of *this; example: - `"iterator does not fit current value"` + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` @return iterator pointing to the first element inserted, or @a pos if `ilist` is empty @@ -5463,15 +6214,15 @@ public: // insert only works for arrays if (not is_array()) { - JSON_THROW( - std::domain_error("cannot use insert() with " + type_name())); + JSON_THROW(type_error::create(309, "cannot use insert() with " + + type_name())); } // check if iterator pos fits to this JSON value if (pos.m_object != this) { - JSON_THROW( - std::domain_error("iterator does not fit current value")); + JSON_THROW(invalid_iterator::create( + 202, "iterator does not fit current value")); } // insert to array and return iterator @@ -5519,8 +6270,8 @@ public: @param[in,out] other array to exchange the contents with - @throw std::domain_error when JSON value is not an array; example: - `"cannot use swap() with string"` + @throw type_error.310 when JSON value is not an array; example: `"cannot + use swap() with string"` @complexity Constant. @@ -5538,8 +6289,8 @@ public: } else { - JSON_THROW( - std::domain_error("cannot use swap() with " + type_name())); + JSON_THROW(type_error::create(310, "cannot use swap() with " + + type_name())); } } @@ -5553,7 +6304,7 @@ public: @param[in,out] other object to exchange the contents with - @throw std::domain_error when JSON value is not an object; example: + @throw type_error.310 when JSON value is not an object; example: `"cannot use swap() with string"` @complexity Constant. @@ -5572,8 +6323,8 @@ public: } else { - JSON_THROW( - std::domain_error("cannot use swap() with " + type_name())); + JSON_THROW(type_error::create(310, "cannot use swap() with " + + type_name())); } } @@ -5587,7 +6338,7 @@ public: @param[in,out] other string to exchange the contents with - @throw std::domain_error when JSON value is not a string; example: `"cannot + @throw type_error.310 when JSON value is not a string; example: `"cannot use swap() with boolean"` @complexity Constant. @@ -5606,78 +6357,230 @@ public: } else { - JSON_THROW( - std::domain_error("cannot use swap() with " + type_name())); + JSON_THROW(type_error::create(310, "cannot use swap() with " + + type_name())); + } + } + + /// @} + +public: + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same according to their respective + `operator==`. + - Integer and floating-point numbers are automatically converted before + comparison. Floating-point numbers are compared indirectly: two + floating-point numbers `f1` and `f2` are considered equal if neither + `f1 > f2` nor `f2 > f1` holds. Note than two NaN values are always + treated as unequal. + - Two JSON null values are equal. + + @note NaN values never compare equal to themselves or to other NaN values. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + { + return *lhs.m_value.array == *rhs.m_value.array; + } + case value_t::object: + { + return *lhs.m_value.object == *rhs.m_value.object; + } + case value_t::null: + { + return true; + } + case value_t::string: + { + return *lhs.m_value.string == *rhs.m_value.string; + } + case value_t::boolean: + { + return lhs.m_value.boolean == rhs.m_value.boolean; + } + case value_t::number_integer: + { + return lhs.m_value.number_integer == rhs.m_value.number_integer; + } + case value_t::number_unsigned: + { + return lhs.m_value.number_unsigned == + rhs.m_value.number_unsigned; + } + case value_t::number_float: + { + return lhs.m_value.number_float == rhs.m_value.number_float; + } + default: + { + return false; + } + } + } + else if (lhs_type == value_t::number_integer and + rhs_type == value_t::number_float) + { + return static_cast<number_float_t>(lhs.m_value.number_integer) == + rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and + rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float == + static_cast<number_float_t>(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and + rhs_type == value_t::number_float) + { + return static_cast<number_float_t>(lhs.m_value.number_unsigned) == + rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and + rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float == + static_cast<number_float_t>(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and + rhs_type == value_t::number_integer) + { + return static_cast<number_integer_t>(lhs.m_value.number_unsigned) == + rhs.m_value.number_integer; + } + else if (lhs_type == value_t::number_integer and + rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer == + static_cast<number_integer_t>(rhs.m_value.number_unsigned); } + + return false; + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template <typename ScalarType, + typename std::enable_if<std::is_scalar<ScalarType>::value, + int>::type = 0> + friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs == basic_json(rhs)); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template <typename ScalarType, + typename std::enable_if<std::is_scalar<ScalarType>::value, + int>::type = 0> + friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) == rhs); } - /// @} + /*! + @brief comparison: not equal - ////////////////////////////////////////// - // lexicographical comparison operators // - ////////////////////////////////////////// + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. - /// @name lexicographical comparison operators - /// @{ + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal -private: - /*! - @brief comparison operator for JSON types + @complexity Linear. - Returns an ordering that is similar to Python: - - order: null < boolean < number < object < array < string - - furthermore, each type is not smaller than itself + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} @since version 1.0.0 */ - friend bool operator<(const value_t lhs, const value_t rhs) noexcept + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { - static constexpr std::array<uint8_t, 8> order = {{ - 0, // null - 3, // object - 4, // array - 5, // string - 1, // boolean - 2, // integer - 2, // unsigned - 2, // float - }}; + return not(lhs == rhs); + } - // discarded values are not comparable - if (lhs == value_t::discarded or rhs == value_t::discarded) - { - return false; - } + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template <typename ScalarType, + typename std::enable_if<std::is_scalar<ScalarType>::value, + int>::type = 0> + friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs != basic_json(rhs)); + } - return order[static_cast<std::size_t>(lhs)] < - order[static_cast<std::size_t>(rhs)]; + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template <typename ScalarType, + typename std::enable_if<std::is_scalar<ScalarType>::value, + int>::type = 0> + friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) != rhs); } -public: /*! - @brief comparison: equal + @brief comparison: less than - Compares two JSON values for equality according to the following rules: - - Two JSON values are equal if (1) they are from the same type and (2) - their stored values are the same. + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. - Integer and floating-point numbers are automatically converted before - comparison. Floating-point numbers are compared indirectly: two - floating-point numbers `f1` and `f2` are considered equal if neither - `f1 > f2` nor `f2 > f1` holds. - - Two JSON null values are equal. + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider - @return whether the values @a lhs and @a rhs are equal + @return whether @a lhs is less than @a rhs @complexity Linear. @liveexample{The example demonstrates comparing several JSON - types.,operator__equal} + types.,operator__less} @since version 1.0.0 */ - friend bool operator==(const_reference lhs, const_reference rhs) noexcept + friend bool operator<(const_reference lhs, const_reference rhs) noexcept { const auto lhs_type = lhs.type(); const auto rhs_type = rhs.type(); @@ -5688,36 +6591,36 @@ public: { case value_t::array: { - return *lhs.m_value.array == *rhs.m_value.array; + return *lhs.m_value.array < *rhs.m_value.array; } case value_t::object: { - return *lhs.m_value.object == *rhs.m_value.object; + return *lhs.m_value.object < *rhs.m_value.object; } case value_t::null: { - return true; + return false; } case value_t::string: { - return *lhs.m_value.string == *rhs.m_value.string; + return *lhs.m_value.string < *rhs.m_value.string; } case value_t::boolean: { - return lhs.m_value.boolean == rhs.m_value.boolean; + return lhs.m_value.boolean < rhs.m_value.boolean; } case value_t::number_integer: { - return lhs.m_value.number_integer == rhs.m_value.number_integer; + return lhs.m_value.number_integer < rhs.m_value.number_integer; } case value_t::number_unsigned: { - return lhs.m_value.number_unsigned == + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; } case value_t::number_float: { - return lhs.m_value.number_float == rhs.m_value.number_float; + return lhs.m_value.number_float < rhs.m_value.number_float; } default: { @@ -5728,318 +6631,815 @@ public: else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) { - return static_cast<number_float_t>(lhs.m_value.number_integer) == + return static_cast<number_float_t>(lhs.m_value.number_integer) < rhs.m_value.number_float; } else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) { - return lhs.m_value.number_float == + return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_integer); } else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) { - return static_cast<number_float_t>(lhs.m_value.number_unsigned) == + return static_cast<number_float_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_float; } else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) { - return lhs.m_value.number_float == + return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_unsigned); } - else if (lhs_type == value_t::number_unsigned and - rhs_type == value_t::number_integer) - { - return static_cast<number_integer_t>(lhs.m_value.number_unsigned) == - rhs.m_value.number_integer; - } else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) { - return lhs.m_value.number_integer == + return lhs.m_value.number_integer < static_cast<number_integer_t>(rhs.m_value.number_unsigned); } + else if (lhs_type == value_t::number_unsigned and + rhs_type == value_t::number_integer) + { + return static_cast<number_integer_t>(lhs.m_value.number_unsigned) < + rhs.m_value.number_integer; + } - return false; + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); } /*! - @brief comparison: equal + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template <typename ScalarType, + typename std::enable_if<std::is_scalar<ScalarType>::value, + int>::type = 0> + friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs < basic_json(rhs)); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template <typename ScalarType, + typename std::enable_if<std::is_scalar<ScalarType>::value, + int>::type = 0> + friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) < rhs); + } - The functions compares the given JSON value against a null pointer. As the - null pointer can be used to initialize a JSON value to null, a comparison - of JSON value @a v with a null pointer should be equivalent to call - `v.is_null()`. + /*! + @brief comparison: less than or equal - @param[in] v JSON value to consider - @return whether @a v is null + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. - @complexity Constant. + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. - @liveexample{The example compares several JSON types to the null pointer. - ,operator__equal__nullptr_t} + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} @since version 1.0.0 */ - friend bool operator==(const_reference v, std::nullptr_t) noexcept + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { - return v.is_null(); + return not(rhs < lhs); } /*! - @brief comparison: equal - @copydoc operator==(const_reference, std::nullptr_t) + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) */ - friend bool operator==(std::nullptr_t, const_reference v) noexcept + template <typename ScalarType, + typename std::enable_if<std::is_scalar<ScalarType>::value, + int>::type = 0> + friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept { - return v.is_null(); + return (lhs <= basic_json(rhs)); } /*! - @brief comparison: not equal + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template <typename ScalarType, + typename std::enable_if<std::is_scalar<ScalarType>::value, + int>::type = 0> + friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) <= rhs); + } - Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. @param[in] lhs first JSON value to consider @param[in] rhs second JSON value to consider - @return whether the values @a lhs and @a rhs are not equal + @return whether @a lhs is greater than to @a rhs @complexity Linear. @liveexample{The example demonstrates comparing several JSON - types.,operator__notequal} + types.,operator__lessequal} @since version 1.0.0 */ - friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + friend bool operator>(const_reference lhs, const_reference rhs) noexcept { - return not(lhs == rhs); + return not(lhs <= rhs); } /*! - @brief comparison: not equal + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template <typename ScalarType, + typename std::enable_if<std::is_scalar<ScalarType>::value, + int>::type = 0> + friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs > basic_json(rhs)); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template <typename ScalarType, + typename std::enable_if<std::is_scalar<ScalarType>::value, + int>::type = 0> + friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) > rhs); + } - The functions compares the given JSON value against a null pointer. As the - null pointer can be used to initialize a JSON value to null, a comparison - of JSON value @a v with a null pointer should be equivalent to call - `not v.is_null()`. + /*! + @brief comparison: greater than or equal - @param[in] v JSON value to consider - @return whether @a v is not null + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. - @complexity Constant. + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. - @liveexample{The example compares several JSON types to the null pointer. - ,operator__notequal__nullptr_t} + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} @since version 1.0.0 */ - friend bool operator!=(const_reference v, std::nullptr_t) noexcept + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { - return not v.is_null(); + return not(lhs < rhs); } /*! - @brief comparison: not equal - @copydoc operator!=(const_reference, std::nullptr_t) + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) */ - friend bool operator!=(std::nullptr_t, const_reference v) noexcept + template <typename ScalarType, + typename std::enable_if<std::is_scalar<ScalarType>::value, + int>::type = 0> + friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept { - return not v.is_null(); + return (lhs >= basic_json(rhs)); } /*! - @brief comparison: less than + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template <typename ScalarType, + typename std::enable_if<std::is_scalar<ScalarType>::value, + int>::type = 0> + friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) >= rhs); + } - Compares whether one JSON value @a lhs is less than another JSON value @a - rhs according to the following rules: - - If @a lhs and @a rhs have the same type, the values are compared using - the default `<` operator. - - Integer and floating-point numbers are automatically converted before - comparison - - In case @a lhs and @a rhs have different types, the values are ignored - and the order of the types is considered, see - @ref operator<(const value_t, const value_t). + /// @} - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is less than @a rhs + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + +private: + /*! + @brief wrapper around the serialization functions + */ + class serializer + { + private: + serializer(const serializer &) = delete; + serializer &operator=(const serializer &) = delete; + + public: + /*! + @param[in] s output stream to serialize to + */ + serializer(std::ostream &s) + : o(s), loc(std::localeconv()), + thousands_sep(!loc->thousands_sep ? '\0' : loc->thousands_sep[0]), + decimal_point(!loc->decimal_point ? '\0' : loc->decimal_point[0]) + { + } + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and + organizes the serialization internally. The indentation level is + propagated as additional parameter. In case of arrays and objects, the + function is called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used + internally) + */ + void dump(const basic_json &val, const bool pretty_print, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_type) + { + case value_t::object: + { + if (val.m_value.object->empty()) + { + o.write("{}", 2); + return; + } + + if (pretty_print) + { + o.write("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (indent_string.size() < new_indent) + { + indent_string.resize(new_indent, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (size_t cnt = 0; cnt < val.m_value.object->size() - 1; + ++cnt, ++i) + { + o.write(indent_string.c_str(), + static_cast<std::streamsize>(new_indent)); + o.put('\"'); + dump_escaped(i->first); + o.write("\": ", 3); + dump(i->second, true, indent_step, new_indent); + o.write(",\n", 2); + } + + // last element + assert(i != val.m_value.object->cend()); + o.write(indent_string.c_str(), + static_cast<std::streamsize>(new_indent)); + o.put('\"'); + dump_escaped(i->first); + o.write("\": ", 3); + dump(i->second, true, indent_step, new_indent); + + o.put('\n'); + o.write(indent_string.c_str(), + static_cast<std::streamsize>(current_indent)); + o.put('}'); + } + else + { + o.put('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (size_t cnt = 0; cnt < val.m_value.object->size() - 1; + ++cnt, ++i) + { + o.put('\"'); + dump_escaped(i->first); + o.write("\":", 2); + dump(i->second, false, indent_step, current_indent); + o.put(','); + } + + // last element + assert(i != val.m_value.object->cend()); + o.put('\"'); + dump_escaped(i->first); + o.write("\":", 2); + dump(i->second, false, indent_step, current_indent); + + o.put('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_value.array->empty()) + { + o.write("[]", 2); + return; + } + + if (pretty_print) + { + o.write("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (indent_string.size() < new_indent) + { + indent_string.resize(new_indent, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o.write(indent_string.c_str(), + static_cast<std::streamsize>(new_indent)); + dump(*i, true, indent_step, new_indent); + o.write(",\n", 2); + } + + // last element + assert(not val.m_value.array->empty()); + o.write(indent_string.c_str(), + static_cast<std::streamsize>(new_indent)); + dump(val.m_value.array->back(), true, indent_step, + new_indent); + + o.put('\n'); + o.write(indent_string.c_str(), + static_cast<std::streamsize>(current_indent)); + o.put(']'); + } + else + { + o.put('['); - @complexity Linear. + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, false, indent_step, current_indent); + o.put(','); + } - @liveexample{The example demonstrates comparing several JSON - types.,operator__less} + // last element + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), false, indent_step, + current_indent); - @since version 1.0.0 - */ - friend bool operator<(const_reference lhs, const_reference rhs) noexcept - { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); + o.put(']'); + } - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - { - return *lhs.m_value.array < *rhs.m_value.array; - } - case value_t::object: - { - return *lhs.m_value.object < *rhs.m_value.object; - } - case value_t::null: - { - return false; + return; } + case value_t::string: { - return *lhs.m_value.string < *rhs.m_value.string; + o.put('\"'); + dump_escaped(*val.m_value.string); + o.put('\"'); + return; } + case value_t::boolean: { - return lhs.m_value.boolean < rhs.m_value.boolean; + if (val.m_value.boolean) + { + o.write("true", 4); + } + else + { + o.write("false", 5); + } + return; } + case value_t::number_integer: { - return lhs.m_value.number_integer < rhs.m_value.number_integer; + dump_integer(val.m_value.number_integer); + return; } + case value_t::number_unsigned: { - return lhs.m_value.number_unsigned < - rhs.m_value.number_unsigned; + dump_integer(val.m_value.number_unsigned); + return; } + case value_t::number_float: { - return lhs.m_value.number_float < rhs.m_value.number_float; + dump_float(val.m_value.number_float); + return; } - default: + + case value_t::discarded: { - return false; + o.write("<discarded>", 11); + return; + } + + case value_t::null: + { + o.write("null", 4); + return; } } } - else if (lhs_type == value_t::number_integer and - rhs_type == value_t::number_float) - { - return static_cast<number_float_t>(lhs.m_value.number_integer) < - rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float and - rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float < - static_cast<number_float_t>(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned and - rhs_type == value_t::number_float) - { - return static_cast<number_float_t>(lhs.m_value.number_unsigned) < - rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float and - rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float < - static_cast<number_float_t>(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_integer and - rhs_type == value_t::number_unsigned) + + private: + /*! + @brief calculates the extra space to escape a JSON string + + @param[in] s the string to escape + @return the number of characters required to escape string @a s + + @complexity Linear in the length of string @a s. + */ + static std::size_t extra_space(const string_t &s) noexcept { - return lhs.m_value.number_integer < - static_cast<number_integer_t>(rhs.m_value.number_unsigned); + return std::accumulate( + s.begin(), s.end(), size_t{}, + [](size_t res, typename string_t::value_type c) { + switch (c) + { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + { + // from c (1 byte) to \x (2 bytes) + return res + 1; + } + + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x0b: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1a: + case 0x1b: + case 0x1c: + case 0x1d: + case 0x1e: + case 0x1f: + { + // from c (1 byte) to \uxxxx (6 bytes) + return res + 5; + } + + default: + { + return res; + } + } + }); } - else if (lhs_type == value_t::number_unsigned and - rhs_type == value_t::number_integer) + + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence + of an escape character (backslash) and another character and other + control characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t &s) const { - return static_cast<number_integer_t>(lhs.m_value.number_unsigned) < - rhs.m_value.number_integer; + const auto space = extra_space(s); + if (space == 0) + { + o.write(s.c_str(), static_cast<std::streamsize>(s.size())); + return; + } + + // create a result string of necessary size + string_t result(s.size() + space, '\\'); + std::size_t pos = 0; + + for (const auto &c : s) + { + switch (c) + { + // quotation mark (0x22) + case '"': + { + result[pos + 1] = '"'; + pos += 2; + break; + } + + // reverse solidus (0x5c) + case '\\': + { + // nothing to change + pos += 2; + break; + } + + // backspace (0x08) + case '\b': + { + result[pos + 1] = 'b'; + pos += 2; + break; + } + + // formfeed (0x0c) + case '\f': + { + result[pos + 1] = 'f'; + pos += 2; + break; + } + + // newline (0x0a) + case '\n': + { + result[pos + 1] = 'n'; + pos += 2; + break; + } + + // carriage return (0x0d) + case '\r': + { + result[pos + 1] = 'r'; + pos += 2; + break; + } + + // horizontal tab (0x09) + case '\t': + { + result[pos + 1] = 't'; + pos += 2; + break; + } + + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x0b: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1a: + case 0x1b: + case 0x1c: + case 0x1d: + case 0x1e: + case 0x1f: + { + // convert a number 0..15 to its hex representation + // (0..f) + static const char hexify[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + // print character c as \uxxxx + for (const char m : + {'u', '0', '0', hexify[c >> 4], hexify[c & 0x0f]}) + { + result[++pos] = m; + } + + ++pos; + break; + } + + default: + { + // all other characters are added as-is + result[pos++] = c; + break; + } + } + } + + assert(pos == s.size() + space); + o.write(result.c_str(), + static_cast<std::streamsize>(result.size())); } - // We only reach this line if we cannot compare values. In that case, - // we compare types. Note we have to call the operator explicitly, - // because MSVC has problems otherwise. - return operator<(lhs_type, rhs_type); - } + /*! + @brief dump an integer - /*! - @brief comparison: less than or equal + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. - Compares whether one JSON value @a lhs is less than or equal to another - JSON value by calculating `not (rhs < lhs)`. + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template <typename NumberType, + detail::enable_if_t< + std::is_same<NumberType, number_unsigned_t>::value or + std::is_same<NumberType, number_integer_t>::value, + int> = 0> + void dump_integer(NumberType x) + { + // special case for "0" + if (x == 0) + { + o.put('0'); + return; + } - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is less than or equal to @a rhs + const bool is_negative = x < 0; + size_t i = 0; - @complexity Linear. + // spare 1 byte for '\0' + while (x != 0 and i < number_buffer.size() - 1) + { + const auto digit = std::labs(static_cast<long>(x % 10)); + number_buffer[i++] = static_cast<char>('0' + digit); + x /= 10; + } - @liveexample{The example demonstrates comparing several JSON - types.,operator__greater} + // make sure the number has been processed completely + assert(x == 0); - @since version 1.0.0 - */ - friend bool operator<=(const_reference lhs, const_reference rhs) noexcept - { - return not(rhs < lhs); - } + if (is_negative) + { + // make sure there is capacity for the '-' + assert(i < number_buffer.size() - 2); + number_buffer[i++] = '-'; + } - /*! - @brief comparison: greater than + std::reverse(number_buffer.begin(), number_buffer.begin() + i); + o.write(number_buffer.data(), static_cast<std::streamsize>(i)); + } - Compares whether one JSON value @a lhs is greater than another - JSON value by calculating `not (lhs <= rhs)`. + /*! + @brief dump a floating-point number - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is greater than to @a rhs + Dump a given floating-point number to output stream @a o. Works + internally with @a number_buffer. - @complexity Linear. + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (not std::isfinite(x) or std::isnan(x)) + { + o.write("null", 4); + return; + } - @liveexample{The example demonstrates comparing several JSON - types.,operator__lessequal} + // special case for 0.0 and -0.0 + if (x == 0) + { + if (std::signbit(x)) + { + o.write("-0.0", 4); + } + else + { + o.write("0.0", 3); + } + return; + } - @since version 1.0.0 - */ - friend bool operator>(const_reference lhs, const_reference rhs) noexcept - { - return not(lhs <= rhs); - } + // get number of digits for a text -> float -> text round-trip + static constexpr auto d = + std::numeric_limits<number_float_t>::digits10; - /*! - @brief comparison: greater than or equal + // the actual conversion + std::ptrdiff_t len = snprintf(number_buffer.data(), + number_buffer.size(), "%.*g", d, x); - Compares whether one JSON value @a lhs is greater than or equal to another - JSON value by calculating `not (lhs < rhs)`. + // negative value indicates an error + assert(len > 0); + // check if buffer was large enough + assert(static_cast<size_t>(len) < number_buffer.size()); - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is greater than or equal to @a rhs + // erase thousands separator + if (thousands_sep != '\0') + { + const auto end = + std::remove(number_buffer.begin(), + number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + assert((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } - @complexity Linear. + // convert decimal point to '.' + if (decimal_point != '\0' and decimal_point != '.') + { + for (auto &c : number_buffer) + { + if (c == decimal_point) + { + c = '.'; + break; + } + } + } - @liveexample{The example demonstrates comparing several JSON - types.,operator__greaterequal} + o.write(number_buffer.data(), static_cast<std::streamsize>(len)); + + // determine if need to append ".0" + const bool value_is_int_like = std::none_of( + number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) { return c == '.' or c == 'e'; }); + + if (value_is_int_like) + { + o.write(".0", 2); + } + } - @since version 1.0.0 - */ - friend bool operator>=(const_reference lhs, const_reference rhs) noexcept - { - return not(lhs < rhs); - } + private: + /// the output of the serializer + std::ostream &o; - /// @} + /// a (hopefully) large enough character buffer + std::array<char, 64> number_buffer{{}}; - /////////////////// - // serialization // - /////////////////// + /// the locale + const std::lconv *loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; - /// @name serialization - /// @{ + /// the indentation string + string_t indent_string = string_t(512, ' '); + }; +public: /*! @brief serialize to stream @@ -6050,10 +7450,6 @@ public: `std::setw(4)` on @a o sets the indentation level to `4` and the serialization result is the same as calling `dump(4)`. - @note During serializaion, the locale and the precision of the output - stream @a o are changed. The original values are restored when the - function returns. - @param[in,out] o stream to serialize to @param[in] j JSON value to serialize @@ -6075,30 +7471,20 @@ public: // reset width to 0 for subsequent calls to this stream o.width(0); - // fix locale problems - const auto old_locale = o.imbue(std::locale::classic()); - // set precision - - // 6, 15 or 16 digits of precision allows round-trip IEEE 754 - // string->float->string, string->double->string or string->long - // double->string; to be safe, we read this value from - // std::numeric_limits<number_float_t>::digits10 - const auto old_precision = - o.precision(std::numeric_limits<double>::digits10); - // do the actual serialization - j.dump(o, pretty_print, static_cast<unsigned int>(indentation)); - - // reset locale and precision - o.imbue(old_locale); - o.precision(old_precision); + serializer s(o); + s.dump(j, pretty_print, static_cast<unsigned int>(indentation)); return o; } /*! @brief serialize to stream - @copydoc operator<<(std::ostream&, const basic_json&) + @deprecated This stream operator is deprecated and will be removed in a + future version of the library. Please use + @ref std::ostream& operator<<(std::ostream&, const basic_json&) + instead; that is, replace calls like `j >> o;` with `o << j;`. */ + JSON_DEPRECATED friend std::ostream &operator>>(const basic_json &j, std::ostream &o) { return o << j; @@ -6129,6 +7515,11 @@ public: @return result of the deserialization + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. @@ -6158,6 +7549,10 @@ public: @return result of the deserialization + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. @@ -6196,6 +7591,11 @@ public: @return result of the deserialization + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + @throw parse_error.111 if input stream is in a bad state + @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. @@ -6255,6 +7655,10 @@ public: @return result of the deserialization + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. @@ -6332,6 +7736,10 @@ public: @return result of the deserialization + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. @@ -6359,6 +7767,20 @@ public: return parse(std::begin(c), std::end(c), cb); } + /*! + @brief deserialize from stream + @deprecated This stream operator is deprecated and will be removed in a + future version of the library. Please use + @ref std::istream& operator>>(std::istream&, basic_json&) + instead; that is, replace calls like `j << i;` with `i >> j;`. + */ + JSON_DEPRECATED + friend std::istream &operator<<(basic_json &j, std::istream &i) + { + j = parser(i).parse(); + return i; + } + /*! @brief deserialize from stream @@ -6367,7 +7789,10 @@ public: @param[in,out] i input stream to read a serialized JSON value from @param[in,out] j JSON value to write the deserialized input to - @throw std::invalid_argument in case of parse errors + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + @throw parse_error.111 if input stream is in a bad state @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. @@ -6382,16 +7807,6 @@ public: @since version 1.0.0 */ - friend std::istream &operator<<(basic_json &j, std::istream &i) - { - j = parser(i).parse(); - return i; - } - - /*! - @brief deserialize from stream - @copydoc operator<<(basic_json&, std::istream&) - */ friend std::istream &operator>>(std::istream &i, basic_json &j) { j = parser(i).parse(); @@ -6408,6 +7823,11 @@ public: /// @{ private: + /*! + @note Some code in the switch cases has been copied, because otherwise + copilers would complain about implicit fallthrough and there is no + portable attribute to mute such warnings. + */ template <typename T> static void add_to_vector(std::vector<uint8_t> &vec, size_t bytes, const T number) @@ -6418,24 +7838,35 @@ private: { case 8: { - vec.push_back(static_cast<uint8_t>((number >> 070) & 0xff)); - vec.push_back(static_cast<uint8_t>((number >> 060) & 0xff)); - vec.push_back(static_cast<uint8_t>((number >> 050) & 0xff)); - vec.push_back(static_cast<uint8_t>((number >> 040) & 0xff)); - // intentional fall-through + vec.push_back(static_cast<uint8_t>( + (static_cast<uint64_t>(number) >> 070) & 0xff)); + vec.push_back(static_cast<uint8_t>( + (static_cast<uint64_t>(number) >> 060) & 0xff)); + vec.push_back(static_cast<uint8_t>( + (static_cast<uint64_t>(number) >> 050) & 0xff)); + vec.push_back(static_cast<uint8_t>( + (static_cast<uint64_t>(number) >> 040) & 0xff)); + vec.push_back(static_cast<uint8_t>((number >> 030) & 0xff)); + vec.push_back(static_cast<uint8_t>((number >> 020) & 0xff)); + vec.push_back(static_cast<uint8_t>((number >> 010) & 0xff)); + vec.push_back(static_cast<uint8_t>(number & 0xff)); + break; } case 4: { vec.push_back(static_cast<uint8_t>((number >> 030) & 0xff)); vec.push_back(static_cast<uint8_t>((number >> 020) & 0xff)); - // intentional fall-through + vec.push_back(static_cast<uint8_t>((number >> 010) & 0xff)); + vec.push_back(static_cast<uint8_t>(number & 0xff)); + break; } case 2: { vec.push_back(static_cast<uint8_t>((number >> 010) & 0xff)); - // intentional fall-through + vec.push_back(static_cast<uint8_t>(number & 0xff)); + break; } case 1: @@ -6460,7 +7891,7 @@ private: @tparam T the integral return type - @throw std::out_of_range if there are less than sizeof(T)+1 bytes in the + @throw parse_error.110 if there are less than sizeof(T)+1 bytes in the vector @a vec to read In the for loop, the bytes from the vector are copied in reverse order into @@ -6486,12 +7917,8 @@ private: static T get_from_vector(const std::vector<uint8_t> &vec, const size_t current_index) { - if (current_index + sizeof(T) + 1 > vec.size()) - { - JSON_THROW(std::out_of_range("cannot read " + - std::to_string(sizeof(T)) + - " bytes from vector")); - } + // check if we can read sizeof(T) bytes starting the next index + check_length(vec.size(), sizeof(T), current_index + 1); T result; auto *ptr = reinterpret_cast<uint8_t *>(&result); @@ -6544,25 +7971,29 @@ private: // positive fixnum add_to_vector(v, 1, j.m_value.number_unsigned); } - else if (j.m_value.number_unsigned <= UINT8_MAX) + else if (j.m_value.number_unsigned <= + (std::numeric_limits<uint8_t>::max)()) { // uint 8 v.push_back(0xcc); add_to_vector(v, 1, j.m_value.number_unsigned); } - else if (j.m_value.number_unsigned <= UINT16_MAX) + else if (j.m_value.number_unsigned <= + (std::numeric_limits<uint16_t>::max)()) { // uint 16 v.push_back(0xcd); add_to_vector(v, 2, j.m_value.number_unsigned); } - else if (j.m_value.number_unsigned <= UINT32_MAX) + else if (j.m_value.number_unsigned <= + (std::numeric_limits<uint32_t>::max)()) { // uint 32 v.push_back(0xce); add_to_vector(v, 4, j.m_value.number_unsigned); } - else if (j.m_value.number_unsigned <= UINT64_MAX) + else if (j.m_value.number_unsigned <= + (std::numeric_limits<uint64_t>::max)()) { // uint 64 v.push_back(0xcf); @@ -6576,29 +8007,37 @@ private: // negative fixnum add_to_vector(v, 1, j.m_value.number_integer); } - else if (j.m_value.number_integer >= INT8_MIN and - j.m_value.number_integer <= INT8_MAX) + else if (j.m_value.number_integer >= + (std::numeric_limits<int8_t>::min)() and + j.m_value.number_integer <= + (std::numeric_limits<int8_t>::max)()) { // int 8 v.push_back(0xd0); add_to_vector(v, 1, j.m_value.number_integer); } - else if (j.m_value.number_integer >= INT16_MIN and - j.m_value.number_integer <= INT16_MAX) + else if (j.m_value.number_integer >= + (std::numeric_limits<int16_t>::min)() and + j.m_value.number_integer <= + (std::numeric_limits<int16_t>::max)()) { // int 16 v.push_back(0xd1); add_to_vector(v, 2, j.m_value.number_integer); } - else if (j.m_value.number_integer >= INT32_MIN and - j.m_value.number_integer <= INT32_MAX) + else if (j.m_value.number_integer >= + (std::numeric_limits<int32_t>::min)() and + j.m_value.number_integer <= + (std::numeric_limits<int32_t>::max)()) { // int 32 v.push_back(0xd2); add_to_vector(v, 4, j.m_value.number_integer); } - else if (j.m_value.number_integer >= INT64_MIN and - j.m_value.number_integer <= INT64_MAX) + else if (j.m_value.number_integer >= + (std::numeric_limits<int64_t>::min)() and + j.m_value.number_integer <= + (std::numeric_limits<int64_t>::max)()) { // int 64 v.push_back(0xd3); @@ -6615,25 +8054,29 @@ private: // positive fixnum add_to_vector(v, 1, j.m_value.number_unsigned); } - else if (j.m_value.number_unsigned <= UINT8_MAX) + else if (j.m_value.number_unsigned <= + (std::numeric_limits<uint8_t>::max)()) { // uint 8 v.push_back(0xcc); add_to_vector(v, 1, j.m_value.number_unsigned); } - else if (j.m_value.number_unsigned <= UINT16_MAX) + else if (j.m_value.number_unsigned <= + (std::numeric_limits<uint16_t>::max)()) { // uint 16 v.push_back(0xcd); add_to_vector(v, 2, j.m_value.number_unsigned); } - else if (j.m_value.number_unsigned <= UINT32_MAX) + else if (j.m_value.number_unsigned <= + (std::numeric_limits<uint32_t>::max)()) { // uint 32 v.push_back(0xce); add_to_vector(v, 4, j.m_value.number_unsigned); } - else if (j.m_value.number_unsigned <= UINT64_MAX) + else if (j.m_value.number_unsigned <= + (std::numeric_limits<uint64_t>::max)()) { // uint 64 v.push_back(0xcf); @@ -6791,19 +8234,22 @@ private: { add_to_vector(v, 1, j.m_value.number_integer); } - else if (j.m_value.number_integer <= UINT8_MAX) + else if (j.m_value.number_integer <= + (std::numeric_limits<uint8_t>::max)()) { v.push_back(0x18); // one-byte uint8_t add_to_vector(v, 1, j.m_value.number_integer); } - else if (j.m_value.number_integer <= UINT16_MAX) + else if (j.m_value.number_integer <= + (std::numeric_limits<uint16_t>::max)()) { v.push_back(0x19); // two-byte uint16_t add_to_vector(v, 2, j.m_value.number_integer); } - else if (j.m_value.number_integer <= UINT32_MAX) + else if (j.m_value.number_integer <= + (std::numeric_limits<uint32_t>::max)()) { v.push_back(0x1a); // four-byte uint32_t @@ -6825,19 +8271,22 @@ private: { v.push_back(static_cast<uint8_t>(0x20 + positive_number)); } - else if (positive_number <= UINT8_MAX) + else if (positive_number <= + (std::numeric_limits<uint8_t>::max)()) { // int 8 v.push_back(0x38); add_to_vector(v, 1, positive_number); } - else if (positive_number <= UINT16_MAX) + else if (positive_number <= + (std::numeric_limits<uint16_t>::max)()) { // int 16 v.push_back(0x39); add_to_vector(v, 2, positive_number); } - else if (positive_number <= UINT32_MAX) + else if (positive_number <= + (std::numeric_limits<uint32_t>::max)()) { // int 32 v.push_back(0x3a); @@ -6904,7 +8353,8 @@ private: const auto N = j.m_value.string->size(); if (N <= 0x17) { - v.push_back(0x60 + N); // 1 byte for string + size + v.push_back( + static_cast<uint8_t>(0x60 + N)); // 1 byte for string + size } else if (N <= 0xff) { @@ -6940,7 +8390,8 @@ private: const auto N = j.m_value.array->size(); if (N <= 0x17) { - v.push_back(0x80 + N); // 1 byte for array + size + v.push_back( + static_cast<uint8_t>(0x80 + N)); // 1 byte for array + size } else if (N <= 0xff) { @@ -6978,7 +8429,8 @@ private: const auto N = j.m_value.object->size(); if (N <= 0x17) { - v.push_back(0xa0 + N); // 1 byte for object + size + v.push_back( + static_cast<uint8_t>(0xa0 + N)); // 1 byte for object + size } else if (N <= 0xff) { @@ -7047,20 +8499,90 @@ private: // simple case: requested length is greater than the vector's length if (len > size or offset > size) { - JSON_THROW(std::out_of_range("len out of range")); + JSON_THROW(parse_error::create( + 110, offset + 1, + "cannot read " + std::to_string(len) + " bytes from vector")); } // second case: adding offset would result in overflow - if ((size > (std::numeric_limits<size_t>::max() - offset))) + if ((size > ((std::numeric_limits<size_t>::max)() - offset))) { - JSON_THROW(std::out_of_range("len+offset out of range")); + JSON_THROW(parse_error::create( + 110, offset + 1, + "cannot read " + std::to_string(len) + " bytes from vector")); } // last case: reading past the end of the vector if (len + offset > size) { - JSON_THROW(std::out_of_range("len+offset out of range")); + JSON_THROW(parse_error::create( + 110, offset + 1, + "cannot read " + std::to_string(len) + " bytes from vector")); + } + } + + /*! + @brief check if the next byte belongs to a string + + While parsing a map, the keys must be strings. This function checks if the + current byte is one of the start bytes for a string in MessagePack: + + - 0xa0 - 0xbf: fixstr + - 0xd9: str 8 + - 0xda: str 16 + - 0xdb: str 32 + + @param[in] v MessagePack serialization + @param[in] idx byte index in @a v to check for a string + + @throw parse_error.113 if `v[idx]` does not belong to a string + */ + static void msgpack_expect_string(const std::vector<uint8_t> &v, size_t idx) + { + check_length(v.size(), 1, idx); + + const auto byte = v[idx]; + if ((byte >= 0xa0 and byte <= 0xbf) or (byte >= 0xd9 and byte <= 0xdb)) + { + return; + } + + std::stringstream ss; + ss << std::hex << static_cast<int>(v[idx]); + JSON_THROW(parse_error::create( + 113, idx + 1, + "expected a MessagePack string; last byte: 0x" + ss.str())); + } + + /*! + @brief check if the next byte belongs to a string + + While parsing a map, the keys must be strings. This function checks if the + current byte is one of the start bytes for a string in CBOR: + + - 0x60 - 0x77: fixed length + - 0x78 - 0x7b: variable length + - 0x7f: indefinity length + + @param[in] v CBOR serialization + @param[in] idx byte index in @a v to check for a string + + @throw parse_error.113 if `v[idx]` does not belong to a string + */ + static void cbor_expect_string(const std::vector<uint8_t> &v, size_t idx) + { + check_length(v.size(), 1, idx); + + const auto byte = v[idx]; + if ((byte >= 0x60 and byte <= 0x7b) or byte == 0x7f) + { + return; } + + std::stringstream ss; + ss << std::hex << static_cast<int>(v[idx]); + JSON_THROW(parse_error::create( + 113, idx + 1, "expected a CBOR string; last byte: 0x" + ss.str())); } /*! @@ -7071,21 +8593,22 @@ private: @return deserialized JSON value - @throw std::invalid_argument if unsupported features from MessagePack were + @throw parse_error.110 if the given vector ends prematurely + @throw parse_error.112 if unsupported features from MessagePack were used in the given vector @a v or if the input is not valid MessagePack - @throw std::out_of_range if the given vector ends prematurely + @throw parse_error.113 if a string was expected as map key, but not found @sa https://github.com/msgpack/msgpack/blob/master/spec.md */ static basic_json from_msgpack_internal(const std::vector<uint8_t> &v, size_t &idx) { - // make sure reading 1 byte is safe - check_length(v.size(), 1, idx); - // store and increment index const size_t current_idx = idx++; + // make sure reading 1 byte is safe + check_length(v.size(), 1, current_idx); + if (v[current_idx] <= 0xbf) { if (v[current_idx] <= 0x7f) // positive fixint @@ -7098,6 +8621,7 @@ private: const size_t len = v[current_idx] & 0x0f; for (size_t i = 0; i < len; ++i) { + msgpack_expect_string(v, idx); std::string key = from_msgpack_internal(v, idx); result[key] = from_msgpack_internal(v, idx); } @@ -7150,11 +8674,12 @@ private: { // copy bytes in reverse order into the double variable float res; + check_length(v.size(), sizeof(float), current_idx + 1); for (size_t byte = 0; byte < sizeof(float); ++byte) { reinterpret_cast<uint8_t *>( &res)[sizeof(float) - byte - 1] = - v.at(current_idx + 1 + byte); + v[current_idx + 1 + byte]; } idx += sizeof(float); // skip content bytes return res; @@ -7164,11 +8689,12 @@ private: { // copy bytes in reverse order into the double variable double res; + check_length(v.size(), sizeof(double), current_idx + 1); for (size_t byte = 0; byte < sizeof(double); ++byte) { reinterpret_cast<uint8_t *>( &res)[sizeof(double) - byte - 1] = - v.at(current_idx + 1 + byte); + v[current_idx + 1 + byte]; } idx += sizeof(double); // skip content bytes return res; @@ -7289,6 +8815,7 @@ private: idx += 2; // skip 2 size bytes for (size_t i = 0; i < len; ++i) { + msgpack_expect_string(v, idx); std::string key = from_msgpack_internal(v, idx); result[key] = from_msgpack_internal(v, idx); } @@ -7303,6 +8830,7 @@ private: idx += 4; // skip 4 size bytes for (size_t i = 0; i < len; ++i) { + msgpack_expect_string(v, idx); std::string key = from_msgpack_internal(v, idx); result[key] = from_msgpack_internal(v, idx); } @@ -7311,9 +8839,11 @@ private: default: { - JSON_THROW(std::invalid_argument( - "error parsing a msgpack @ " + std::to_string(current_idx) + - ": " + std::to_string(static_cast<int>(v[current_idx])))); + std::stringstream ss; + ss << std::hex << static_cast<int>(v[current_idx]); + JSON_THROW(parse_error::create( + 112, current_idx + 1, + "error reading MessagePack; last byte: 0x" + ss.str())); } } } @@ -7327,9 +8857,10 @@ private: @return deserialized JSON value - @throw std::invalid_argument if unsupported features from CBOR were used in - the given vector @a v or if the input is not valid CBOR - @throw std::out_of_range if the given vector ends prematurely + @throw parse_error.110 if the given vector ends prematurely + @throw parse_error.112 if unsupported features from CBOR were + used in the given vector @a v or if the input is not valid CBOR + @throw parse_error.113 if a string was expected as map key, but not found @sa https://tools.ietf.org/html/rfc7049 */ @@ -7339,7 +8870,10 @@ private: // store and increment index const size_t current_idx = idx++; - switch (v.at(current_idx)) + // make sure reading 1 byte is safe + check_length(v.size(), 1, current_idx); + + switch (v[current_idx]) { // Integer 0x00..0x17 (0..23) case 0x00: @@ -7534,7 +9068,8 @@ private: case 0x7f: // UTF-8 string (indefinite length) { std::string result; - while (v.at(idx) != 0xff) + while (static_cast<void>(check_length(v.size(), 1, idx)), + v[idx] != 0xff) { string_t s = from_cbor_internal(v, idx); result += s; @@ -7634,7 +9169,8 @@ private: case 0x9f: // array (indefinite length) { basic_json result = value_t::array; - while (v.at(idx) != 0xff) + while (static_cast<void>(check_length(v.size(), 1, idx)), + v[idx] != 0xff) { result.push_back(from_cbor_internal(v, idx)); } @@ -7673,6 +9209,7 @@ private: const auto len = static_cast<size_t>(v[current_idx] - 0xa0); for (size_t i = 0; i < len; ++i) { + cbor_expect_string(v, idx); std::string key = from_cbor_internal(v, idx); result[key] = from_cbor_internal(v, idx); } @@ -7687,6 +9224,7 @@ private: idx += 1; // skip 1 size byte for (size_t i = 0; i < len; ++i) { + cbor_expect_string(v, idx); std::string key = from_cbor_internal(v, idx); result[key] = from_cbor_internal(v, idx); } @@ -7701,6 +9239,7 @@ private: idx += 2; // skip 2 size bytes for (size_t i = 0; i < len; ++i) { + cbor_expect_string(v, idx); std::string key = from_cbor_internal(v, idx); result[key] = from_cbor_internal(v, idx); } @@ -7715,6 +9254,7 @@ private: idx += 4; // skip 4 size bytes for (size_t i = 0; i < len; ++i) { + cbor_expect_string(v, idx); std::string key = from_cbor_internal(v, idx); result[key] = from_cbor_internal(v, idx); } @@ -7729,6 +9269,7 @@ private: idx += 8; // skip 8 size bytes for (size_t i = 0; i < len; ++i) { + cbor_expect_string(v, idx); std::string key = from_cbor_internal(v, idx); result[key] = from_cbor_internal(v, idx); } @@ -7738,8 +9279,10 @@ private: case 0xbf: // map (indefinite length) { basic_json result = value_t::object; - while (v.at(idx) != 0xff) + while (static_cast<void>(check_length(v.size(), 1, idx)), + v[idx] != 0xff) { + cbor_expect_string(v, idx); std::string key = from_cbor_internal(v, idx); result[key] = from_cbor_internal(v, idx); } @@ -7774,8 +9317,8 @@ private: // include at least decoding support for them even without such // support. An example of a small decoder for half-precision // floating-point numbers in the C language is shown in Fig. 3. - const int half = - (v.at(current_idx + 1) << 8) + v.at(current_idx + 2); + check_length(v.size(), 2, current_idx + 1); + const int half = (v[current_idx + 1] << 8) + v[current_idx + 2]; const int exp = (half >> 10) & 0x1f; const int mant = half & 0x3ff; double val; @@ -7789,7 +9332,8 @@ private: } else { - val = mant == 0 ? INFINITY : NAN; + val = mant == 0 ? std::numeric_limits<double>::infinity() + : std::numeric_limits<double>::quiet_NaN(); } return (half & 0x8000) != 0 ? -val : val; } @@ -7798,10 +9342,11 @@ private: { // copy bytes in reverse order into the float variable float res; + check_length(v.size(), sizeof(float), current_idx + 1); for (size_t byte = 0; byte < sizeof(float); ++byte) { reinterpret_cast<uint8_t *>(&res)[sizeof(float) - byte - 1] = - v.at(current_idx + 1 + byte); + v[current_idx + 1 + byte]; } idx += sizeof(float); // skip content bytes return res; @@ -7811,10 +9356,11 @@ private: { // copy bytes in reverse order into the double variable double res; + check_length(v.size(), sizeof(double), current_idx + 1); for (size_t byte = 0; byte < sizeof(double); ++byte) { reinterpret_cast<uint8_t *>(&res)[sizeof(double) - byte - 1] = - v.at(current_idx + 1 + byte); + v[current_idx + 1 + byte]; } idx += sizeof(double); // skip content bytes return res; @@ -7822,9 +9368,11 @@ private: default: // anything else (0xFF is handled inside the other types) { - JSON_THROW(std::invalid_argument( - "error parsing a CBOR @ " + std::to_string(current_idx) + ": " + - std::to_string(static_cast<int>(v[current_idx])))); + std::stringstream ss; + ss << std::hex << static_cast<int>(v[current_idx]); + JSON_THROW(parse_error::create(112, current_idx + 1, + "error reading CBOR; last byte: 0x" + + ss.str())); } } } @@ -7837,6 +9385,86 @@ public: serialization format. MessagePack is a binary serialization format which aims to be more compact than JSON itself, yet more efficient to parse. + The library uses the following mapping from JSON values types to + MessagePack types according to the MessagePack specification: + + JSON value type | value/range | MessagePack type | + first byte + --------------- | --------------------------------- | ---------------- | + ---------- + null | `null` | nil | + 0xc0 + boolean | `true` | true | + 0xc3 + boolean | `false` | false | + 0xc2 + number_integer | -9223372036854775808..-2147483649 | int64 | + 0xd3 + number_integer | -2147483648..-32769 | int32 | + 0xd2 + number_integer | -32768..-129 | int16 |Â 0xd1 + number_integer | -128..-33 | int8 | + 0xd0 + number_integer | -32..-1 | negative fixint | + 0xe0..0xff + number_integer | 0..127 | positive fixint | + 0x00..0x7f + number_integer | 128..255 | uint 8 |Â 0xcc + number_integer | 256..65535 | uint 16 | + 0xcd + number_integer | 65536..4294967295 | uint 32 | + 0xce + number_integer | 4294967296..18446744073709551615 | uint 64 | + 0xcf + number_unsigned | 0..127 | positive fixint | + 0x00..0x7f + number_unsigned | 128..255 | uint 8 |Â 0xcc + number_unsigned | 256..65535 | uint 16 | + 0xcd + number_unsigned | 65536..4294967295 | uint 32 | + 0xce + number_unsigned | 4294967296..18446744073709551615 | uint 64 | + 0xcf + number_float | *any value* | float 64 | + 0xcb + string | *length*: 0..31 | fixstr | + 0xa0..0xbf + string | *length*: 32..255 | str 8 | + 0xd9 + string | *length*: 256..65535 | str 16 | + 0xda + string | *length*: 65536..4294967295 | str 32 | + 0xdb + array | *size*: 0..15 | fixarray | + 0x90..0x9f + array | *size*: 16..65535 | array 16 | + 0xdc + array | *size*: 65536..4294967295 | array 32 | + 0xdd + object | *size*: 0..15 | fix map | + 0x80..0x8f + object | *size*: 16..65535 | map 16 | + 0xde + object | *size*: 65536..4294967295 | map 32 | + 0xdf + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a MessagePack value. + + @note The following values can **not** be converted to a MessagePack value: + - strings with more than 4294967295 bytes + - arrays with more than 4294967295 elements + - objects with more than 4294967295 elements + + @note The following MessagePack types are not used in the conversion: + - bin 8 - bin 32 (0xc4..0xc6) + - ext 8 - ext 32 (0xc7..0xc9) + - float 32 (0xca) + - fixext 1 - fixext 16 (0xd4..0xd8) + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + @param[in] j JSON value to serialize @return MessagePack serialization as byte vector @@ -7846,9 +9474,11 @@ public: vector in MessagePack format.,to_msgpack} @sa http://msgpack.org - @sa @ref from_msgpack(const std::vector<uint8_t>&) for the analogous - deserialization + @sa @ref from_msgpack(const std::vector<uint8_t>&, const size_t) for the + analogous deserialization @sa @ref to_cbor(const basic_json& for the related CBOR format + + @since version 2.0.9 */ static std::vector<uint8_t> to_msgpack(const basic_json &j) { @@ -7863,12 +9493,54 @@ public: Deserializes a given byte vector @a v to a JSON value using the MessagePack serialization format. + The library maps MessagePack types to JSON value types as follows: + + MessagePack type | JSON value type | first byte + ---------------- | --------------- | ---------- + positive fixint | number_unsigned | 0x00..0x7f + fixmap | object | 0x80..0x8f + fixarray | array | 0x90..0x9f + fixstr | string | 0xa0..0xbf + nil | `null` | 0xc0 + false | `false` | 0xc2 + true | `true` | 0xc3 + float 32 | number_float | 0xca + float 64 | number_float | 0xcb + uint 8 | number_unsigned | 0xcc + uint 16 | number_unsigned | 0xcd + uint 32 | number_unsigned | 0xce + uint 64 | number_unsigned | 0xcf + int 8 | number_integer | 0xd0 + int 16 | number_integer | 0xd1 + int 32 | number_integer | 0xd2 + int 64 | number_integer | 0xd3 + str 8 | string | 0xd9 + str 16 | string | 0xda + str 32 | string | 0xdb + array 16 | array | 0xdc + array 32 | array | 0xdd + map 16 | object | 0xde + map 32 | object | 0xdf + negative fixint | number_integer | 0xe0-0xff + + @warning The mapping is **incomplete** in the sense that not all + MessagePack types can be converted to a JSON value. The following + MessagePack types are not supported and will yield parse errors: + - bin 8 - bin 32 (0xc4..0xc6) + - ext 8 - ext 32 (0xc7..0xc9) + - fixext 1 - fixext 16 (0xd4..0xd8) + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + @param[in] v a byte vector in MessagePack format + @param[in] start_index the index to start reading from @a v (0 by default) @return deserialized JSON value - @throw std::invalid_argument if unsupported features from MessagePack were + @throw parse_error.110 if the given vector ends prematurely + @throw parse_error.112 if unsupported features from MessagePack were used in the given vector @a v or if the input is not valid MessagePack - @throw std::out_of_range if the given vector ends prematurely + @throw parse_error.113 if a string was expected as map key, but not found @complexity Linear in the size of the byte vector @a v. @@ -7877,11 +9549,15 @@ public: @sa http://msgpack.org @sa @ref to_msgpack(const basic_json&) for the analogous serialization - @sa @ref from_cbor(const std::vector<uint8_t>&) for the related CBOR format + @sa @ref from_cbor(const std::vector<uint8_t>&, const size_t) for the + related CBOR format + + @since version 2.0.9, parameter @a start_index since 2.1.1 */ - static basic_json from_msgpack(const std::vector<uint8_t> &v) + static basic_json from_msgpack(const std::vector<uint8_t> &v, + const size_t start_index = 0) { - size_t i = 0; + size_t i = start_index; return from_msgpack_internal(v, i); } @@ -7893,6 +9569,98 @@ public: serialization format which aims to be more compact than JSON itself, yet more efficient to parse. + The library uses the following mapping from JSON values types to + CBOR types according to the CBOR specification (RFC 7049): + + JSON value type | value/range | CBOR type + | first byte + --------------- | ------------------------------------------ | + ---------------------------------- | --------------- + null | `null` | Null | 0xf6 + boolean | `true` | True | 0xf5 + boolean | `false` | False | 0xf4 + number_integer | -9223372036854775808..-2147483649 | Negative + integer (8 bytes follow) | 0x3b + number_integer | -2147483648..-32769 | Negative + integer (4 bytes follow) | 0x3a + number_integer | -32768..-129 | Negative + integer (2 bytes follow) | 0x39 + number_integer | -128..-25 | Negative + integer (1 byte follow) | 0x38 + number_integer | -24..-1 | Negative + integer | 0x20..0x37 + number_integer | 0..23 | Integer + | 0x00..0x17 + number_integer | 24..255 | Unsigned + integer (1 byte follow) | 0x18 + number_integer | 256..65535 | Unsigned + integer (2 bytes follow) | 0x19 + number_integer | 65536..4294967295 | Unsigned + integer (4 bytes follow) | 0x1a + number_integer | 4294967296..18446744073709551615 | Unsigned + integer (8 bytes follow) | 0x1b + number_unsigned | 0..23 | Integer + | 0x00..0x17 + number_unsigned | 24..255 | Unsigned + integer (1 byte follow) | 0x18 + number_unsigned | 256..65535 | Unsigned + integer (2 bytes follow) | 0x19 + number_unsigned | 65536..4294967295 | Unsigned + integer (4 bytes follow) | 0x1a + number_unsigned | 4294967296..18446744073709551615 | Unsigned + integer (8 bytes follow) | 0x1b + number_float | *any value* | + Double-Precision Float | 0xfb + string | *length*: 0..23 | UTF-8 string + | 0x60..0x77 + string | *length*: 23..255 | UTF-8 string + (1 byte follow) | 0x78 + string | *length*: 256..65535 | UTF-8 string + (2 bytes follow) | 0x79 + string | *length*: 65536..4294967295 | UTF-8 string + (4 bytes follow) | 0x7a + string | *length*: 4294967296..18446744073709551615 | UTF-8 string + (8 bytes follow) | 0x7b + array | *size*: 0..23 | array + | 0x80..0x97 + array | *size*: 23..255 | array (1 byte + follow) | 0x98 + array | *size*: 256..65535 | array (2 + bytes follow) | 0x99 + array | *size*: 65536..4294967295 | array (4 + bytes follow) | 0x9a + array | *size*: 4294967296..18446744073709551615 | array (8 + bytes follow) | 0x9b + object | *size*: 0..23 | map + | 0xa0..0xb7 + object | *size*: 23..255 | map (1 byte + follow) | 0xb8 + object | *size*: 256..65535 | map (2 bytes + follow) | 0xb9 + object | *size*: 65536..4294967295 | map (4 bytes + follow) | 0xba + object | *size*: 4294967296..18446744073709551615 | map (8 bytes + follow) | 0xbb + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a CBOR value. + + @note The following CBOR types are not used in the conversion: + - byte strings (0x40..0x5f) + - UTF-8 strings terminated by "break" (0x7f) + - arrays terminated by "break" (0x9f) + - maps terminated by "break" (0xbf) + - date/time (0xc0..0xc1) + - bignum (0xc2..0xc3) + - decimal fraction (0xc4) + - bigfloat (0xc5) + - tagged items (0xc6..0xd4, 0xd8..0xdb) + - expected conversions (0xd5..0xd7) + - simple values (0xe0..0xf3, 0xf8) + - undefined (0xf7) + - half and single-precision floats (0xf9-0xfa) + - break (0xff) + @param[in] j JSON value to serialize @return MessagePack serialization as byte vector @@ -7902,9 +9670,11 @@ public: vector in CBOR format.,to_cbor} @sa http://cbor.io - @sa @ref from_cbor(const std::vector<uint8_t>&) for the analogous - deserialization + @sa @ref from_cbor(const std::vector<uint8_t>&, const size_t) for the + analogous deserialization @sa @ref to_msgpack(const basic_json& for the related MessagePack format + + @since version 2.0.9 */ static std::vector<uint8_t> to_cbor(const basic_json &j) { @@ -7919,382 +9689,135 @@ public: Deserializes a given byte vector @a v to a JSON value using the CBOR (Concise Binary Object Representation) serialization format. - @param[in] v a byte vector in CBOR format - @return deserialized JSON value - - @throw std::invalid_argument if unsupported features from CBOR were used in - the given vector @a v or if the input is not valid MessagePack - @throw std::out_of_range if the given vector ends prematurely - - @complexity Linear in the size of the byte vector @a v. - - @liveexample{The example shows the deserialization of a byte vector in CBOR - format to a JSON value.,from_cbor} - - @sa http://cbor.io - @sa @ref to_cbor(const basic_json&) for the analogous serialization - @sa @ref from_msgpack(const std::vector<uint8_t>&) for the related - MessagePack format - */ - static basic_json from_cbor(const std::vector<uint8_t> &v) - { - size_t i = 0; - return from_cbor_internal(v, i); - } - - /// @} - -private: - /////////////////////////// - // convenience functions // - /////////////////////////// - - /*! - @brief return the type as string - - Returns the type name as string to be used in error messages - usually to - indicate that a function was called on a wrong JSON type. - - @return basically a string representation of a the @a m_type member - - @complexity Constant. - - @since version 1.0.0 - */ - std::string type_name() const - { - switch (m_type) - { - case value_t::null: - return "null"; - case value_t::object: - return "object"; - case value_t::array: - return "array"; - case value_t::string: - return "string"; - case value_t::boolean: - return "boolean"; - case value_t::discarded: - return "discarded"; - default: - return "number"; - } - } - - /*! - @brief calculates the extra space to escape a JSON string - - @param[in] s the string to escape - @return the number of characters required to escape string @a s - - @complexity Linear in the length of string @a s. - */ - static std::size_t extra_space(const string_t &s) noexcept - { - return std::accumulate(s.begin(), s.end(), size_t{}, - [](size_t res, typename string_t::value_type c) { - switch (c) - { - case '"': - case '\\': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - { - // from c (1 byte) to \x (2 bytes) - return res + 1; - } - - default: - { - if (c >= 0x00 and c <= 0x1f) - { - // from c (1 byte) to \uxxxx (6 - // bytes) - return res + 5; - } - - return res; - } - } - }); - } - - /*! - @brief escape a string - - Escape a string by replacing certain special characters by a sequence of - an escape character (backslash) and another character and other control - characters by a sequence of "\u" followed by a four-digit hex - representation. - - @param[in] s the string to escape - @return the escaped string - - @complexity Linear in the length of string @a s. - */ - static string_t escape_string(const string_t &s) - { - const auto space = extra_space(s); - if (space == 0) - { - return s; - } - - // create a result string of necessary size - string_t result(s.size() + space, '\\'); - std::size_t pos = 0; - - for (const auto &c : s) - { - switch (c) - { - // quotation mark (0x22) - case '"': - { - result[pos + 1] = '"'; - pos += 2; - break; - } - - // reverse solidus (0x5c) - case '\\': - { - // nothing to change - pos += 2; - break; - } - - // backspace (0x08) - case '\b': - { - result[pos + 1] = 'b'; - pos += 2; - break; - } - - // formfeed (0x0c) - case '\f': - { - result[pos + 1] = 'f'; - pos += 2; - break; - } - - // newline (0x0a) - case '\n': - { - result[pos + 1] = 'n'; - pos += 2; - break; - } - - // carriage return (0x0d) - case '\r': - { - result[pos + 1] = 'r'; - pos += 2; - break; - } - - // horizontal tab (0x09) - case '\t': - { - result[pos + 1] = 't'; - pos += 2; - break; - } - - default: - { - if (c >= 0x00 and c <= 0x1f) - { - // convert a number 0..15 to its hex representation - // (0..f) - static const char hexify[16] = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - - // print character c as \uxxxx - for (const char m : - {'u', '0', '0', hexify[c >> 4], hexify[c & 0x0f]}) - { - result[++pos] = m; - } - - ++pos; - } - else - { - // all other characters are added as-is - result[pos++] = c; - } - break; - } - } - } - - return result; - } - - /*! - @brief internal implementation of the serialization function - - This function is called by the public member function dump and organizes - the serialization internally. The indentation level is propagated as - additional parameter. In case of arrays and objects, the function is - called recursively. Note that - - - strings and object keys are escaped using `escape_string()` - - integer numbers are converted implicitly via `operator<<` - - floating-point numbers are converted to a string using `"%g"` format - - @param[out] o stream to write to - @param[in] pretty_print whether the output shall be pretty-printed - @param[in] indent_step the indent level - @param[in] current_indent the current indent level (only used internally) - */ - void dump(std::ostream &o, const bool pretty_print, - const unsigned int indent_step, - const unsigned int current_indent = 0) const - { - // variable to hold indentation for recursive calls - unsigned int new_indent = current_indent; - - switch (m_type) - { - case value_t::object: - { - if (m_value.object->empty()) - { - o << "{}"; - return; - } - - o << "{"; - - // increase indentation - if (pretty_print) - { - new_indent += indent_step; - o << "\n"; - } + The library maps CBOR types to JSON value types as follows: + + CBOR type | JSON value type | first byte + ---------------------- | --------------- | ---------- + Integer | number_unsigned | 0x00..0x17 + Unsigned integer | number_unsigned | 0x18 + Unsigned integer | number_unsigned | 0x19 + Unsigned integer | number_unsigned | 0x1a + Unsigned integer | number_unsigned | 0x1b + Negative integer | number_integer | 0x20..0x37 + Negative integer | number_integer | 0x38 + Negative integer | number_integer | 0x39 + Negative integer | number_integer | 0x3a + Negative integer | number_integer | 0x3b + Negative integer | number_integer | 0x40..0x57 + UTF-8 string | string | 0x60..0x77 + UTF-8 string | string | 0x78 + UTF-8 string | string | 0x79 + UTF-8 string | string | 0x7a + UTF-8 string | string | 0x7b + UTF-8 string | string | 0x7f + array | array | 0x80..0x97 + array | array | 0x98 + array | array | 0x99 + array | array | 0x9a + array | array | 0x9b + array | array | 0x9f + map | object | 0xa0..0xb7 + map | object | 0xb8 + map | object | 0xb9 + map | object | 0xba + map | object | 0xbb + map | object | 0xbf + False | `false` | 0xf4 + True |Â `true` | 0xf5 + Nill | `null` | 0xf6 + Half-Precision Float | number_float | 0xf9 + Single-Precision Float | number_float | 0xfa + Double-Precision Float | number_float | 0xfb + + @warning The mapping is **incomplete** in the sense that not all CBOR + types can be converted to a JSON value. The following CBOR types + are not supported and will yield parse errors (parse_error.112): + - byte strings (0x40..0x5f) + - date/time (0xc0..0xc1) + - bignum (0xc2..0xc3) + - decimal fraction (0xc4) + - bigfloat (0xc5) + - tagged items (0xc6..0xd4, 0xd8..0xdb) + - expected conversions (0xd5..0xd7) + - simple values (0xe0..0xf3, 0xf8) + - undefined (0xf7) + + @warning CBOR allows map keys of any type, whereas JSON only allows + strings as keys in object values. Therefore, CBOR maps with keys + other than UTF-8 strings are rejected (parse_error.113). + + @note Any CBOR output created @ref to_cbor can be successfully parsed by + @ref from_cbor. - for (auto i = m_value.object->cbegin(); i != m_value.object->cend(); - ++i) - { - if (i != m_value.object->cbegin()) - { - o << (pretty_print ? ",\n" : ","); - } - o << string_t(new_indent, ' ') << "\"" - << escape_string(i->first) - << "\":" << (pretty_print ? " " : ""); - i->second.dump(o, pretty_print, indent_step, new_indent); - } + @param[in] v a byte vector in CBOR format + @param[in] start_index the index to start reading from @a v (0 by default) + @return deserialized JSON value - // decrease indentation - if (pretty_print) - { - new_indent -= indent_step; - o << "\n"; - } + @throw parse_error.110 if the given vector ends prematurely + @throw parse_error.112 if unsupported features from CBOR were + used in the given vector @a v or if the input is not valid CBOR + @throw parse_error.113 if a string was expected as map key, but not found - o << string_t(new_indent, ' ') + "}"; - return; - } + @complexity Linear in the size of the byte vector @a v. - case value_t::array: - { - if (m_value.array->empty()) - { - o << "[]"; - return; - } + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} - o << "["; + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(const std::vector<uint8_t>&, const size_t) for the + related MessagePack format - // increase indentation - if (pretty_print) - { - new_indent += indent_step; - o << "\n"; - } + @since version 2.0.9, parameter @a start_index since 2.1.1 + */ + static basic_json from_cbor(const std::vector<uint8_t> &v, + const size_t start_index = 0) + { + size_t i = start_index; + return from_cbor_internal(v, i); + } - for (auto i = m_value.array->cbegin(); i != m_value.array->cend(); - ++i) - { - if (i != m_value.array->cbegin()) - { - o << (pretty_print ? ",\n" : ","); - } - o << string_t(new_indent, ' '); - i->dump(o, pretty_print, indent_step, new_indent); - } + /// @} - // decrease indentation - if (pretty_print) - { - new_indent -= indent_step; - o << "\n"; - } + /////////////////////////// + // convenience functions // + /////////////////////////// - o << string_t(new_indent, ' ') << "]"; - return; - } + /*! + @brief return the type as string - case value_t::string: - { - o << string_t("\"") << escape_string(*m_value.string) << "\""; - return; - } + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. - case value_t::boolean: - { - o << (m_value.boolean ? "true" : "false"); - return; - } + @return basically a string representation of a the @a m_type member - case value_t::number_integer: - { - o << m_value.number_integer; - return; - } + @complexity Constant. - case value_t::number_unsigned: - { - o << m_value.number_unsigned; - return; - } + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,type_name} - case value_t::number_float: + @since version 1.0.0, public since 2.1.0 + */ + std::string type_name() const + { { - if (m_value.number_float == 0) + switch (m_type) { - // special case for zero to get "0.0"/"-0.0" - o << (std::signbit(m_value.number_float) ? "-0.0" : "0.0"); - } - else - { - o << m_value.number_float; + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; } - return; - } - - case value_t::discarded: - { - o << "<discarded>"; - return; - } - - case value_t::null: - { - o << "null"; - return; - } } } @@ -8326,6 +9849,7 @@ private: class primitive_iterator_t { public: + difference_type get_value() const noexcept { return m_it; } /// set iterator to a defined beginning void set_begin() noexcept { m_it = begin_value; } @@ -8341,11 +9865,98 @@ private: /// return whether the iterator is at end constexpr bool is_end() const noexcept { return (m_it == end_value); } - /// return reference to the value to change and compare - operator difference_type &() noexcept { return m_it; } + friend constexpr bool operator==(primitive_iterator_t lhs, + primitive_iterator_t rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator!=(primitive_iterator_t lhs, + primitive_iterator_t rhs) noexcept + { + return !(lhs == rhs); + } + + friend constexpr bool operator<(primitive_iterator_t lhs, + primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + friend constexpr bool operator<=(primitive_iterator_t lhs, + primitive_iterator_t rhs) noexcept + { + return lhs.m_it <= rhs.m_it; + } + + friend constexpr bool operator>(primitive_iterator_t lhs, + primitive_iterator_t rhs) noexcept + { + return lhs.m_it > rhs.m_it; + } + + friend constexpr bool operator>=(primitive_iterator_t lhs, + primitive_iterator_t rhs) noexcept + { + return lhs.m_it >= rhs.m_it; + } + + primitive_iterator_t operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + friend constexpr difference_type + operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + friend std::ostream &operator<<(std::ostream &os, + primitive_iterator_t it) + { + return os << it.m_it; + } + + primitive_iterator_t &operator++() + { + ++m_it; + return *this; + } + + primitive_iterator_t operator++(int) + { + auto result = *this; + m_it++; + return result; + } + + primitive_iterator_t &operator--() + { + --m_it; + return *this; + } + + primitive_iterator_t operator--(int) + { + auto result = *this; + m_it--; + return result; + } + + primitive_iterator_t &operator+=(difference_type n) + { + m_it += n; + return *this; + } - /// return value to compare - constexpr operator difference_type() const noexcept { return m_it; } + primitive_iterator_t &operator-=(difference_type n) + { + m_it -= n; + return *this; + } private: static constexpr difference_type begin_value = 0; @@ -8701,7 +10312,7 @@ public: case basic_json::value_t::null: { - JSON_THROW(std::out_of_range("cannot get value")); + JSON_THROW(invalid_iterator::create(214, "cannot get value")); } default: @@ -8711,7 +10322,7 @@ public: return *m_object; } - JSON_THROW(std::out_of_range("cannot get value")); + JSON_THROW(invalid_iterator::create(214, "cannot get value")); } } } @@ -8745,7 +10356,7 @@ public: return m_object; } - JSON_THROW(std::out_of_range("cannot get value")); + JSON_THROW(invalid_iterator::create(214, "cannot get value")); } } } @@ -8845,8 +10456,8 @@ public: // if objects are not the same, the comparison is undefined if (m_object != other.m_object) { - JSON_THROW(std::domain_error( - "cannot compare iterators of different containers")); + JSON_THROW(invalid_iterator::create( + 212, "cannot compare iterators of different containers")); } assert(m_object != nullptr); @@ -8889,8 +10500,8 @@ public: // if objects are not the same, the comparison is undefined if (m_object != other.m_object) { - JSON_THROW(std::domain_error( - "cannot compare iterators of different containers")); + JSON_THROW(invalid_iterator::create( + 212, "cannot compare iterators of different containers")); } assert(m_object != nullptr); @@ -8899,8 +10510,8 @@ public: { case basic_json::value_t::object: { - JSON_THROW(std::domain_error( - "cannot compare order of object iterators")); + JSON_THROW(invalid_iterator::create( + 213, "cannot compare order of object iterators")); } case basic_json::value_t::array: @@ -8955,8 +10566,8 @@ public: { case basic_json::value_t::object: { - JSON_THROW(std::domain_error( - "cannot use offsets with object iterators")); + JSON_THROW(invalid_iterator::create( + 209, "cannot use offsets with object iterators")); } case basic_json::value_t::array: @@ -9015,8 +10626,8 @@ public: { case basic_json::value_t::object: { - JSON_THROW(std::domain_error( - "cannot use offsets with object iterators")); + JSON_THROW(invalid_iterator::create( + 209, "cannot use offsets with object iterators")); } case basic_json::value_t::array: @@ -9043,8 +10654,8 @@ public: { case basic_json::value_t::object: { - JSON_THROW(std::domain_error( - "cannot use operator[] for object iterators")); + JSON_THROW(invalid_iterator::create( + 208, "cannot use operator[] for object iterators")); } case basic_json::value_t::array: @@ -9054,17 +10665,17 @@ public: case basic_json::value_t::null: { - JSON_THROW(std::out_of_range("cannot get value")); + JSON_THROW(invalid_iterator::create(214, "cannot get value")); } default: { - if (m_it.primitive_iterator == -n) + if (m_it.primitive_iterator.get_value() == -n) { return *m_object; } - JSON_THROW(std::out_of_range("cannot get value")); + JSON_THROW(invalid_iterator::create(214, "cannot get value")); } } } @@ -9082,8 +10693,8 @@ public: return m_it.object_iterator->first; } - JSON_THROW( - std::domain_error("cannot use key() for non-object iterators")); + JSON_THROW(invalid_iterator::create( + 207, "cannot use key() for non-object iterators")); } /*! @@ -9233,16 +10844,21 @@ private: /// token types for the parser enum class token_type { - uninitialized, ///< indicating the scanner is uninitialized - literal_true, ///< the `true` literal - literal_false, ///< the `false` literal - literal_null, ///< the `null` literal - value_string, ///< a string -- use get_string() for actual value - value_number, ///< a number -- use get_number() for actual value - begin_array, ///< the character for array begin `[` - begin_object, ///< the character for object begin `{` - end_array, ///< the character for array end `]` - end_object, ///< the character for object end `}` + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number() for + /// actual value + value_integer, ///< a signed integer -- use get_number() for actual + /// value + value_float, ///< an floating point number -- use get_number() for + /// actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` name_separator, ///< the name separator `:` value_separator, ///< the value separator `,` parse_error, ///< indicating a parse error @@ -9261,13 +10877,16 @@ private: m_limit = m_content + len; } - /// a lexer from an input stream + /*! + @brief a lexer from an input stream + @throw parse_error.111 if input stream is in a bad state + */ explicit lexer(std::istream &s) : m_stream(&s), m_line_buffer() { // immediately abort if stream is erroneous if (s.fail()) { - JSON_THROW(std::invalid_argument("stream error")); + JSON_THROW(parse_error::create(111, 0, "bad input stream")); } // fill buffer @@ -9302,17 +10921,17 @@ private: @return string representation of the code point; the length of the result string is between 1 and 4 characters. - @throw std::out_of_range if code point is > 0x10ffff; example: `"code - points above 0x10FFFF are invalid"` - @throw std::invalid_argument if the low surrogate is invalid; example: + @throw parse_error.102 if the low surrogate is invalid; example: `""missing or wrong low surrogate""` + @throw parse_error.103 if code point is > 0x10ffff; example: `"code + points above 0x10FFFF are invalid"` @complexity Constant. @see <http://en.wikipedia.org/wiki/UTF-8#Sample_code> */ - static string_t to_unicode(const std::size_t codepoint1, - const std::size_t codepoint2 = 0) + string_t to_unicode(const std::size_t codepoint1, + const std::size_t codepoint2 = 0) const { // calculate the code point from the given code points std::size_t codepoint = codepoint1; @@ -9335,8 +10954,8 @@ private: } else { - JSON_THROW(std::invalid_argument( - "missing or wrong low surrogate")); + JSON_THROW(parse_error::create( + 102, get_position(), "missing or wrong low surrogate")); } } @@ -9352,7 +10971,7 @@ private: { // 2-byte characters: 110xxxxx 10xxxxxx result.append(1, static_cast<typename string_t::value_type>( - 0xC0 | ((codepoint >> 6) & 0x1F))); + 0xC0 | (codepoint >> 6))); result.append(1, static_cast<typename string_t::value_type>( 0x80 | (codepoint & 0x3F))); } @@ -9360,7 +10979,7 @@ private: { // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx result.append(1, static_cast<typename string_t::value_type>( - 0xE0 | ((codepoint >> 12) & 0x0F))); + 0xE0 | (codepoint >> 12))); result.append(1, static_cast<typename string_t::value_type>( 0x80 | ((codepoint >> 6) & 0x3F))); result.append(1, static_cast<typename string_t::value_type>( @@ -9370,7 +10989,7 @@ private: { // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx result.append(1, static_cast<typename string_t::value_type>( - 0xF0 | ((codepoint >> 18) & 0x07))); + 0xF0 | (codepoint >> 18))); result.append(1, static_cast<typename string_t::value_type>( 0x80 | ((codepoint >> 12) & 0x3F))); result.append(1, static_cast<typename string_t::value_type>( @@ -9380,7 +10999,8 @@ private: } else { - JSON_THROW(std::out_of_range( + JSON_THROW(parse_error::create( + 103, get_position(), "code points above 0x10FFFF are invalid")); } @@ -9402,7 +11022,9 @@ private: return "null literal"; case token_type::value_string: return "string literal"; - case token_type::value_number: + case lexer::token_type::value_unsigned: + case lexer::token_type::value_integer: + case lexer::token_type::value_float: return "number literal"; case token_type::begin_array: return "'['"; @@ -9444,10 +11066,9 @@ private: Proof (by contradiction): Assume a finite input. To loop forever, the loop must never hit code with a `break` statement. The only code - snippets without a `break` statement are the continue statements for - whitespace and byte-order-marks. To loop forever, the input must be an - infinite sequence of whitespace or byte-order-marks. This contradicts - the assumption of finite input, q.e.d. + snippets without a `break` statement is the continue statement for + whitespace. To loop forever, the input must be an infinite sequence + whitespace. This contradicts the assumption of finite input, q.e.d. */ token_type scan() { @@ -9629,6 +11250,7 @@ private: goto basic_json_parser_6; } { + position += static_cast<size_t>((m_cursor - m_start)); continue; } basic_json_parser_9: @@ -9665,37 +11287,47 @@ private: } if (yych <= '0') { - goto basic_json_parser_13; + goto basic_json_parser_43; } if (yych <= '9') { - goto basic_json_parser_15; + goto basic_json_parser_45; } goto basic_json_parser_5; basic_json_parser_13: yyaccept = 1; yych = *(m_marker = ++m_cursor); - if (yych <= 'D') + if (yych <= '9') { if (yych == '.') { - goto basic_json_parser_43; + goto basic_json_parser_47; + } + if (yych >= '0') + { + goto basic_json_parser_48; } } else { if (yych <= 'E') { - goto basic_json_parser_44; + if (yych >= 'E') + { + goto basic_json_parser_51; + } } - if (yych == 'e') + else { - goto basic_json_parser_44; + if (yych == 'e') + { + goto basic_json_parser_51; + } } } basic_json_parser_14: { - last_token_type = token_type::value_number; + last_token_type = token_type::value_unsigned; break; } basic_json_parser_15: @@ -9714,7 +11346,7 @@ private: { if (yych == '.') { - goto basic_json_parser_43; + goto basic_json_parser_47; } goto basic_json_parser_14; } @@ -9722,11 +11354,11 @@ private: { if (yych <= 'E') { - goto basic_json_parser_44; + goto basic_json_parser_51; } if (yych == 'e') { - goto basic_json_parser_44; + goto basic_json_parser_51; } goto basic_json_parser_14; } @@ -9753,7 +11385,7 @@ private: yych = *(m_marker = ++m_cursor); if (yych == 'a') { - goto basic_json_parser_45; + goto basic_json_parser_52; } goto basic_json_parser_5; basic_json_parser_24: @@ -9761,7 +11393,7 @@ private: yych = *(m_marker = ++m_cursor); if (yych == 'u') { - goto basic_json_parser_46; + goto basic_json_parser_53; } goto basic_json_parser_5; basic_json_parser_25: @@ -9769,7 +11401,7 @@ private: yych = *(m_marker = ++m_cursor); if (yych == 'r') { - goto basic_json_parser_47; + goto basic_json_parser_54; } goto basic_json_parser_5; basic_json_parser_26: @@ -9851,13 +11483,27 @@ private: } basic_json_parser_32: m_cursor = m_marker; - if (yyaccept == 0) + if (yyaccept <= 1) { - goto basic_json_parser_5; + if (yyaccept == 0) + { + goto basic_json_parser_5; + } + else + { + goto basic_json_parser_14; + } } else { - goto basic_json_parser_14; + if (yyaccept == 2) + { + goto basic_json_parser_44; + } + else + { + goto basic_json_parser_58; + } } basic_json_parser_33: ++m_cursor; @@ -9938,7 +11584,7 @@ private: } if (yych <= 'u') { - goto basic_json_parser_48; + goto basic_json_parser_55; } goto basic_json_parser_32; } @@ -10057,6 +11703,81 @@ private: } goto basic_json_parser_32; basic_json_parser_43: + yyaccept = 2; + yych = *(m_marker = ++m_cursor); + if (yych <= '9') + { + if (yych == '.') + { + goto basic_json_parser_47; + } + if (yych >= '0') + { + goto basic_json_parser_48; + } + } + else + { + if (yych <= 'E') + { + if (yych >= 'E') + { + goto basic_json_parser_51; + } + } + else + { + if (yych == 'e') + { + goto basic_json_parser_51; + } + } + } + basic_json_parser_44: + { + last_token_type = token_type::value_integer; + break; + } + basic_json_parser_45: + yyaccept = 2; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + fill_line_buffer(3); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '9') + { + if (yych == '.') + { + goto basic_json_parser_47; + } + if (yych <= '/') + { + goto basic_json_parser_44; + } + goto basic_json_parser_45; + } + else + { + if (yych <= 'E') + { + if (yych <= 'D') + { + goto basic_json_parser_44; + } + goto basic_json_parser_51; + } + else + { + if (yych == 'e') + { + goto basic_json_parser_51; + } + goto basic_json_parser_44; + } + } + basic_json_parser_47: yych = *++m_cursor; if (yych <= '/') { @@ -10064,16 +11785,36 @@ private: } if (yych <= '9') { - goto basic_json_parser_49; + goto basic_json_parser_56; } goto basic_json_parser_32; - basic_json_parser_44: + basic_json_parser_48: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '/') + { + goto basic_json_parser_50; + } + if (yych <= '9') + { + goto basic_json_parser_48; + } + basic_json_parser_50: + { + last_token_type = token_type::parse_error; + break; + } + basic_json_parser_51: yych = *++m_cursor; if (yych <= ',') { if (yych == '+') { - goto basic_json_parser_51; + goto basic_json_parser_59; } goto basic_json_parser_32; } @@ -10081,7 +11822,7 @@ private: { if (yych <= '-') { - goto basic_json_parser_51; + goto basic_json_parser_59; } if (yych <= '/') { @@ -10089,32 +11830,32 @@ private: } if (yych <= '9') { - goto basic_json_parser_52; + goto basic_json_parser_60; } goto basic_json_parser_32; } - basic_json_parser_45: + basic_json_parser_52: yych = *++m_cursor; if (yych == 'l') { - goto basic_json_parser_54; + goto basic_json_parser_62; } goto basic_json_parser_32; - basic_json_parser_46: + basic_json_parser_53: yych = *++m_cursor; if (yych == 'l') { - goto basic_json_parser_55; + goto basic_json_parser_63; } goto basic_json_parser_32; - basic_json_parser_47: + basic_json_parser_54: yych = *++m_cursor; if (yych == 'u') { - goto basic_json_parser_56; + goto basic_json_parser_64; } goto basic_json_parser_32; - basic_json_parser_48: + basic_json_parser_55: ++m_cursor; if (m_limit <= m_cursor) { @@ -10129,7 +11870,7 @@ private: } if (yych <= '9') { - goto basic_json_parser_57; + goto basic_json_parser_65; } goto basic_json_parser_32; } @@ -10137,7 +11878,7 @@ private: { if (yych <= 'F') { - goto basic_json_parser_57; + goto basic_json_parser_65; } if (yych <= '`') { @@ -10145,12 +11886,12 @@ private: } if (yych <= 'f') { - goto basic_json_parser_57; + goto basic_json_parser_65; } goto basic_json_parser_32; } - basic_json_parser_49: - yyaccept = 1; + basic_json_parser_56: + yyaccept = 3; m_marker = ++m_cursor; if ((m_limit - m_cursor) < 3) { @@ -10161,27 +11902,30 @@ private: { if (yych <= '/') { - goto basic_json_parser_14; + goto basic_json_parser_58; } if (yych <= '9') { - goto basic_json_parser_49; + goto basic_json_parser_56; } - goto basic_json_parser_14; } else { if (yych <= 'E') { - goto basic_json_parser_44; + goto basic_json_parser_51; } if (yych == 'e') { - goto basic_json_parser_44; + goto basic_json_parser_51; } - goto basic_json_parser_14; } - basic_json_parser_51: + basic_json_parser_58: + { + last_token_type = token_type::value_float; + break; + } + basic_json_parser_59: yych = *++m_cursor; if (yych <= '/') { @@ -10191,7 +11935,7 @@ private: { goto basic_json_parser_32; } - basic_json_parser_52: + basic_json_parser_60: ++m_cursor; if (m_limit <= m_cursor) { @@ -10200,35 +11944,35 @@ private: yych = *m_cursor; if (yych <= '/') { - goto basic_json_parser_14; + goto basic_json_parser_58; } if (yych <= '9') { - goto basic_json_parser_52; + goto basic_json_parser_60; } - goto basic_json_parser_14; - basic_json_parser_54: + goto basic_json_parser_58; + basic_json_parser_62: yych = *++m_cursor; if (yych == 's') { - goto basic_json_parser_58; + goto basic_json_parser_66; } goto basic_json_parser_32; - basic_json_parser_55: + basic_json_parser_63: yych = *++m_cursor; if (yych == 'l') { - goto basic_json_parser_59; + goto basic_json_parser_67; } goto basic_json_parser_32; - basic_json_parser_56: + basic_json_parser_64: yych = *++m_cursor; if (yych == 'e') { - goto basic_json_parser_61; + goto basic_json_parser_69; } goto basic_json_parser_32; - basic_json_parser_57: + basic_json_parser_65: ++m_cursor; if (m_limit <= m_cursor) { @@ -10243,7 +11987,7 @@ private: } if (yych <= '9') { - goto basic_json_parser_63; + goto basic_json_parser_71; } goto basic_json_parser_32; } @@ -10251,7 +11995,7 @@ private: { if (yych <= 'F') { - goto basic_json_parser_63; + goto basic_json_parser_71; } if (yych <= '`') { @@ -10259,30 +12003,30 @@ private: } if (yych <= 'f') { - goto basic_json_parser_63; + goto basic_json_parser_71; } goto basic_json_parser_32; } - basic_json_parser_58: + basic_json_parser_66: yych = *++m_cursor; if (yych == 'e') { - goto basic_json_parser_64; + goto basic_json_parser_72; } goto basic_json_parser_32; - basic_json_parser_59: + basic_json_parser_67: ++m_cursor; { last_token_type = token_type::literal_null; break; } - basic_json_parser_61: + basic_json_parser_69: ++m_cursor; { last_token_type = token_type::literal_true; break; } - basic_json_parser_63: + basic_json_parser_71: ++m_cursor; if (m_limit <= m_cursor) { @@ -10297,7 +12041,7 @@ private: } if (yych <= '9') { - goto basic_json_parser_66; + goto basic_json_parser_74; } goto basic_json_parser_32; } @@ -10305,7 +12049,7 @@ private: { if (yych <= 'F') { - goto basic_json_parser_66; + goto basic_json_parser_74; } if (yych <= '`') { @@ -10313,17 +12057,17 @@ private: } if (yych <= 'f') { - goto basic_json_parser_66; + goto basic_json_parser_74; } goto basic_json_parser_32; } - basic_json_parser_64: + basic_json_parser_72: ++m_cursor; { last_token_type = token_type::literal_false; break; } - basic_json_parser_66: + basic_json_parser_74: ++m_cursor; if (m_limit <= m_cursor) { @@ -10361,6 +12105,7 @@ private: } } + position += static_cast<size_t>((m_cursor - m_start)); return last_token_type; } @@ -10422,7 +12167,7 @@ private: if (m_stream == nullptr or m_stream->eof()) { // m_start may or may not be pointing into m_line_buffer at - // this point. We trust the standand library to do the right + // this point. We trust the standard library to do the right // thing. See http://stackoverflow.com/q/28142011/266378 m_line_buffer.assign(m_start, m_limit); @@ -10440,6 +12185,13 @@ private: m_line_buffer.erase(0, num_processed_chars); // read next line from input stream m_line_buffer_tmp.clear(); + + // check if stream is still good + if (m_stream->fail()) + { + JSON_THROW(parse_error::create(111, 0, "bad input stream")); + } + std::getline(*m_stream, m_line_buffer_tmp, '\n'); // add line with newline symbol to the line buffer @@ -10512,7 +12264,7 @@ private: m_start + 1 + x < m_cursor - 1 must hold to loop indefinitely. This can be rephrased to m_cursor - m_start - 2 > x. With the precondition, we x <= 0, meaning that the loop condition holds - indefinitly if i is always decreased. However, observe that the value + indefinitely if i is always decreased. However, observe that the value of i is strictly increasing with each iteration, as it is incremented by 1 in the iteration expression and never decremented inside the loop body. Hence, the loop condition will eventually be false which @@ -10521,7 +12273,8 @@ private: @return string value of current token without opening and closing quotes - @throw std::out_of_range if to_unicode fails + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails */ string_t get_string() const { @@ -10615,7 +12368,8 @@ private: if ((i + 6 >= m_limit) or *(i + 5) != '\\' or *(i + 6) != 'u') { - JSON_THROW(std::invalid_argument( + JSON_THROW(parse_error::create( + 102, get_position(), "missing low surrogate")); } @@ -10635,8 +12389,8 @@ private: else if (codepoint >= 0xDC00 and codepoint <= 0xDFFF) { // we found a lone low surrogate - JSON_THROW(std::invalid_argument( - "missing high surrogate")); + JSON_THROW(parse_error::create( + 102, get_position(), "missing high surrogate")); } else { @@ -10655,200 +12409,254 @@ private: } /*! - @brief parse floating point number - - This function (and its overloads) serves to select the most approprate - standard floating point number parsing function based on the type - supplied via the first parameter. Set this to @a - static_cast<number_float_t*>(nullptr). + @brief parse string into a built-in arithmetic type as if the current + locale is POSIX. - @param[in,out] endptr recieves a pointer to the first character after - the number + @note in floating-point case strtod may parse past the token's end - + this is not an error - @return the floating point number + @note any leading blanks are not handled */ - long double str_to_float_t(long double * /* type */, - char **endptr) const + struct strtonum { - return std::strtold( - reinterpret_cast<typename string_t::const_pointer>(m_start), - endptr); - } + public: + strtonum(const char *start, const char *end) + : m_start(start), m_end(end) + { + } - /*! - @brief parse floating point number + /*! + @return true iff parsed successfully as number of type T - This function (and its overloads) serves to select the most approprate - standard floating point number parsing function based on the type - supplied via the first parameter. Set this to @a - static_cast<number_float_t*>(nullptr). + @param[in,out] val shall contain parsed value, or undefined value + if could not parse + */ + template <typename T, typename = typename std::enable_if< + std::is_arithmetic<T>::value>::type> + bool to(T &val) const + { + return parse(val, std::is_integral<T>()); + } - @param[in,out] endptr recieves a pointer to the first character after - the number + private: + const char *const m_start = nullptr; + const char *const m_end = nullptr; - @return the floating point number - */ - double str_to_float_t(double * /* type */, char **endptr) const - { - return std::strtod( - reinterpret_cast<typename string_t::const_pointer>(m_start), - endptr); - } + // floating-point conversion - /*! - @brief parse floating point number + // overloaded wrappers for strtod/strtof/strtold + // that will be called from parse<floating_point_t> + static void strtof(float &f, const char *str, char **endptr) + { + f = std::strtof(str, endptr); + } - This function (and its overloads) serves to select the most approprate - standard floating point number parsing function based on the type - supplied via the first parameter. Set this to @a - static_cast<number_float_t*>(nullptr). + static void strtof(double &f, const char *str, char **endptr) + { + f = std::strtod(str, endptr); + } - @param[in,out] endptr recieves a pointer to the first character after - the number + static void strtof(long double &f, const char *str, char **endptr) + { + f = std::strtold(str, endptr); + } - @return the floating point number - */ - float str_to_float_t(float * /* type */, char **endptr) const - { - return std::strtof( - reinterpret_cast<typename string_t::const_pointer>(m_start), - endptr); - } + template <typename T> + bool parse(T &value, /*is_integral=*/std::false_type) const + { + // replace decimal separator with locale-specific version, + // when necessary; data will point to either the original + // string, or buf, or tempstr containing the fixed string. + std::string tempstr; + std::array<char, 64> buf; + const size_t len = static_cast<size_t>(m_end - m_start); - /*! - @brief return number value for number tokens + // lexer will reject empty numbers + assert(len > 0); - This function translates the last token into the most appropriate - number type (either integer, unsigned integer or floating point), - which is passed back to the caller via the result parameter. + // since dealing with strtod family of functions, we're + // getting the decimal point char from the C locale facilities + // instead of C++'s numpunct facet of the current std::locale + const auto loc = localeconv(); + assert(loc != nullptr); + const char decimal_point_char = (loc->decimal_point == nullptr) + ? '.' + : loc->decimal_point[0]; - This function parses the integer component up to the radix point or - exponent while collecting information about the 'floating point - representation', which it stores in the result parameter. If there is - no radix point or exponent, and the number can fit into a @ref - number_integer_t or @ref number_unsigned_t then it sets the result - parameter accordingly. + const char *data = m_start; - If the number is a floating point number the number is then parsed - using @a std:strtod (or @a std:strtof or @a std::strtold). + if (decimal_point_char != '.') + { + const size_t ds_pos = static_cast<size_t>( + std::find(m_start, m_end, '.') - m_start); - @param[out] result @ref basic_json object to receive the number, or - NAN if the conversion read past the current token. The latter case - needs to be treated by the caller function. - */ - void get_number(basic_json &result) const - { - assert(m_start != nullptr); + if (ds_pos != len) + { + // copy the data into the local buffer or tempstr, if + // buffer is too small; replace decimal separator, and + // update data to point to the modified bytes + if ((len + 1) < buf.size()) + { + std::copy(m_start, m_end, buf.begin()); + buf[len] = 0; + buf[ds_pos] = decimal_point_char; + data = buf.data(); + } + else + { + tempstr.assign(m_start, m_end); + tempstr[ds_pos] = decimal_point_char; + data = tempstr.c_str(); + } + } + } - const lexer::lexer_char_t *curptr = m_start; + char *endptr = nullptr; + value = 0; + // this calls appropriate overload depending on T + strtof(value, data, &endptr); - // accumulate the integer conversion result (unsigned for now) - number_unsigned_t value = 0; + // parsing was successful iff strtof parsed exactly the number + // of characters determined by the lexer (len) + const bool ok = (endptr == (data + len)); - // maximum absolute value of the relevant integer type - number_unsigned_t max; + if (ok and (value == static_cast<T>(0.0)) and (*data == '-')) + { + // some implementations forget to negate the zero + value = -0.0; + } + + return ok; + } - // temporarily store the type to avoid unecessary bitfield access - value_t type; + // integral conversion - // look for sign - if (*curptr == '-') + signed long long parse_integral(char **endptr, + /*is_signed*/ std::true_type) const { - type = value_t::number_integer; - max = static_cast<uint64_t>( - (std::numeric_limits<number_integer_t>::max)()) + - 1; - curptr++; + return std::strtoll(m_start, endptr, 10); } - else + + unsigned long long + parse_integral(char **endptr, /*is_signed*/ std::false_type) const { - type = value_t::number_unsigned; - max = static_cast<uint64_t>( - (std::numeric_limits<number_unsigned_t>::max)()); + return std::strtoull(m_start, endptr, 10); } - // count the significant figures - for (; curptr < m_cursor; curptr++) + template <typename T> + bool parse(T &value, /*is_integral=*/std::true_type) const { - // quickly skip tests if a digit - if (*curptr < '0' || *curptr > '9') - { - if (*curptr == '.') - { - // don't count '.' but change to float - type = value_t::number_float; - continue; - } - // assume exponent (if not then will fail parse): change to - // float, stop counting and record exponent details - type = value_t::number_float; - break; - } + char *endptr = nullptr; + errno = 0; // these are thread-local + const auto x = parse_integral(&endptr, std::is_signed<T>()); - // skip if definitely not an integer - if (type != value_t::number_float) - { - auto digit = static_cast<number_unsigned_t>(*curptr - '0'); + // called right overload? + static_assert( + std::is_signed<T>() == std::is_signed<decltype(x)>(), ""); - // overflow if value * 10 + digit > max, move terms around - // to avoid overflow in intermediate values - if (value > (max - digit) / 10) - { - // overflow - type = value_t::number_float; - } - else - { - // no overflow - value = value * 10 + digit; - } - } + value = static_cast<T>(x); + + return (x == static_cast<decltype(x)>( + value)) // x fits into destination T + and (x < 0) == (value < 0) // preserved sign + // and ((x != 0) or is_integral()) // strto[u]ll + // did nto fail + and (errno == 0) // strto[u]ll did not overflow + and (m_start < m_end) // token was not empty + and (endptr == m_end); // parsed entire token exactly } + }; + + /*! + @brief return number value for number tokens + + This function translates the last token into the most appropriate + number type (either integer, unsigned integer or floating point), + which is passed back to the caller via the result parameter. + + integral numbers that don't fit into the the range of the respective + type are parsed as number_float_t + + floating-point values do not satisfy std::isfinite predicate + are converted to value_t::null + + throws if the entire string [m_start .. m_cursor) cannot be + interpreted as a number + + @param[out] result @ref basic_json object to receive the number. + @param[in] token the type of the number token + */ + bool get_number(basic_json &result, const token_type token) const + { + assert(m_start != nullptr); + assert(m_start < m_cursor); + assert((token == token_type::value_unsigned) or + (token == token_type::value_integer) or + (token == token_type::value_float)); + + strtonum num_converter(reinterpret_cast<const char *>(m_start), + reinterpret_cast<const char *>(m_cursor)); - // save the value (if not a float) - if (type == value_t::number_unsigned) + switch (token) { - result.m_value.number_unsigned = value; - } - else if (type == value_t::number_integer) + case lexer::token_type::value_unsigned: { - // invariant: if we parsed a '-', the absolute value is between - // 0 (we allow -0) and max == -INT64_MIN - assert(value >= 0); - assert(value <= max); - - if (value == max) + number_unsigned_t val; + if (num_converter.to(val)) { - // we cannot simply negate value (== max == -INT64_MIN), - // see https://github.com/nlohmann/json/issues/389 - result.m_value.number_integer = - static_cast<number_integer_t>(INT64_MIN); + // parsing successful + result.m_type = value_t::number_unsigned; + result.m_value = val; + return true; } - else + break; + } + + case lexer::token_type::value_integer: + { + number_integer_t val; + if (num_converter.to(val)) { - // all other values can be negated safely - result.m_value.number_integer = - -static_cast<number_integer_t>(value); + // parsing successful + result.m_type = value_t::number_integer; + result.m_value = val; + return true; } + break; } - else + + default: + { + break; + } + } + + // parse float (either explicitly or because a previous conversion + // failed) + number_float_t val; + if (num_converter.to(val)) { - // parse with strtod - result.m_value.number_float = str_to_float_t( - static_cast<number_float_t *>(nullptr), nullptr); + // parsing successful + result.m_type = value_t::number_float; + result.m_value = val; - // replace infinity and NAN by null + // throw in case of infinity or NAN if (not std::isfinite(result.m_value.number_float)) { - type = value_t::null; - result.m_value = basic_json::json_value(); + JSON_THROW(out_of_range::create( + 406, "number overflow parsing '" + get_token_string() + + "'")); } + + return true; } - // save the type - result.m_type = type; + // couldn't parse number in any format + return false; } + constexpr size_t get_position() const { return position; } + private: /// optional input stream std::istream *m_stream = nullptr; @@ -10868,6 +12676,8 @@ private: const lexer_char_t *m_limit = nullptr; /// the last token type token_type last_token_type = token_type::end_of_input; + /// current position in the input (read bytes) + size_t position = 0; }; /*! @@ -10886,7 +12696,10 @@ private: { } - /// a parser reading from an input stream + /*! + @brief a parser reading from an input stream + @throw parse_error.111 if input stream is in a bad state + */ parser(std::istream &is, const parser_callback_t cb = nullptr) : callback(cb), m_lexer(is) { @@ -10908,7 +12721,12 @@ private: { } - /// public parser interface + /*! + @brief public parser interface + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ basic_json parse() { // read first token @@ -10925,7 +12743,12 @@ private: } private: - /// the actual parser + /*! + @brief the actual parser + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ basic_json parse_internal(bool keep) { auto result = basic_json(value_t::discarded); @@ -11105,9 +12928,11 @@ private: break; } - case lexer::token_type::value_number: + case lexer::token_type::value_unsigned: + case lexer::token_type::value_integer: + case lexer::token_type::value_float: { - m_lexer.get_number(result); + m_lexer.get_number(result, last_token); get_token(); break; } @@ -11134,6 +12959,9 @@ private: return last_token; } + /*! + @throw parse_error.101 if expected token did not occur + */ void expect(typename lexer::token_type t) const { if (t != last_token) @@ -11143,10 +12971,14 @@ private: ? ("'" + m_lexer.get_token_string() + "'") : lexer::token_type_name(last_token)); error_msg += "; expected " + lexer::token_type_name(t); - JSON_THROW(std::invalid_argument(error_msg)); + JSON_THROW(parse_error::create(101, m_lexer.get_position(), + error_msg)); } } + /*! + @throw parse_error.101 if unexpected token occurred + */ void unexpect(typename lexer::token_type t) const { if (t == last_token) @@ -11155,7 +12987,8 @@ private: error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token_string() + "'") : lexer::token_type_name(last_token)); - JSON_THROW(std::invalid_argument(error_msg)); + JSON_THROW(parse_error::create(101, m_lexer.get_position(), + error_msg)); } } @@ -11199,12 +13032,12 @@ public: empty string is assumed which references the whole JSON value - @throw std::domain_error if reference token is nonempty and does not - begin with a slash (`/`); example: `"JSON pointer must be empty or - begin with /"` - @throw std::domain_error if a tilde (`~`) is not followed by `0` - (representing `~`) or `1` (representing `/`); example: `"escape error: - ~ must be followed with 0 or 1"` + @throw parse_error.107 if the given JSON pointer @a s is nonempty and + does not begin with a slash (`/`); see example below + + @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s + is not followed by `0` (representing `~`) or `1` (representing `/`); + see example below @liveexample{The example shows the construction several valid JSON pointers as well as the exceptional behavior.,json_pointer} @@ -11244,12 +13077,16 @@ public: operator std::string() const { return to_string(); } private: - /// remove and return last reference pointer + /*! + @brief remove and return last reference pointer + @throw out_of_range.405 if JSON pointer has no parent + */ std::string pop_back() { if (is_root()) { - JSON_THROW(std::domain_error("JSON pointer has no parent")); + JSON_THROW( + out_of_range::create(405, "JSON pointer has no parent")); } auto last = reference_tokens.back(); @@ -11264,7 +13101,8 @@ public: { if (is_root()) { - JSON_THROW(std::domain_error("JSON pointer has no parent")); + JSON_THROW( + out_of_range::create(405, "JSON pointer has no parent")); } json_pointer result = *this; @@ -11276,6 +13114,9 @@ public: @brief create and return a reference to the pointed to value @complexity Linear in the number of reference tokens. + + @throw parse_error.109 if array index is not a number + @throw type_error.313 if value cannot be unflattened */ reference get_and_create(reference j) const { @@ -11312,8 +13153,17 @@ public: case value_t::array: { // create an entry in the array - result = &result->operator[]( - static_cast<size_type>(std::stoi(reference_token))); + JSON_TRY + { + result = &result->operator[]( + static_cast<size_type>(std::stoi(reference_token))); + } + JSON_CATCH(std::invalid_argument &) + { + JSON_THROW(parse_error::create( + 109, 0, "array index '" + reference_token + + "' is not a number")); + } break; } @@ -11326,7 +13176,8 @@ public: */ default: { - JSON_THROW(std::domain_error("invalid value to unflatten")); + JSON_THROW( + type_error::create(313, "invalid value to unflatten")); } } } @@ -11349,9 +13200,9 @@ public: @complexity Linear in the length of the JSON pointer. - @throw std::out_of_range if the JSON pointer can not be resolved - @throw std::domain_error if an array index begins with '0' - @throw std::invalid_argument if an array index was not a number + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved */ reference get_unchecked(pointer ptr) const { @@ -11363,7 +13214,7 @@ public: // check if reference token is a number const bool nums = std::all_of( reference_token.begin(), reference_token.end(), - [](const char x) { return std::isdigit(x); }); + [](const char x) { return (x >= '0' and x <= '9'); }); // change value to array for numbers or "-" or to object // otherwise @@ -11392,29 +13243,39 @@ public: if (reference_token.size() > 1 and reference_token[0] == '0') { - JSON_THROW(std::domain_error( - "array index must not begin with '0'")); + JSON_THROW(parse_error::create( + 106, 0, "array index '" + reference_token + + "' must not begin with '0'")); } if (reference_token == "-") { - // explicityly treat "-" as index beyond the end + // explicitly treat "-" as index beyond the end ptr = &ptr->operator[](ptr->m_value.array->size()); } else { // convert array index to number; unchecked access - ptr = &ptr->operator[]( - static_cast<size_type>(std::stoi(reference_token))); + JSON_TRY + { + ptr = &ptr->operator[](static_cast<size_type>( + std::stoi(reference_token))); + } + JSON_CATCH(std::invalid_argument &) + { + JSON_THROW(parse_error::create( + 109, 0, "array index '" + reference_token + + "' is not a number")); + } } break; } default: { - JSON_THROW( - std::out_of_range("unresolved reference token '" + - reference_token + "'")); + JSON_THROW(out_of_range::create( + 404, "unresolved reference token '" + reference_token + + "'")); } } } @@ -11422,6 +13283,12 @@ public: return *ptr; } + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ reference get_checked(pointer ptr) const { for (const auto &reference_token : reference_tokens) @@ -11440,31 +13307,42 @@ public: if (reference_token == "-") { // "-" always fails the range check - throw std::out_of_range( + JSON_THROW(out_of_range::create( + 402, "array index '-' (" + - std::to_string(ptr->m_value.array->size()) + - ") is out of range"); + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); } // error condition (cf. RFC 6901, Sect. 4) if (reference_token.size() > 1 and reference_token[0] == '0') { - JSON_THROW(std::domain_error( - "array index must not begin with '0'")); + JSON_THROW(parse_error::create( + 106, 0, "array index '" + reference_token + + "' must not begin with '0'")); } // note: at performs range check - ptr = &ptr->at( - static_cast<size_type>(std::stoi(reference_token))); + JSON_TRY + { + ptr = &ptr->at( + static_cast<size_type>(std::stoi(reference_token))); + } + JSON_CATCH(std::invalid_argument &) + { + JSON_THROW(parse_error::create( + 109, 0, "array index '" + reference_token + + "' is not a number")); + } break; } default: { - JSON_THROW( - std::out_of_range("unresolved reference token '" + - reference_token + "'")); + JSON_THROW(out_of_range::create( + 404, "unresolved reference token '" + reference_token + + "'")); } } } @@ -11479,6 +13357,11 @@ public: @return const reference to the JSON value pointed to by the JSON pointer + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved */ const_reference get_unchecked(const_pointer ptr) const { @@ -11498,31 +13381,42 @@ public: if (reference_token == "-") { // "-" cannot be used for const access - throw std::out_of_range( + JSON_THROW(out_of_range::create( + 402, "array index '-' (" + - std::to_string(ptr->m_value.array->size()) + - ") is out of range"); + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); } // error condition (cf. RFC 6901, Sect. 4) if (reference_token.size() > 1 and reference_token[0] == '0') { - JSON_THROW(std::domain_error( - "array index must not begin with '0'")); + JSON_THROW(parse_error::create( + 106, 0, "array index '" + reference_token + + "' must not begin with '0'")); } // use unchecked array access - ptr = &ptr->operator[]( - static_cast<size_type>(std::stoi(reference_token))); + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast<size_type>(std::stoi(reference_token))); + } + JSON_CATCH(std::invalid_argument &) + { + JSON_THROW(parse_error::create( + 109, 0, "array index '" + reference_token + + "' is not a number")); + } break; } default: { - JSON_THROW( - std::out_of_range("unresolved reference token '" + - reference_token + "'")); + JSON_THROW(out_of_range::create( + 404, "unresolved reference token '" + reference_token + + "'")); } } } @@ -11530,6 +13424,12 @@ public: return *ptr; } + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ const_reference get_checked(const_pointer ptr) const { for (const auto &reference_token : reference_tokens) @@ -11548,31 +13448,42 @@ public: if (reference_token == "-") { // "-" always fails the range check - throw std::out_of_range( + JSON_THROW(out_of_range::create( + 402, "array index '-' (" + - std::to_string(ptr->m_value.array->size()) + - ") is out of range"); + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); } // error condition (cf. RFC 6901, Sect. 4) if (reference_token.size() > 1 and reference_token[0] == '0') { - JSON_THROW(std::domain_error( - "array index must not begin with '0'")); + JSON_THROW(parse_error::create( + 106, 0, "array index '" + reference_token + + "' must not begin with '0'")); } // note: at performs range check - ptr = &ptr->at( - static_cast<size_type>(std::stoi(reference_token))); + JSON_TRY + { + ptr = &ptr->at( + static_cast<size_type>(std::stoi(reference_token))); + } + JSON_CATCH(std::invalid_argument &) + { + JSON_THROW(parse_error::create( + 109, 0, "array index '" + reference_token + + "' is not a number")); + } break; } default: { - JSON_THROW( - std::out_of_range("unresolved reference token '" + - reference_token + "'")); + JSON_THROW(out_of_range::create( + 404, "unresolved reference token '" + reference_token + + "'")); } } } @@ -11580,7 +13491,15 @@ public: return *ptr; } - /// split the string input to reference tokens + /*! + @brief split the string input to reference tokens + + @note This function is only called by the json_pointer constructor. + All exceptions below are documented there. + + @throw parse_error.107 if the pointer is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' + */ static std::vector<std::string> split(const std::string &reference_string) { @@ -11595,8 +13514,10 @@ public: // check if nonempty reference string begins with slash if (reference_string[0] != '/') { - JSON_THROW(std::domain_error( - "JSON pointer must be empty or begin with '/'")); + JSON_THROW(parse_error::create( + 107, 1, + "JSON pointer must be empty or begin with '/' - was: '" + + reference_string + "'")); } // extract the reference tokens: @@ -11632,9 +13553,9 @@ public: (reference_token[pos + 1] != '0' and reference_token[pos + 1] != '1')) { - JSON_THROW( - std::domain_error("escape error: '~' must be " - "followed with '0' or '1'")); + JSON_THROW(parse_error::create( + 108, 0, "escape character '~' must be followed " + "with '0' or '1'")); } } @@ -11646,7 +13567,6 @@ public: return result; } - private: /*! @brief replace all occurrences of a substring by another string @@ -11655,7 +13575,8 @@ public: @param[in] f the substring to replace with @a t @param[in] t the string to replace @a f - @pre The search string @a f must not be empty. + @pre The search string @a f must not be empty. **This precondition is + enforced with an assertion.** @since version 2.0.0 */ @@ -11753,13 +13674,18 @@ public: @param[in] value flattened JSON @return unflattened JSON + + @throw parse_error.109 if array index is not a number + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + @throw type_error.313 if value cannot be unflattened */ static basic_json unflatten(const basic_json &value) { if (not value.is_object()) { JSON_THROW( - std::domain_error("only objects can be unflattened")); + type_error::create(314, "only objects can be unflattened")); } basic_json result; @@ -11769,8 +13695,8 @@ public: { if (not element.second.is_primitive()) { - JSON_THROW(std::domain_error( - "values in object must be primitive")); + JSON_THROW(type_error::create( + 315, "values in object must be primitive")); } // assign value to reference pointed to by JSON pointer; Note @@ -11785,7 +13711,18 @@ public: return result; } - private: + friend bool operator==(json_pointer const &lhs, + json_pointer const &rhs) noexcept + { + return lhs.reference_tokens == rhs.reference_tokens; + } + + friend bool operator!=(json_pointer const &lhs, + json_pointer const &rhs) noexcept + { + return !(lhs == rhs); + } + /// the reference tokens std::vector<std::string> reference_tokens{}; }; @@ -11822,9 +13759,9 @@ public: @complexity Constant. - @throw std::out_of_range if the JSON pointer can not be resolved - @throw std::domain_error if an array index begins with '0' - @throw std::invalid_argument if an array index was not a number + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved @liveexample{The behavior is shown in the example.,operatorjson_pointer} @@ -11849,9 +13786,10 @@ public: @complexity Constant. - @throw std::out_of_range if the JSON pointer can not be resolved - @throw std::domain_error if an array index begins with '0' - @throw std::invalid_argument if an array index was not a number + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} @@ -11873,15 +13811,30 @@ public: @return reference to the element pointed to by @a ptr - @complexity Constant. + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. - @throw std::out_of_range if the JSON pointer can not be resolved - @throw std::domain_error if an array index begins with '0' - @throw std::invalid_argument if an array index was not a number + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. - @liveexample{The behavior is shown in the example.,at_json_pointer} + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer} */ reference at(const json_pointer &ptr) { return ptr.get_checked(this); } @@ -11895,15 +13848,30 @@ public: @return reference to the element pointed to by @a ptr - @complexity Constant. + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. - @throw std::out_of_range if the JSON pointer can not be resolved - @throw std::domain_error if an array index begins with '0' - @throw std::invalid_argument if an array index was not a number + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. - @liveexample{The behavior is shown in the example.,at_json_pointer_const} + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} */ const_reference at(const json_pointer &ptr) const { @@ -11918,7 +13886,7 @@ public: primitive. The original JSON value can be restored using the @ref unflatten() function. - @return an object that maps JSON pointers to primitve values + @return an object that maps JSON pointers to primitive values @note Empty objects and arrays are flattened to `null` and will not be reconstructed correctly by the @ref unflatten() function. @@ -11959,6 +13927,9 @@ public: @complexity Linear in the size the JSON value. + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitve + @liveexample{The following code shows how a flattened JSON object is unflattened into the original nested JSON object.,unflatten} @@ -11982,7 +13953,7 @@ public: [JSON Patch](http://jsonpatch.com) defines a JSON document structure for expressing a sequence of operations to apply to a JSON) document. With - this funcion, a JSON Patch is applied to the current JSON value by + this function, a JSON Patch is applied to the current JSON value by executing all operations from the patch. @param[in] json_patch JSON patch document @@ -11993,12 +13964,23 @@ public: any case, the original value is not changed: the patch is applied to a copy of the value. - @throw std::out_of_range if a JSON pointer inside the patch could not - be resolved successfully in the current JSON value; example: `"key baz - not found"` - @throw invalid_argument if the JSON patch is malformed (e.g., mandatory + @throw parse_error.104 if the JSON patch does not consist of an array of + objects + + @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory attributes are missing); example: `"operation add must have member path"` + @throw out_of_range.401 if an array index is out of range. + + @throw out_of_range.403 if a JSON pointer inside the patch could not be + resolved successfully in the current JSON value; example: `"key baz not + found"` + + @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", + "move") + + @throw other_error.501 if "test" operation was unsuccessful + @complexity Linear in the size of the JSON value and the length of the JSON patch. As usually only a fraction of the JSON value is affected by the patch, the complexity can usually be neglected. @@ -12030,7 +14012,7 @@ public: invalid }; - const auto get_op = [](const std::string op) { + const auto get_op = [](const std::string &op) { if (op == "add") { return patch_operations::add; @@ -12103,9 +14085,9 @@ public: if (static_cast<size_type>(idx) > parent.size()) { // avoid undefined behavior - JSON_THROW(std::out_of_range("array index " + - std::to_string(idx) + - " is out of range")); + JSON_THROW(out_of_range::create( + 401, "array index " + std::to_string(idx) + + " is out of range")); } else { @@ -12144,8 +14126,8 @@ public: } else { - JSON_THROW( - std::out_of_range("key '" + last_path + "' not found")); + JSON_THROW(out_of_range::create(403, "key '" + last_path + + "' not found")); } } else if (parent.is_array()) @@ -12155,15 +14137,14 @@ public: } }; - // type check + // type check: top level value must be an array if (not json_patch.is_array()) { - // a JSON patch must be an array of objects - JSON_THROW(std::invalid_argument( - "JSON patch must be an array of objects")); + JSON_THROW(parse_error::create( + 104, 0, "JSON patch must be an array of objects")); } - // iterate and apply th eoperations + // iterate and apply the operations for (const auto &val : json_patch) { // wrapper to get a value for an operation @@ -12180,27 +14161,28 @@ public: // check if desired value is present if (it == val.m_value.object->end()) { - JSON_THROW(std::invalid_argument( + JSON_THROW(parse_error::create( + 105, 0, error_msg + " must have member '" + member + "'")); } // check if result is of type string if (string_type and not it->second.is_string()) { - JSON_THROW(std::invalid_argument( - error_msg + " must have string member '" + member + - "'")); + JSON_THROW(parse_error::create( + 105, 0, error_msg + " must have string member '" + + member + "'")); } // no error: return value return it->second; }; - // type check + // type check: every element of the array must be an object if (not val.is_object()) { - JSON_THROW(std::invalid_argument( - "JSON patch must be an array of objects")); + JSON_THROW(parse_error::create( + 104, 0, "JSON patch must be an array of objects")); } // collect mandatory members @@ -12267,7 +14249,7 @@ public: success = (result.at(ptr) == get_value("test", "value", false)); } - JSON_CATCH(std::out_of_range &) + JSON_CATCH(out_of_range &) { // ignore out of range errors: success remains false } @@ -12275,8 +14257,8 @@ public: // throw an exception if test fails if (not success) { - JSON_THROW( - std::domain_error("unsuccessful: " + val.dump())); + JSON_THROW(other_error::create(501, "unsuccessful: " + + val.dump())); } break; @@ -12286,8 +14268,8 @@ public: { // op must be "add", "remove", "replace", "move", "copy", or // "test" - JSON_THROW(std::invalid_argument("operation value '" + op + - "' is invalid")); + JSON_THROW(parse_error::create( + 105, 0, "operation value '" + op + "' is invalid")); } } } @@ -12310,8 +14292,8 @@ public: @note Currently, only `remove`, `add`, and `replace` operations are generated. - @param[in] source JSON value to copare from - @param[in] target JSON value to copare against + @param[in] source JSON value to compare from + @param[in] target JSON value to compare against @param[in] path helper value to create JSON pointers @return a JSON patch to convert the @a source to @a target @@ -12499,6 +14481,22 @@ struct hash<nlohmann::json> return h(j.dump()); } }; + +/// specialization for std::less<value_t> +template <> +struct less<::nlohmann::detail::value_t> +{ + /*! + @brief compare two value_t enum values + @since version 3.0.0 + */ + bool operator()(nlohmann::detail::value_t lhs, + nlohmann::detail::value_t rhs) const noexcept + { + return nlohmann::detail::operator<(lhs, rhs); + } +}; + } // namespace std /*! @@ -12543,11 +14541,14 @@ inline nlohmann::json::json_pointer operator"" _json_pointer(const char *s, #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #pragma GCC diagnostic pop #endif +#if defined(__clang__) +#pragma GCC diagnostic pop +#endif // clean up +#undef JSON_CATCH #undef JSON_THROW #undef JSON_TRY -#undef JSON_CATCH #undef JSON_DEPRECATED #endif