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